> Every UQL docs page, as Markdown: https://uql-orm.dev/llms.txt
> The same docs over MCP: https://uql-orm.dev/mcp
> Before writing UQL code, read the skill: https://uql-orm.dev/.well-known/agent-skills/uql-orm/SKILL.md

# Enum + Checks

> Enum fields and table-level CHECK constraints, so the database enforces what the entity declares.

Source: https://uql-orm.dev/entities/enum-checks

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

## Enum fields

`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:

```ts
@Entity()
export class Invoice {
  @Id({ type: Number })
  id?: number;

  @Field({ type: String, enum: ['draft', 'paid', 'void'] as const })
  status?: 'draft' | 'paid' | 'void' | null;
}
```

Values are strings or numbers, and the list needs `as const`: without it the property is narrowed to `__enumNeedsAsConst`, the name you will see in the error.

A check rather than PostgreSQL’s `CREATE TYPE ... AS ENUM` works on every engine, and changing the list is an ordinary migration.

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

```ts
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, `enum: [0, 1] as const` with a `0 | 1` property.

## Check constraints

`@Entity({ checks })` adds conditions over the whole row. Write each as a `$where` predicate, or as a callback returning [`sql`](https://uql-orm.dev/querying/raw-sql.md) for what a predicate cannot say:

```ts
@Entity({
  checks: [
    { name: 'wallet_non_negative_ck', where: { creditsBalance: { $gte: 0 } } },
    { where: (wallet) => sql`${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)`.

## How a change is detected

A check is installed as `_uql_<table>__<name>_<hash>`: `<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 there the table is rebuilt.

> **Caution**
>
> `sync` in its default safe mode never drops a check. A changed check is added beside the old one, which keeps enforcing until a migration or `sync` with `safe: false` drops it.

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

## MongoDB

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 `sql` check is refused. See [MongoDB](https://uql-orm.dev/mongodb.md#checks-and-enums).
