Skip to content

Imperative (Decorator-free) Definition

defineEntity registers the exact same metadata as the decorators, without requiring experimentalDecorators or emitDecoratorMetadata in tsconfig.json. Use it when decorators are unavailable (some edge runtimes and build pipelines), when generating entities from external metadata or JSON at runtime, or when you prefer domain classes free of annotations.

The configuration object mirrors the decorator API one to one:

import { v7 as uuidv7 } from 'uuid';
import { defineEntity } from 'uql-orm';
export class User {
id?: string;
name?: string;
email?: string;
}
export class Post {
id?: number;
title?: string;
authorId?: string;
author?: User;
publishedAt?: Date;
}
defineEntity(User, {
fields: {
id: { type: 'uuid', isId: true, onInsert: () => uuidv7() },
name: { type: String, index: true },
email: { type: String, unique: true, comment: 'User login email' },
},
});
defineEntity(Post, {
fields: {
id: { type: Number, isId: true },
title: { type: String, nullable: false },
authorId: { references: () => User },
publishedAt: { type: Date, nullable: true },
},
relations: {
author: { cardinality: 'm1', entity: () => User },
},
indexes: [{ columns: ['title', 'authorId'], unique: true }],
filters: {
published: { condition: { publishedAt: { $ne: null } }, default: false },
},
});

Every entry maps directly to a decorator:

Key Decorator equivalent Notes
fields @Field / @Id Same field options; mark the primary key with isId: true instead of @Id.
relations @OneToOne@ManyToMany Same relation options, plus cardinality: '11', '1m', 'm1', or 'mm'.
indexes @Index { columns, name?, unique?, type?, where? }, see Indexes.
hooks @BeforeInsert(), … Maps each lifecycle event to method names, e.g. { beforeInsert: ['stamp'] }.
filters @Filter Same filter options: { condition, default?, security?, onMissing? }.

For dynamic schemas, register piece by piece with defineField, defineId, defineRelation, defineIndex, defineFilter, and defineHook, then call defineEntity last; it validates the metadata (fields present, exactly one primary key) and finalizes the entity:

import {
defineEntity,
defineField,
defineFilter,
defineHook,
defineId,
defineIndex,
defineRelation,
type HookContext,
} from 'uql-orm';
class Article {
createdAt?: Date;
publishedAt?: Date;
stamp(_ctx: HookContext): void {
this.createdAt = new Date();
}
}
defineId(Article, 'id', { type: 'uuid' });
defineField(Article, 'title', { type: String, nullable: false });
defineField(Article, 'createdAt', { type: Date });
defineField(Article, 'publishedAt', { type: Date, nullable: true });
defineRelation(Article, 'author', { cardinality: 'm1', entity: () => User });
defineIndex(Article, { columns: ['title'], unique: true });
defineFilter(Article, 'published', { condition: { publishedAt: { $ne: null } }, default: false });
defineHook(Article, 'stamp', 'beforeInsert');
defineEntity(Article, { name: 'articles' });