Inheritance
An entity inherits what the class it extends declares, abstract or concrete, so shared columns are declared once:
import { Entity, Id, Field } from 'uql-orm';import { v7 as uuidv7 } from 'uuid';
export abstract class BaseEntity { @Id({ type: String, onInsert: uuidv7 }) id?: string; @Field({ type: 'timestamptz', onInsert: () => new Date() }) createdAt?: Date | null; @Field({ type: 'timestamptz', onUpdate: () => new Date() }) updatedAt?: Date | null;}
@Entity({ name: 'company' })export class Company extends BaseEntity { @Field({ type: String, length: 150 }) name?: string | null;}A child inherits the base’s fields, relations, hooks, filters, indexes, checks and triggers. They are typed and queried like its own, and a change to the base reaches every child. The base’s entries come first, and a child entry with the same name replaces the base’s. The base needs no @Entity() unless it is a table too. A child may add nothing of its own, which gives a second table of the same shape:
@Index((entity) => [entity.tenantId])@Trigger({ on: 'afterInsert', name: 'audit', run: logInsert })abstract class Tenanted { @Field({ type: String }) tenantId?: string | null;}
@Entity()export class Invoice extends Tenanted {} // gets its own tenant index and audit triggerEach table derives its own index and trigger names. An index you name yourself keeps that name in every child, and PostgreSQL and SQLite need index names unique across tables, so leave a shared index unnamed.
Replacing an inherited key
Section titled “Replacing an inherited key”A child that declares its own @Id replaces the parent’s key entirely (every column of it, if it was composite) and keeps every other inherited field. Name the new key with the idKey brand, or TypeScript still reads the inherited id as the key:
import { Entity, Id, Field, idKey } from 'uql-orm';import { v7 as uuidv7 } from 'uuid';
@Entity()export class TaxCategory extends BaseEntity { [idKey]?: 'pk';
@Id({ type: String, onInsert: uuidv7 }) pk?: string; @Field({ type: String }) name?: string | null;}Naming a base you cannot extend
Section titled “Naming a base you cannot extend”A class created at runtime, or one whose base is chosen from data, has no extends clause. Pass the base to defineEntity as extends instead:
import { defineEntity, defineField } from 'uql-orm';
class Audited { createdBy?: string;}defineField(Audited, 'createdBy', { type: String });
// a class built at runtime, with no extends clauseconst Faq = { faq: class { id!: number; question?: string | null; createdBy?: string; },}['faq'];
defineEntity(Faq, { extends: Audited, fields: { id: { type: 'bigint', isId: true }, question: { type: 'text' } },});It merges like a class extends: the base’s own ancestors come too, the child’s members win, and a child’s own key replaces the base’s. When a class both extends a base in code and names one in extends, the nearer one wins.
The base’s properties are type-checked against the entity’s, so a base declaring createdBy?: number here is a compile error. A class that declares no properties, such as a runtime class with an index signature, accepts any base.