Skip to content
NewThe SQL tag: a function called without parentheses4 min read

Enum + Checks

Constraints limit which values a column or row may hold. You declare them on the entity, and uql creates them with the table.

enum lists the values a column accepts. The database enforces them with a CHECK (col IN (...)), and the property type must not allow anything outside the list:

@Entity()
export class Invoice {
@Id({ type: Number })
id?: number;
@Field({ type: String, enum: ['draft', 'paid', 'void'] as const })
status?: 'draft' | 'paid' | 'void' | null;
}

The list needs as const. Without it, the property is narrowed to __enumNeedsAsConst, so that name shows up in the error. Values are strings or numbers.

uql uses a check instead of PostgreSQL’s CREATE TYPE ... AS ENUM, so it works on every engine and changing the list is an ordinary migration.

A string enum works too, without as const. Type the property as the enum itself, since a TypeScript enum does not accept the matching literal union:

enum Status {
Draft = 'draft',
Paid = 'paid',
}
@Field({ type: String, enum: Object.values(Status) })
status?: Status | null;

Numeric enums do not work: they accept any number, and Object.values also returns their member names. Write the values as literals instead, enum: [0, 1] as const with a 0 | 1 property.

@Entity({ checks }) adds conditions over the whole row. Write each as a $where predicate, or as a callback returning raw for what a predicate cannot say:

@Entity({
checks: [
{ name: 'wallet_non_negative_ck', where: { creditsBalance: { $gte: 0 } } },
{ where: (wallet) => raw`${wallet.spent} <= ${wallet.creditsBalance}` },
],
})
export class Wallet {
@Id({ type: Number })
id?: number;
@Field({ type: Number })
creditsBalance?: number | null;
@Field({ type: Number })
spent?: number | null;
}

Columns are quoted for the engine and follow a rename. Values are written as literals, since DDL cannot bind them: { stock: { $gt: 0 } } becomes CHECK ("stock" > 0).

A check is installed as _uql_<table>__<name>_<hash>, where <name> is yours (ck if unnamed, the column’s for an enum) and the hash is of its SQL. Databases reprint a check in their own spelling, so generate:entities, sync and drift:check compare these names, not the SQL. Editing a check or an enum’s values changes the hash, so the old check is dropped and the new one added. SQLite cannot alter a constraint, so it rebuilds the table.

A check without the _uql_ prefix is never touched. uql warns about each such check on a table whose entity declares checks, so you can drop one by hand if a declared check replaces it.

MongoDB enforces checks and enums as one collection validator: each check’s where, then each enum as { field: { $in: [...values, null] } }. Unlike a SQL CHECK, a check there can fail a document that lacks the field, and a raw check is refused. See MongoDB.