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:
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)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:
| Method | Type | Description |
|---|---|---|
f.id() | string | Auto-generated primary key (time-sortable ID) |
f.string() | string | Text values |
f.number() | number | Numeric values |
f.boolean() | boolean | True/false values |
f.date() | Date | Date/time values (serialized automatically) |
f.json<T>() | T | Arbitrary JSON data with optional type parameter |
f.enum(values) | string | String constrained to specified values |
Field Modifiers
Chain modifiers after any field type:
| Modifier | Description |
|---|---|
.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-generatedconst warning = await Warning.create({
guildId: '123456789',
userId: '987654321',
reason: 'Spam in general chat',
severity: 'medium'
})
// warning.id is auto-generatedFields 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:
| Error | When |
|---|---|
ValidationError | Invalid field value, missing required field, or enum violation |
UniqueConstraintError | Duplicate value for a unique field |
FeatureNotSupportedError | Operation 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
}