Imperative Definition
defineEntity defines an entity without decorators. It takes the same options as the decorators and registers the same metadata, so the two styles mix freely in one project.
import { defineEntity } from 'uql-orm';import { v7 as uuidv7 } from 'uuid';
export class User { id!: string; email?: string | null;}
export class Post { id!: number; title!: string; authorId?: string | null; author?: User; publishedAt?: Date | null;}
defineEntity(User, { fields: { id: { type: 'uuid', isId: true, onInsert: uuidv7 }, email: { type: String, unique: true }, },});
defineEntity(Post, { fields: { id: { type: Number, isId: true }, title: { type: String, nullable: false }, authorId: { references: () => User }, publishedAt: { type: Date }, }, relations: { author: { cardinality: 'm1', entity: () => User, references: (post) => post.authorId, }, }, indexes: [{ columns: (post) => [post.title, post.authorId], unique: true }], filters: { published: { where: { publishedAt: { $ne: null } }, default: false }, },});Reach for it when decorators get in the way:
- Your transformer does not implement them, like Oxc (Vite 8’s own).
- You are in a NestJS app, which keeps
experimentalDecoratorson for its own DI, and one config cannot compile both decorator specs. - You run the CLI on plain
nodewithouttsx. Node’s type stripping runs no decorators, so auql.config.tsimporting decorated entities needs Bun ortsxinstalled (the CLI uses it when it finds it), while adefineEntityfile loads as is. - You write JavaScript, or want to leave domain classes unannotated.
Options
Section titled “Options”Each key matches a decorator:
| Key | Decorator | Notes |
|---|---|---|
name |
@Entity |
Table name. Set it if your build minifies. |
schema |
@Entity |
See multiple schemas. |
fields |
@Field / @Id |
Field options, with is in place of @Id. |
relations |
@Many and the others |
Relation options, plus cardinality: '11', '1m', 'm1' or 'mm'. |
indexes |
@Index |
{ columns: (post) => [post., see Indexes. |
checks |
@Entity |
Check constraints. |
triggers |
@Trigger |
Trigger options. |
hooks |
Hook decorators | Each lifecycle event and its methods: { before. |
filters |
@Filter |
Filter options. |
extends |
class Child extends Base |
A base to inherit from, see Inheritance. |
Incremental registration
Section titled “Incremental registration”When the shape arrives piece by piece, register each part with defineField, defineId, defineRelation, defineIndex, defineTrigger, defineFilter and defineHook, then call defineEntity last to validate and finish it:
import { defineEntity, defineField, defineHook, defineId, defineRelation, type HookContext,} from 'uql-orm';import { v7 as uuidv7 } from 'uuid';
class Article { id?: string; title?: string; authorId?: string; author?: User; createdAt?: Date;
stamp(_ctx: HookContext): void { this.createdAt = new Date(); }}
defineId(Article, 'id', { type: 'uuid', onInsert: uuidv7 });defineField(Article, 'title', { type: String, nullable: false });defineField(Article, 'createdAt', { type: Date });defineField(Article, 'authorId', { references: () => User });defineRelation(Article, 'author', { cardinality: 'm1', entity: () => User, references: (article) => article.authorId,});defineHook(Article, 'stamp', 'beforeInsert');defineEntity(Article, { name: 'articles' });A schema defined at runtime
Section titled “A schema defined at runtime”When the shape is data, such as a CMS content type an admin creates, the same calls register it and sync({ entity }) gives it a table. See Runtime schemas.