Skip to content

Models

A model is a class that declares a database table's schema and behavior in one place.

In one example

The smallest real model: a Drizzle table wrapped in a class, registered with @model.

typescript
import { pgTable, text } from 'drizzle-orm/pg-core';
import { BaseEntity, model, generateIdColumnDefs } from '@venizia/ignis';

@model({ type: 'entity' })
export class User extends BaseEntity<typeof User.schema> {
  static override schema = pgTable('User', {
    ...generateIdColumnDefs({ id: { dataType: 'string' } }),
    email: text('email').notNull(),
  });
}

A @repository binds this model to a datasource, and the datasource auto-discovers the schema from that binding - no manual schema registration.

How it works

  • Registration. The @model decorator registers the class in the framework's metadata registry, keyed by table name (resolved as tableName > static TABLE_NAME > class name).
  • Validation at decoration time. It validates settings.defaultLimit and, when you declare an authorization principal, copies it onto the static AUTHORIZATION_SUBJECT property.
  • Plain Drizzle schema. The static schema is a plain Drizzle pgTable. Enrichers such as generateIdColumnDefs return column definitions you spread into that table, so common columns (id, timestamps, audit, principal) stay standardized across models.
  • Zod on demand. BaseEntity generates Zod schemas from the Drizzle schema via getSchema({ type }) - 'select', 'create', and 'update' variants for validating query results, inserts, and updates. The generator is a shared lazy singleton, so there is no per-entity cost.

Two layers

LayerClassCarries
Engine-neutral rootAbstractEntityA name, getSchema(), getIdType(), and toObject()/toJSON()
PostgreSQL connectorBaseEntity (canonical class BaseRelationalEntity)Adds the Drizzle-backed schema and Zod generation

Everything on this page is the PostgreSQL connector - see the Full reference and Connectors for the base-vs-connector split.

Common tasks

Add id and timestamp columns

Enrichers return column definitions - spread them into the pgTable. generateIdColumnDefs adds the primary key; generateTzColumnDefs adds createdAt and modifiedAt.

typescript
import { pgTable, text } from 'drizzle-orm/pg-core';
import { BaseEntity, model, generateIdColumnDefs, generateTzColumnDefs } from '@venizia/ignis';

@model({ type: 'entity' })
export class Article extends BaseEntity<typeof Article.schema> {
  static override schema = pgTable('Article', {
    ...generateIdColumnDefs({ id: { dataType: 'string' } }),
    ...generateTzColumnDefs(),
    title: text('title').notNull(),
  });
}

Hide a field

List a column in settings.hiddenProperties. Hidden columns are excluded at the SQL level - never selected or returned through repositories.

typescript
@model({
  type: 'entity',
  settings: { hiddenProperties: ['password'] },
})
export class User extends BaseEntity<typeof User.schema> {
  static override schema = pgTable('User', {
    ...generateIdColumnDefs({ id: { dataType: 'string' } }),
    email: text('email').notNull(),
    password: text('password'),
  });
}

Apply a default filter

settings.defaultFilter is merged into every repository read, count, update, and delete for the model. A common use is soft delete.

typescript
import { model, BaseEntity } from '@venizia/ignis';
import { postTable } from '@/schemas';

@model({
  type: 'entity',
  settings: { defaultFilter: { where: { isDeleted: false } } },
})
export class Post extends BaseEntity<typeof Post.schema> {
  static override schema = postTable;
}

Pass options: { shouldSkipDefaultFilter: true } on a query to bypass it. See Default Filter.

Set a default limit

settings.defaultLimit caps queries that omit limit. It must be a positive integer (validated at decoration time) and falls back to the global DEFAULT_LIMIT (10).

typescript
import { model, BaseEntity } from '@venizia/ignis';
import { eventTable } from '@/schemas';

@model({
  type: 'entity',
  settings: { defaultLimit: 50 },
})
export class Event extends BaseEntity<typeof Event.schema> {
  static override schema = eventTable;
}

See Pagination.

Declare an authorization principal

settings.authorize.principal names the model as an authorization subject. The decorator auto-populates the static AUTHORIZATION_SUBJECT from it.

typescript
import { model, BaseEntity } from '@venizia/ignis';
import { userTable } from '@/schemas';

@model({
  type: 'entity',
  settings: { authorize: { principal: 'User' } },
})
export class User extends BaseEntity<typeof User.schema> {
  static override schema = userTable;
}

See Authorization.

See also

Files: