Models
Single source of truth for types and queries
Section titled “Single source of truth for types and queries”Models are the single source of truth in jsorm. A model definition drives both TypeScript types and runtime query behavior.
Defining a model
Section titled “Defining a model”import type { InferInput, InferModel } from 'jsorm';import { defineModel, t } from 'jsorm';
const User = defineModel('users', { id: t.number().primary(), name: t.string(), email: t.string().optional(), active: t.boolean().default(true), createdAt: t.date(), score: t.number().nullable(),});
type UserRecord = InferModel<typeof User>;// {// id: number;// name: string;// email?: string;// active: boolean;// createdAt: Date;// score: number | null;// }
type UserInput = InferInput<typeof User>;// {// name: string;// email?: string;// active?: boolean;// createdAt: Date;// score?: number | null;// }// Note: primary keys and relation fields excluded from InferInput; FK columns stay includedField builders
Section titled “Field builders”| Builder | TypeScript type | Notes |
|---|---|---|
t.string() | string | VARCHAR-equivalent |
t.text() | string | Unbounded text / TEXT column |
t.number() | number | Numeric / FLOAT |
t.boolean() | boolean | |
t.date() | Date | Date only |
t.time() | string | Time string HH:MM:SS |
t.dateTime() | Date | Full timestamp |
t.uuid() | string | UUID string |
t.autoIncrement() | number | Auto-incrementing integer primary key |
t.enum(['a', 'b'] as const) | 'a' | 'b' | String union, checked at runtime |
t.json() | unknown | Stored as JSON, returned as parsed object |
t.belongsTo(Model) | ModelRecord | FK → parent |
t.hasOne(Model) | ModelRecord | Reverse of belongsTo |
t.hasMany(Model) | ModelRecord[] | One-to-many |
t.manyToMany(Model) | ModelRecord[] | Many-to-many via junction |
Field modifiers
Section titled “Field modifiers”t.string() .optional() // undefined allowed; excluded from required input .nullable() // null allowed in type and DB .primary() // marks as primary key (excluded from InferInput) .default('unknown') // default value; makes input optional .unique() // UNIQUE constraint in migrations .index() // INDEX in migrationsModel with all common modifiers
Section titled “Model with all common modifiers”const Post = defineModel('posts', { id: t.autoIncrement().primary(), title: t.string(), slug: t.string().unique(), body: t.text().optional(), status: t.enum(['draft', 'published', 'archived'] as const).default('draft'), published: t.boolean().default(false), viewCount: t.number().default(0), publishedAt: t.dateTime().nullable(), deletedAt: t.date().nullable(), metadata: t.json(),});Relations
Section titled “Relations”const Role = defineModel('roles', { id: t.number().primary(), name: t.string().unique(),});
const User = defineModel('users', { id: t.number().primary(), name: t.string(), // belongsTo: FK column on this table role: t.belongsTo(Role, { onUpdate: 'cascade', onDelete: 'restrict', constraintName: 'fk_users_role_id', // explicit constraint name for migrations }).index(),});
const Tag = defineModel('tags', { id: t.number().primary(), name: t.string(),});
const Article = defineModel('articles', { id: t.number().primary(), title: t.string(), tags: t.manyToMany(Tag), author: t.belongsTo(User),});Relation mutation shapes in insert/update:
belongsTo:role: 1,role: { connect: 1 },role: { connect: null }hasOne:profile: { connect: 1 },profile: { create: { ... } }hasMany:posts: { connect: [1, 2] },posts: { create: [{ ... }] }manyToMany:tags: [1, 2],tags: { connect: [1, 2] },tags: { create: [{ ... }] }
Organizing models
Section titled “Organizing models”For larger projects, co-locate models near domain boundaries:
src/ schema/ main/ user.ts ← defineModel('users', {...}) role.ts posts/ article.ts tag.ts index.ts ← re-exports all modelsjsorm.config.ts ← defines connectionSources, migrationSources, modelsBest practices
Section titled “Best practices”- Keep table names stable and explicit — rename only through migrations.
- Use
t.autoIncrement().primary()for auto-incrementing integer PKs. - Use
t.uuid()for UUID primary keys. - Prefer inferred
InferModel/InferInputinstead of manual interfaces. - One model per file in large projects; co-locate small related models.
- Add explicit
constraintNameonbelongsTowhen you need predictable FK names in migrations.