Relations
Builders
Section titled “Builders”jsorm uses explicit relation builders that describe the exact SQL relationship between two models. Each builder generates the appropriate join and mutation SQL without hidden eager loading or lazy proxies.
Relation builders
Section titled “Relation builders”| Builder | SQL relationship | When to use |
|---|---|---|
t.belongsTo(Model) | Foreign key on this table | User has roleId column |
t.hasOne(Model) | Foreign key on other table | Profile has userId column |
t.hasMany(Model) | Foreign key on other table | Post has authorId column |
t.manyToMany(Model) | Junction table | Posts ↔ Tags via post_tags |
Example
Section titled “Example”import { defineModel, t } from 'jsorm';
const Role = defineModel('roles', { id: t.number().primary(), name: t.string().unique(),});
const Profile = defineModel('profiles', { id: t.number().primary(), bio: t.string().optional(),});
const Tag = defineModel('tags', { id: t.number().primary(), name: t.string().unique(),});
const Post = defineModel('posts', { id: t.number().primary(), title: t.string(), // Infer junction table name from naming config (default: alphabetical) tags: t.manyToMany(Tag), // Or specify junction table and FK columns explicitly: // tags: t.manyToMany(Tag, { // through: 'post_tags', // throughLocalKey: 'post_id', // throughForeignKey: 'tag_id', // }),});
const User = defineModel('users', { id: t.number().primary(), name: t.string(), role: t.belongsTo(Role, { onUpdate: 'cascade', onDelete: 'restrict', constraintName: 'fk_users_role_id', // explicit FK constraint name }).index(), profile: t.hasOne(Profile), posts: t.hasMany(Post),});Selecting relations
Section titled “Selecting relations”Include relations in select to load them as nested objects:
const users = await jsorm.get(User, { select: { id: true, name: true, role: { name: true }, profile: { bio: true }, posts: { title: true, tags: { name: true }, }, },});// Typed: Array<{ id: number; name: string; role: { name: string }; ... }>Filtering through relations
Section titled “Filtering through relations”Use nested where to filter by relation fields:
const admins = await jsorm.get(User, { select: { name: true }, where: { role: { name: { eq: 'admin' } }, posts: { title: { contains: 'release' } }, },});Relation mutations
Section titled “Relation mutations”Connect to an existing record
Section titled “Connect to an existing record”await jsorm.update(User, { data: { role: { connect: 1 }, }, where: { id: 5 },});Create and connect
Section titled “Create and connect”await jsorm.update(User, { data: { profile: { create: { bio: 'Builder from Day 1' }, }, }, where: { id: 5 },});Many-to-many connect and disconnect
Section titled “Many-to-many connect and disconnect”await jsorm.update(Post, { data: { tags: { connect: [1, 2, 3], disconnect: [4], }, }, where: { id: 10 },});Full example with mixed mutation
Section titled “Full example with mixed mutation”await jsorm.insert(User, { name: 'Alice', role: { connect: 1 }, profile: { create: { bio: 'Builder' }, }, posts: { create: [ { title: 'First post', tags: { connect: [1, 2] } }, ], },});Referential integrity options
Section titled “Referential integrity options”Configure onUpdate and onDelete on belongsTo to control database-level referential integrity:
role: t.belongsTo(Role, { onUpdate: 'cascade', // 'cascade' | 'restrict' | 'set-null' | 'no-action' onDelete: 'restrict',}),Best practices
Section titled “Best practices”- Use the relation builder that matches the real data shape — don’t use
hasManywhen the relationship ismanyToMany. - Configure
onUpdateandonDeleteintentionally rather than relying on database defaults. - Keep relation mutations close to write operations instead of scattering pivot logic manually.
- Always include relation fields explicitly in
select— jsorm never loads relations implicitly.