Enum + Checks
A column’s type says what shape a value has. A constraint says which values are allowed. Both are declared on the entity and emitted with the table.
Enum fields
Section titled “Enum fields”enum states the values a column accepts. The database enforces them with a CHECK (col IN (...)) and TypeScript checks the property against the same set, so a property admitting a value the column would reject is a compile error:
import { Entity, Field, Id } from 'uql-orm';
@Entity()export class Invoice { @Id({ type: Number }) id?: number;
@Field({ type: String, enum: ['draft', 'paid', 'void'] as const }) status?: 'draft' | 'paid' | 'void';}as const is what makes any of it check. Without it the values widen to string[] and the property is narrowed to __enumNeedsAsConst - a type nothing can hold, whose name is the error you get. It fails loudly rather than leaving the check silently off.
Strings and numbers only, each escaped by the dialect’s own literal rules, so a number stays bare where a string is quoted.
A check rather than PostgreSQL’s CREATE TYPE ... AS ENUM: one declaration works on every dialect, and changing the set stays an ordinary column change instead of an irreversible ALTER TYPE ... ADD VALUE or a rewrite of every dependent column.
TypeScript enums
Section titled “TypeScript enums”A string enum works in place of the literal array, and needs no as const - its members already infer narrower than string:
enum Status { Draft = 'draft', Paid = 'paid', Void = 'void',}
@Entity()export class Invoice { @Id({ type: Number }) id?: number;
@Field({ type: String, enum: Object.values(Status) }) status?: Status;}The check is the same CHECK ("status" IN ('draft', 'paid', 'void')) - the values, never the member names. The property has to be typed Status though: a TS enum is nominal, so the equivalent literal union is not assignable to it.
Numeric enums do not work. Their members are assignable from any number, so they narrow nothing and the guard fires, and Object.values on one yields the reverse-mapped names alongside the numbers. Write those values as literals instead - enum: [0, 1] as const with a 0 | 1 property.
The values are fixed when the table is created. A check is never diffed, so adding one later emits no statement and the column goes on rejecting it, silently. Widen it by hand - the constraint carries the name the engine gave it:
ALTER TABLE "invoice" DROP CONSTRAINT "invoice_status_check";ALTER TABLE "invoice" ADD CONSTRAINT "invoice_status_check" CHECK ("status" IN ('draft', 'paid', 'void'));Check constraints
Section titled “Check constraints”A condition over the whole row, declared on the entity:
The expression is raw SQL, so leave names unquoted: double quotes are identifiers on PostgreSQL and SQLite but string literals on MySQL and MariaDB, where they compare two constants and quietly reject every row.
import { Entity, Field, Id, raw } from 'uql-orm';
@Entity({ checks: [ { name: 'wallet_non_negative_ck', expression: raw`creditsBalance >= 0` }, { expression: raw`spent <= creditsBalance` }, ],})export class Wallet { @Id({ type: Number }) id?: number;
@Field({ type: Number }) creditsBalance?: number;
@Field({ type: Number }) spent?: number;}An unnamed check is named <table>__<position>_ck by declaration order, the same rule that names an unnamed index or foreign key.
The expression is raw with no interpolation, the rule every DDL expression follows, partial-index predicates included. CREATE TABLE has no placeholder to bind a value into, so an expression carrying one is refused rather than emitted:
@Entity({ checks: [{ expression: raw`"stock" > ${0}` }] }) // throwsConstraining a single column to a set of values is enum instead, which writes the check for you.