LogoRobo.js

Models & Schemas

Define structured data with typed fields, validation, and auto-generated IDs.

Models give Flashcore a Prisma-like API for structured data. Define a schema with the f field builder and get type-safe CRUD operations.

Defining a Model

Import createModel and f from robo.js/flashcore:

src/models/Warning.ts
import { createModel, f } from 'robo.js/flashcore'

const schema = {
	id: f.id(),
	guildId: f.string(),
	userId: f.string(),
	reason: f.string(),
	severity: f.enum(['low', 'medium', 'high']),
	points: f.number().default(1),
	active: f.boolean().default(true),
	metadata: f.json<{ notes?: string }>().optional(),
	createdAt: f.date().default(() => new Date())
}

export const Warning = createModel('Warning', schema)
src/models/Warning.js
import { createModel, f } from 'robo.js/flashcore'

const schema = {
	id: f.id(),
	guildId: f.string(),
	userId: f.string(),
	reason: f.string(),
	severity: f.enum(['low', 'medium', 'high']),
	points: f.number().default(1),
	active: f.boolean().default(true),
	metadata: f.json().optional(),
	createdAt: f.date().default(() => new Date())
}

export const Warning = createModel('Warning', schema)

Field Types

The f builder provides methods for each supported type:

MethodTypeDescription
f.id()stringAuto-generated primary key (time-sortable ID)
f.string()stringText values
f.number()numberNumeric values
f.boolean()booleanTrue/false values
f.date()DateDate/time values (serialized automatically)
f.json<T>()TArbitrary JSON data with optional type parameter
f.enum(values)stringString constrained to specified values

Field Modifiers

Chain modifiers after any field type:

ModifierDescription
.optional()Field can be undefined (omitted on create)
.unique()Value must be unique across all records
.default(value)Auto-fill on create. Accepts a value or factory function
.indexed()Create a sorted index for faster queries
.version()Auto-incrementing version for optimistic locking
const schema = {
	id: f.id(),
	email: f.string().unique(),
	nickname: f.string().optional(),
	role: f.enum(['member', 'moderator', 'admin']).default('member'),
	xp: f.number().default(0).indexed(),
	lastSeen: f.date().default(() => new Date()),
	version: f.number().version()
}
const schema = {
	id: f.id(),
	email: f.string().unique(),
	nickname: f.string().optional(),
	role: f.enum(['member', 'moderator', 'admin']).default('member'),
	xp: f.number().default(0).indexed(),
	lastSeen: f.date().default(() => new Date()),
	version: f.number().version()
}

Compound Unique Constraints

Enforce uniqueness across a combination of fields with compoundUnique:

import { createModel, f, compoundUnique } from 'robo.js/flashcore'

export const GuildMember = createModel('GuildMember', {
	id: f.id(),
	guildId: f.string(),
	userId: f.string(),
	nickname: f.string().optional(),
	_guildUser: compoundUnique(['guildId', 'userId'])
})
import { createModel, f, compoundUnique } from 'robo.js/flashcore'

export const GuildMember = createModel('GuildMember', {
	id: f.id(),
	guildId: f.string(),
	userId: f.string(),
	nickname: f.string().optional(),
	_guildUser: compoundUnique(['guildId', 'userId'])
})

The constraint name (prefixed with _) is only used for identification. Attempting to create a duplicate throws a UniqueConstraintError.

CRUD Operations

Create

const warning = await Warning.create({
	guildId: '123456789',
	userId: '987654321',
	reason: 'Spam in general chat',
	severity: 'medium'
})
// warning.id is auto-generated
const warning = await Warning.create({
	guildId: '123456789',
	userId: '987654321',
	reason: 'Spam in general chat',
	severity: 'medium'
})
// warning.id is auto-generated

Fields with defaults are filled automatically. The id field is auto-generated unless you provide one.

Find Unique

const warning = await Warning.findUnique({ where: { id: 'abc123' } })
// Returns null if not found

// Find by unique field
const member = await GuildMember.findUnique({
	where: { email: 'user@example.com' }
})
const warning = await Warning.findUnique({ where: { id: 'abc123' } })
// Returns null if not found

// Find by unique field
const member = await GuildMember.findUnique({
	where: { email: 'user@example.com' }
})

Find Many

const warnings = await Warning.findMany({
	where: { guildId: '123456789', active: true },
	orderBy: { createdAt: 'desc' },
	take: 10
})
const warnings = await Warning.findMany({
	where: { guildId: '123456789', active: true },
	orderBy: { createdAt: 'desc' },
	take: 10
})

See Queries for the full filtering, sorting, and pagination API.

Update

const updated = await Warning.update({
	where: { id: 'abc123' },
	data: { active: false, reason: 'Updated reason' }
})
const updated = await Warning.update({
	where: { id: 'abc123' },
	data: { active: false, reason: 'Updated reason' }
})

Delete

const deleted = await Warning.delete({ where: { id: 'abc123' } })
const deleted = await Warning.delete({ where: { id: 'abc123' } })

Count

const total = await Warning.count({
	where: { guildId: '123456789', active: true }
})
const total = await Warning.count({
	where: { guildId: '123456789', active: true }
})

Model Hooks

Hooks let you run logic before or after CRUD operations:

export const Warning = createModel('Warning', schema, {
	hooks: {
		beforeCreate: (data) => {
			// Modify data before saving
			return { ...data, reason: data.reason.trim() }
		},
		afterCreate: (record) => {
			console.log(`Warning ${record.id} created`)
		},
		beforeUpdate: (data, existing) => {
			return data
		},
		afterUpdate: (record) => {
			console.log(`Warning ${record.id} updated`)
		},
		beforeDelete: (record) => {
			console.log(`About to delete warning ${record.id}`)
		},
		afterDelete: (record) => {
			console.log(`Warning ${record.id} deleted`)
		}
	}
})
export const Warning = createModel('Warning', schema, {
	hooks: {
		beforeCreate: (data) => {
			return { ...data, reason: data.reason.trim() }
		},
		afterCreate: (record) => {
			console.log(`Warning ${record.id} created`)
		},
		beforeUpdate: (data, existing) => {
			return data
		},
		afterUpdate: (record) => {
			console.log(`Warning ${record.id} updated`)
		},
		beforeDelete: (record) => {
			console.log(`About to delete warning ${record.id}`)
		},
		afterDelete: (record) => {
			console.log(`Warning ${record.id} deleted`)
		}
	}
})

beforeCreate and beforeUpdate can return modified data to transform the input.

Custom Methods

Add reusable methods to your model:

export const Warning = createModel('Warning', schema, {
	methods: {
		async getActiveForUser(guildId: string, userId: string) {
			return Warning.findMany({
				where: { guildId, userId, active: true },
				orderBy: { createdAt: 'desc' }
			})
		}
	}
})

// Usage
const warnings = await (Warning as any).getActiveForUser('123', '456')
export const Warning = createModel('Warning', schema, {
	methods: {
		async getActiveForUser(guildId, userId) {
			return Warning.findMany({
				where: { guildId, userId, active: true },
				orderBy: { createdAt: 'desc' }
			})
		}
	}
})

// Usage
const warnings = await Warning.getActiveForUser('123', '456')

TypeScript Inference

Use InferModelType to derive a TypeScript type from your schema:

import { createModel, f, type InferModelType } from 'robo.js/flashcore'

const schema = {
	id: f.id(),
	name: f.string(),
	score: f.number().optional()
}

export const Player = createModel('Player', schema)

// Derive the type
type PlayerRecord = InferModelType<typeof schema>
// { id: string; name: string; score?: number }

Error Handling

Flashcore throws specific errors for different failure cases:

ErrorWhen
ValidationErrorInvalid field value, missing required field, or enum violation
UniqueConstraintErrorDuplicate value for a unique field
FeatureNotSupportedErrorOperation requires adapter capabilities not available
import { UniqueConstraintError, ValidationError } from 'robo.js/flashcore'

try {
	await GuildMember.create({ guildId: '123', userId: '456' })
} catch (error) {
	if (error instanceof UniqueConstraintError) {
		return 'This member already exists.'
	}
	if (error instanceof ValidationError) {
		return `Invalid data: ${error.message}`
	}
	throw error
}
import { UniqueConstraintError, ValidationError } from 'robo.js/flashcore'

try {
	await GuildMember.create({ guildId: '123', userId: '456' })
} catch (error) {
	if (error instanceof UniqueConstraintError) {
		return 'This member already exists.'
	}
	if (error instanceof ValidationError) {
		return `Invalid data: ${error.message}`
	}
	throw error
}

On this page