zanith

Mixins & Reuse

Mixins are reusable field groups that eliminate repetition across your models. Instead of copy-pasting id, timestamps, and audit fields into every model, spread a mixin with one line.

The problem

Every model needs an ID. Most need timestamps. Many need audit trail fields or soft delete. Without mixins, you'd write the same 3-6 lines in every model. With 50+ models, that's hundreds of duplicated lines — and if you change the ID strategy, you change it everywhere.

Built-in mixins

Zanith provides these mixins out of the box. Use them with the spread syntax:

MixinWhat it addsFields created
...m.id()UUID primary keyid: uuid, primary, default(uuid())
...m.intId()Auto-increment integer PKid: int, primary, default(autoincrement())
...m.timestamps()Created + updated timestampscreatedAt: datetime default(now()), updatedAt: datetime autoUpdate
...m.auditable()Who created/updated this recordcreatedById: uuid nullable, updatedById: uuid nullable
...m.orgScoped()Multi-tenant foreign keyorganizationId: uuid with database index
...m.softDelete()Soft delete supportdeletedAt: datetime nullable

Using mixins

Spread mixins at the top of your fields, then add model-specific fields below. You can use any combination:

TSmodels/order.ts
const Order = defineModel((m) => ({
name: 'Order',
table: 'orders',
fields: {
...m.id(), // → id (uuid pk)
...m.timestamps(), // → createdAt, updatedAt
...m.auditable(), // → createdById, updatedById
...m.orgScoped(), // → organizationId (indexed)
...m.softDelete(), // → deletedAt (nullable)
 
// Model-specific fields:
total: m.float(),
status: m.enum(OrderStatus).default('PENDING'),
customerId: m.uuid().index(),
},
}));

Custom mixins

Since the builder is plain TypeScript, creating custom mixins is just writing a function that returns an object of field builders. This is where Zanith's TypeScript-first approach shines — no special syntax, just functions.

TSmixins/address.ts
// Custom mixin: reusable address fields
export function addressFields(m) {
return {
street: m.string(),
city: m.string(),
state: m.string(),
zip: m.string(),
country: m.string().default('US'),
};
}
 
// Use in any model:
const Customer = defineModel((m) => ({
name: 'Customer', table: 'customers',
fields: {
...m.id(),
...m.timestamps(),
...addressFields(m), // ← reused across models
name: m.string(),
email: m.string().unique(),
},
}));
 
const Warehouse = defineModel((m) => ({
name: 'Warehouse', table: 'warehouses',
fields: {
...m.id(),
...addressFields(m), // ← same fields, different model
capacity: m.int(),
},
}));

Conditional mixins

Because mixins are just functions, you can use conditions:

TS
function tenantFields(m, { multiTenant = false }) {
if (!multiTenant) return {};
return {
...m.orgScoped(), // only added when multi-tenant is enabled
};
}
 
const Product = defineModel((m) => ({
name: 'Product', table: 'products',
fields: {
...m.id(),
...tenantFields(m, { multiTenant: true }),
name: m.string(),
},
}));