@vibeorm/dsl 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,14 +12,38 @@
12
12
  * one instance serves every model. The type parameters on
13
13
  * {@link PredicateBuilder} are what bind it to a particular model's columns.
14
14
  */
15
- import type { ExclusionIR, CheckIR, EnumIR, FieldIR, PredicateIR } from "@vibeorm/schema";
15
+ import type { ExclusionIR, CheckIR, EnumIR, FieldIR, PredicateExpression, PredicateIR } from "@vibeorm/schema";
16
16
  import type { SqlDialect } from "@vibeorm/sql";
17
- import type { ModelDef, ChecksCallback, ModelCheckConfig, PredicateBuilder, SchemaDefinition } from "./types.ts";
17
+ import type { ModelDef, ChecksBuilder, ChecksCallback, ModelCheckConfig, PredicateBuilder, SchemaDefinition } from "./types.ts";
18
+ /** Internal shared scalar lowering for index keys; preserves B's exact literal carriers. */
19
+ export declare function toPredicateExpression(params: {
20
+ readonly value: unknown;
21
+ readonly at: string;
22
+ }): PredicateExpression;
18
23
  /**
19
24
  * One predicate builder instance. Stateless and structural — the type
20
25
  * parameters, not the value, bind it to a model.
21
26
  */
22
27
  export declare function createPredicateBuilder<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition>(): PredicateBuilder<TColumns, TSchema>;
28
+ /** What the check rules need to know about the model they belong to. */
29
+ export type CheckModelContext = {
30
+ /** Logical model name, for messages. */
31
+ readonly model: string;
32
+ /** PHYSICAL table name: generated CHECK names start with it. */
33
+ readonly table: string;
34
+ /** The model's built fields: logical name, `@map`ped name, scalar type, optional, list. */
35
+ readonly fields: readonly FieldIR[];
36
+ /** Schema enums, for a presence table's members. */
37
+ readonly enums: readonly EnumIR[];
38
+ };
39
+ /**
40
+ * The `c` a model's `checks` callback receives: the predicate builder plus the
41
+ * named check rules, each validated against `context` and returned as a named
42
+ * CHECK.
43
+ */
44
+ export declare function createChecksBuilder<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition>(params: {
45
+ context: CheckModelContext;
46
+ }): ChecksBuilder<TColumns, TSchema>;
23
47
  /** A model's CHECKs, still un-rendered: raw text stays raw, typed stays typed. */
24
48
  export type EvaluatedCheck = {
25
49
  readonly kind: "raw";
@@ -29,6 +53,10 @@ export type EvaluatedCheck = {
29
53
  readonly kind: "typed";
30
54
  readonly name: string;
31
55
  readonly predicate: PredicateIR;
56
+ /** The check rule that produced it, when one did. */
57
+ readonly origin?: string;
58
+ /** True when the name was generated by the rule. */
59
+ readonly minted?: boolean;
32
60
  };
33
61
  /**
34
62
  * Run a model's `checks` declaration, whichever form it took.
@@ -36,10 +64,14 @@ export type EvaluatedCheck = {
36
64
  * The raw array form and the typed callback form produce the same shape of
37
65
  * answer, so every consumer downstream handles ONE list — and raw text is
38
66
  * carried through untouched rather than parsed into a predicate.
67
+ *
68
+ * The check rules (`c.order`, `c.allOrNone`, `c.presence`, …) need the model's
69
+ * fields: pass `context`. Without it they refuse with `check-rule-context`.
39
70
  */
40
71
  export declare function evaluateChecks(params: {
41
72
  checks: readonly ModelCheckConfig[] | ChecksCallback | undefined;
42
73
  model: string;
74
+ context?: CheckModelContext;
43
75
  }): readonly EvaluatedCheck[];
44
76
  /** What a predicate needs to know about the model it belongs to. */
45
77
  export type PredicateModel = {
@@ -62,11 +94,36 @@ export declare function compileExclusions(params: {
62
94
  model: PredicateModel;
63
95
  dialect: SqlDialect;
64
96
  }): readonly ExclusionIR[];
97
+ /**
98
+ * A `checks` declaration as IR, for a caller that holds the built fields but
99
+ * not the model definition. Check rules generate their names from `table`;
100
+ * pass it when the model is `@@map`ped, because `PredicateModel` does not
101
+ * carry the table name. Without it the model name is the table name, as for
102
+ * an unmapped model. A generated name that collides or is too long for the
103
+ * dialect is refused, exactly as `compileModelChecks` refuses it.
104
+ */
65
105
  export declare function compileChecks(params: {
66
106
  checks: readonly ModelCheckConfig[] | ChecksCallback | undefined;
67
107
  model: PredicateModel;
68
108
  dialect: SqlDialect;
69
109
  enums: readonly EnumIR[];
110
+ /** PHYSICAL table name generated CHECK names start with; defaults to `model.name`. */
111
+ table?: string;
112
+ }): readonly CheckIR[];
113
+ /**
114
+ * A model's CHECKs as IR: the `checks` declaration (hand-written and check
115
+ * rules) first, in declaration order, then the column rules (`.nonNegative()`,
116
+ * …) in column order. Generated names use the PHYSICAL table and column names.
117
+ *
118
+ * The one compiler both `toIR` and the `.prisma` generator call, so a printed
119
+ * schema and a compiled one hold the same CHECKs.
120
+ */
121
+ export declare function compileModelChecks(params: {
122
+ model: ModelDef;
123
+ /** The model's built fields, from the same `buildField` the IR uses. */
124
+ predicateModel: PredicateModel;
125
+ dialect: SqlDialect;
126
+ enums: readonly EnumIR[];
70
127
  }): readonly CheckIR[];
71
128
  /**
72
129
  * An index's partial predicate as IR: the raw string verbatim, or a typed
@@ -20,7 +20,7 @@
20
20
  * parent) use the `@relation` alias when there is one, else the source model
21
21
  * name, suffixed on collision.
22
22
  */
23
- import type { CascadeAction, PredicateIR, QualifiedSqlName, RelationType } from "@vibeorm/schema";
23
+ import type { CascadeAction, IndexKeyTermIR, PredicateIR, QualifiedSqlName, RelationType } from "@vibeorm/schema";
24
24
  import type { IndexType, ModelDef, RelationsMap, SchemaDef } from "./types.ts";
25
25
  /**
26
26
  * Structural role of a planned relation field:
@@ -57,6 +57,8 @@ export type PlannedIndex = {
57
57
  readonly map?: string;
58
58
  readonly isUnique: boolean;
59
59
  readonly columns: readonly string[];
60
+ /** Whole ordered key, lowered once by the index builder. */
61
+ readonly keys?: readonly IndexKeyTermIR[];
60
62
  readonly indexType?: IndexType;
61
63
  /** Qualified operator classes keyed by logical index column. */
62
64
  readonly ops?: Readonly<Partial<Record<string, QualifiedSqlName>>>;
@@ -0,0 +1,63 @@
1
+ import type { Dialect, ModelIR, RowRuleIR, SchemaIR } from "@vibeorm/schema";
2
+ import type { ModelDef } from "./types.ts";
3
+ /** Model-bound authoring declarations; only frozen uses the short creation gate spelling. */
4
+ export type RowRuleDeclaration<TColumn extends string = string> = {
5
+ readonly kind: "appendOnly";
6
+ readonly name?: string;
7
+ } | {
8
+ readonly kind: "fixed";
9
+ readonly name?: string;
10
+ readonly columns: readonly TColumn[];
11
+ readonly when: {
12
+ readonly fromCreation: true;
13
+ readonly onceSet?: never;
14
+ } | {
15
+ readonly onceSet: TColumn;
16
+ readonly fromCreation?: never;
17
+ };
18
+ } | {
19
+ readonly kind: "rowFixed";
20
+ readonly name?: string;
21
+ readonly except: readonly TColumn[];
22
+ } | {
23
+ readonly kind: "frozen";
24
+ readonly name?: string;
25
+ readonly from: "create" | TColumn;
26
+ readonly mutable: readonly TColumn[];
27
+ };
28
+ /** Immutable row-rule data builder, without client-side row-state inference. */
29
+ export type RowRuleBuilder<TColumn extends string = string> = {
30
+ appendOnly(options?: {
31
+ readonly name?: string;
32
+ }): RowRuleDeclaration<TColumn>;
33
+ fixed(options: {
34
+ readonly columns: readonly TColumn[];
35
+ readonly when: {
36
+ readonly fromCreation: true;
37
+ readonly onceSet?: never;
38
+ } | {
39
+ readonly onceSet: TColumn;
40
+ readonly fromCreation?: never;
41
+ };
42
+ readonly name?: string;
43
+ }): RowRuleDeclaration<TColumn>;
44
+ rowFixed(options: {
45
+ readonly except: readonly TColumn[];
46
+ readonly name?: string;
47
+ }): RowRuleDeclaration<TColumn>;
48
+ frozen(options: {
49
+ readonly from: "create" | TColumn;
50
+ readonly mutable: readonly TColumn[];
51
+ readonly name?: string;
52
+ }): RowRuleDeclaration<TColumn>;
53
+ };
54
+ /** Build model-bound row-rule declarations and copy every retained list and gate. */
55
+ export declare function createRowRuleBuilder<TColumn extends string = string>(): RowRuleBuilder<TColumn>;
56
+ /** Evaluate the callback against finalized fields and lower short frozen gates without guessing. */
57
+ export declare function compileRowRules(params: {
58
+ model: ModelDef;
59
+ irModel: ModelIR;
60
+ dialect: Dialect;
61
+ schema?: SchemaIR;
62
+ }): readonly RowRuleIR[];
63
+ //# sourceMappingURL=row-rules.d.ts.map
package/dist/to-ir.d.ts CHANGED
@@ -27,8 +27,11 @@
27
27
  * - `.collation()` / `{ collation }` → `FieldIR.collation`.
28
28
  * - relation types are derived structurally by `resolveRelationType`.
29
29
  */
30
- import type { DefaultValueIR, ExtensionRegistry, FieldIR, Provider, SchemaIR } from "@vibeorm/schema";
31
- import type { ColumnDef, DatetimeColumnConfig, SchemaDef } from "./types.ts";
30
+ import type { DefaultValueIR, EnumIR, ExtensionRegistry, FieldIR, IndexIR, Provider, SchemaIR } from "@vibeorm/schema";
31
+ import type { SqlDialect } from "@vibeorm/sql";
32
+ import type { PredicateModel } from "./predicate.ts";
33
+ import type { PlannedIndex } from "./relation-plan.ts";
34
+ import type { ColumnDef, DatetimeColumnConfig, IdColumnConfig, SchemaDef, StringColumnConfig } from "./types.ts";
32
35
  /** Compile a column default once for both DSL output paths. */
33
36
  export declare function fieldDefault(params: {
34
37
  column: ColumnDef;
@@ -43,6 +46,20 @@ export declare function datetimeNativeType(params: {
43
46
  config: DatetimeColumnConfig;
44
47
  column: string | undefined;
45
48
  }): string | undefined;
49
+ /**
50
+ * `Uuid`, `VarChar(n)` or `Text` for a String column, or `undefined`. Shared
51
+ * with the `.prisma` generator so both spell it alike. A column has one type,
52
+ * so `uuid` refuses to combine with `maxLength` or `text`.
53
+ */
54
+ export declare function stringNativeType(params: {
55
+ config: StringColumnConfig;
56
+ column: string | undefined;
57
+ }): string | undefined;
58
+ /** `Uuid` for a `uuid()` String id, or `undefined`; an Int (autoincrement) id refuses it. */
59
+ export declare function idNativeType(params: {
60
+ config: IdColumnConfig;
61
+ column: string | undefined;
62
+ }): string | undefined;
46
63
  /**
47
64
  * One column as `FieldIR`.
48
65
  *
@@ -56,6 +73,13 @@ export declare function buildField(params: {
56
73
  /** Native type resolved by an extension field-type lowering, when present. */
57
74
  extensionNativeType?: string;
58
75
  }): FieldIR;
76
+ /** Shared planned-index lowering for IR and source spelling; not a package-root authoring API. */
77
+ export declare function buildIndex(params: {
78
+ index: PlannedIndex;
79
+ model: PredicateModel;
80
+ dialect: SqlDialect;
81
+ enums: readonly EnumIR[];
82
+ }): IndexIR;
59
83
  /**
60
84
  * Compiles a composed DSL schema to the canonical `SchemaIR`, then validates it.
61
85
  *
package/dist/types.d.ts CHANGED
@@ -5,14 +5,16 @@
5
5
  * through `defineRelations` (Drizzle-style), which keeps column references
6
6
  * type-safe without inference-heavy model types.
7
7
  */
8
- import type { ExclusionSpec, ExclusionEndpoint, CascadeAction, EnumStorage, JsonSchemaRefIR, ExtensionAttributeValue, PredicateExpression, PredicateIR, Provider, QualifiedSqlName, RlsContextIR, RlsContextRefIR, RlsPolicyIR } from "@vibeorm/schema";
8
+ import type { ExclusionSpec, ExclusionEndpoint, CascadeAction, EnumStorage, GeneratedColumnOptions, IndexKeyTermIR, JsonSchemaRefIR, ValidatorRefIR, ExtensionAttributeValue, PredicateExpression, PredicateIR, PredicateNumericType, Provider, QualifiedSqlName, RlsContextIR, RlsContextRefIR, RlsPolicyIR } from "@vibeorm/schema";
9
9
  import type { ChainableEnumColumn } from "./column.ts";
10
+ import type { RowRuleBuilder, RowRuleDeclaration } from "./row-rules.ts";
11
+ export type { RowRuleBuilder, RowRuleDeclaration } from "./row-rules.ts";
10
12
  /** Referential actions, re-exported from the IR so both sources agree. */
11
13
  export type { CascadeAction };
12
14
  /** Enum storage strategies (`"database"` | `"string"`), re-exported from the IR. */
13
15
  export type { EnumStorage };
14
16
  /** Column kinds the DSL can declare. */
15
- export type ColumnType = "string" | "int" | "bigint" | "float" | "decimal" | "boolean" | "datetime" | "json" | "id" | "bytes" | "enum" | "unsupported";
17
+ export type ColumnType = "string" | "localDate" | "int" | "bigint" | "float" | "decimal" | "boolean" | "datetime" | "json" | "id" | "bytes" | "enum" | "unsupported";
16
18
  /** Primary-key generator strategies for `dsl.id()`. */
17
19
  export type IdGeneratorType = "ulid" | "cuid" | "uuid" | "nanoid" | "autoincrement" | "none";
18
20
  /** PostgreSQL index types Prisma can express via `@@index(..., type: X)`. */
@@ -25,18 +27,64 @@ export type BaseColumnConfig = {
25
27
  array?: boolean;
26
28
  /** `@map("column_name")` — database column name when it differs. */
27
29
  map?: string;
30
+ /** Stored database expression; the declaration is copied and frozen. */
31
+ generated?: GeneratedColumnOptions;
28
32
  /** `@default(dbgenerated("..."))` — database-native default expression. */
29
33
  dbgenerated?: string;
30
34
  /** Emitted as a `///` doc comment above the column (Zod generators, docs). */
31
35
  comment?: string;
36
+ /**
37
+ * One-column CHECK rules (`.nonNegative()`, `.range()`, `.oneOf()`, …),
38
+ * each compiled to a named CHECK. Stored copy-on-write and frozen, so a
39
+ * template column never shares rules with another model.
40
+ */
41
+ checks?: readonly ColumnCheckRule[];
42
+ /** Consumer-owned write-only scalar validator export; types remain unchanged. */
43
+ validator?: ValidatorRefIR;
32
44
  [key: string]: unknown;
33
45
  };
46
+ /** Options every check rule takes. */
47
+ export type CheckRuleOptions = {
48
+ /** Explicit CHECK name, replacing the generated `<Table>_<column>_<rule>_check`. */
49
+ readonly name?: string;
50
+ /** The rule word in the generated name (`range` in `Device_volume_range_check`). */
51
+ readonly rule?: string;
52
+ };
53
+ /** What a one-column rule asserts, as data; the column is the one it is declared on. */
54
+ export type ColumnCheckShape = {
55
+ readonly kind: "compare";
56
+ readonly op: "gt" | "gte" | "lt" | "lte";
57
+ readonly value: string | number;
58
+ } | {
59
+ readonly kind: "range";
60
+ readonly min: string | number;
61
+ readonly max: string | number;
62
+ } | {
63
+ readonly kind: "oneOf";
64
+ readonly values: readonly string[];
65
+ } | {
66
+ readonly kind: "lengthAtMost";
67
+ readonly max: number;
68
+ };
69
+ /** One one-column CHECK rule, as a column method records it. */
70
+ export type ColumnCheckRule = {
71
+ /** The rule word in the generated name. */
72
+ readonly rule: string;
73
+ readonly check: ColumnCheckShape;
74
+ /** Explicit CHECK name, replacing the generated one. */
75
+ readonly name?: string;
76
+ };
34
77
  export type StringColumnConfig = BaseColumnConfig & {
35
78
  default?: string | readonly string[];
36
79
  /** `@db.VarChar(n)` */
37
80
  maxLength?: number;
38
81
  /** `@db.Text` */
39
82
  text?: boolean;
83
+ /**
84
+ * `@db.Uuid` — the engine's native uuid column (PostgreSQL); still a string
85
+ * in JavaScript. SQLite stores it as TEXT; MySQL has none and refuses it.
86
+ */
87
+ uuid?: boolean;
40
88
  /**
41
89
  * `@collation("...")` — explicit column collation. Rendered as `COLLATE` on
42
90
  * mysql, inert on postgres/sqlite (same contract as the IR field). The
@@ -44,6 +92,10 @@ export type StringColumnConfig = BaseColumnConfig & {
44
92
  */
45
93
  collation?: string;
46
94
  };
95
+ /** A calendar day stored as String @db.Date; only canonical literal defaults are admitted. */
96
+ export type LocalDateColumnConfig = BaseColumnConfig & {
97
+ default?: string;
98
+ };
47
99
  export type IntColumnConfig = BaseColumnConfig & {
48
100
  autoincrement?: boolean;
49
101
  default?: number | readonly number[];
@@ -79,6 +131,8 @@ export type JsonColumnConfig = BaseColumnConfig & {
79
131
  };
80
132
  export type IdColumnConfig = BaseColumnConfig & {
81
133
  type?: IdGeneratorType;
134
+ /** `@db.Uuid` on a String id — see {@link StringColumnConfig.uuid}. */
135
+ uuid?: boolean;
82
136
  };
83
137
  export type BytesColumnConfig = BaseColumnConfig & {
84
138
  default?: Uint8Array | string | readonly (Uint8Array | string)[];
@@ -227,6 +281,10 @@ export type NamedCheckDef = {
227
281
  readonly __type: "check";
228
282
  readonly name: string;
229
283
  readonly predicate: PredicateIR;
284
+ /** The check rule that produced it (`c.allOrNone(…)`, a presence table), for error messages. */
285
+ readonly __origin?: string;
286
+ /** True when the name was generated, not passed with `{ name }`. */
287
+ readonly __minted?: boolean;
230
288
  };
231
289
  /**
232
290
  * The `c` handed to a model's `checks` callback, returning typed or raw named CHECKs.
@@ -236,7 +294,91 @@ export type NamedCheckDef = {
236
294
  * c.named("dispatch_clock", c.gt(c.field("deadlineAt"), c.field("startedAt"))),
237
295
  * ]
238
296
  */
239
- export type ChecksCallback<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition> = (c: PredicateBuilder<TColumns, TSchema>) => (NamedCheckDef | ModelCheckConfig)[];
297
+ export type ChecksCallback<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition> = (c: ChecksBuilder<TColumns, TSchema>) => (NamedCheckDef | ModelCheckConfig)[];
298
+ /** Options of `c.order()`. */
299
+ export type OrderCheckOptions<TColumn extends string = string> = CheckRuleOptions & {
300
+ /** `false` allows equal values (`>=`); default `true` (`>`). */
301
+ readonly strict?: boolean;
302
+ /**
303
+ * The column the generated name is built from and the CHECK reads first:
304
+ * the later column by default (`"endAt" > "startAt"`), or the earlier one
305
+ * (`"cancelledAt" < "startAt"`).
306
+ */
307
+ readonly nameColumn?: TColumn;
308
+ };
309
+ /** One state of a presence table: the optional columns that must be set, and those that must be empty. */
310
+ export type PresenceState<TColumns extends string = string> = {
311
+ /** NOT NULL while the enum column holds this member. */
312
+ readonly set?: readonly TColumns[];
313
+ /** NULL while the enum column holds this member. */
314
+ readonly unset?: readonly TColumns[];
315
+ };
316
+ /**
317
+ * A presence table: per enum member, which optional columns are set and which
318
+ * are empty. A column listed in neither is free in that state. Every member
319
+ * must appear exactly once.
320
+ */
321
+ export type PresenceCheckConfig<TColumns extends string = string, TMember extends string = string, TBy extends string = string> = {
322
+ /** A REQUIRED enum column of this model. */
323
+ readonly by: TBy;
324
+ /** The rule word in each generated name; defaults to the `by` column's name. */
325
+ readonly rule?: string;
326
+ /** Explicit CHECK names per column, replacing the generated ones. */
327
+ readonly names?: {
328
+ readonly [C in TColumns]?: string;
329
+ };
330
+ readonly states: {
331
+ readonly [S in TMember]: PresenceState<TColumns>;
332
+ };
333
+ };
334
+ /**
335
+ * The model's enum columns, as names. An unbound builder (`ChecksBuilder` with
336
+ * its default `string` columns) knows no column kinds, so any name is allowed
337
+ * and the schema build checks it.
338
+ */
339
+ type EnumColumnNames<TColumns extends string, TSchema extends SchemaDefinition> = string extends TColumns ? TColumns : {
340
+ [C in TColumns]: C extends keyof TSchema ? (TSchema[C] extends ColumnDef<"enum"> ? C : never) : never;
341
+ }[TColumns];
342
+ /** The members of one enum column, read from its `__enumValues` tuple. */
343
+ type EnumMembersOf<TSchema extends SchemaDefinition, TColumn extends string> = TColumn extends keyof TSchema ? TSchema[TColumn] extends {
344
+ readonly __enumValues?: infer TValues extends readonly string[];
345
+ } ? TValues[number] : string : string;
346
+ /** Columns that can pair with another column through the shared scalar-family table. */
347
+ type PairableCheckColumns<TColumns extends string, TSchema extends SchemaDefinition, TColumn extends TColumns> = ColumnRefKind extends SchemaColumnRefKind<TSchema, TColumn> ? TColumns : {
348
+ [C in TColumns]: SchemaColumnRefKind<TSchema, C> extends PairableRefKind[SchemaColumnRefKind<TSchema, TColumn>] ? C : never;
349
+ }[TColumns];
350
+ /**
351
+ * The `c` handed to a model's `checks` callback: the predicate builder plus
352
+ * the named check rules, each returning ready, named CHECKs.
353
+ *
354
+ * Names are generated as `<Table>_<column>_<rule>_check` from the PHYSICAL
355
+ * table and column names; every rule takes `{ name }` to replace it.
356
+ *
357
+ * @example
358
+ * checks: (c) => [
359
+ * c.order("startAt", "endAt"),
360
+ * c.allOrNone("latitude", "longitude"),
361
+ * ...c.presence({ by: "state", states: { draft: { unset: ["sentAt"] }, sent: { set: ["sentAt"] } } }),
362
+ * ]
363
+ */
364
+ export type ChecksBuilder<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition> = PredicateBuilder<TColumns, TSchema> & {
365
+ /** `later > earlier` (or `>=` with `strict: false`), named after the later column. NULL in either passes. */
366
+ order<TEarlier extends TColumns, TLater extends PairableCheckColumns<TColumns, TSchema, NoInfer<TEarlier>>>(earlier: TEarlier, later: TLater, options?: OrderCheckOptions<NoInfer<TEarlier | TLater>>): NamedCheckDef;
367
+ /** All of the columns set, or none of them. */
368
+ allOrNone<C extends TColumns>(...columns: C[]): NamedCheckDef;
369
+ allOrNone<C extends TColumns>(columns: readonly C[], options: CheckRuleOptions): NamedCheckDef;
370
+ /** Exactly one of the columns set. */
371
+ exactlyOne<C extends TColumns>(...columns: C[]): NamedCheckDef;
372
+ exactlyOne<C extends TColumns>(columns: readonly C[], options: CheckRuleOptions): NamedCheckDef;
373
+ /** At most one of the columns set. */
374
+ atMostOne<C extends TColumns>(...columns: C[]): NamedCheckDef;
375
+ atMostOne<C extends TColumns>(columns: readonly C[], options: CheckRuleOptions): NamedCheckDef;
376
+ /** At least one of the columns set. */
377
+ anySet<C extends TColumns>(...columns: C[]): NamedCheckDef;
378
+ anySet<C extends TColumns>(columns: readonly C[], options: CheckRuleOptions): NamedCheckDef;
379
+ /** One CHECK per column the presence table names; spread the result into the list. */
380
+ presence<TBy extends EnumColumnNames<TColumns, TSchema>>(config: PresenceCheckConfig<TColumns, EnumMembersOf<TSchema, TBy>, TBy>): NamedCheckDef[];
381
+ };
240
382
  /**
241
383
  * Application-level row-scoping policy — the DSL twin of
242
384
  * `@@policy([field], context: "key")`. The runtime fails closed on policied
@@ -256,6 +398,8 @@ export type IndexDef = {
256
398
  readonly __map?: string;
257
399
  readonly __type: "index" | "unique";
258
400
  readonly __columns: readonly string[];
401
+ /** Complete ordered authoring keys when computation or explicit direction is present. */
402
+ readonly __keys?: readonly IndexKeyTermIR[];
259
403
  readonly __indexType?: IndexType;
260
404
  /** Non-unique PostgreSQL operator classes, keyed by logical index field name. */
261
405
  readonly __ops?: Readonly<Partial<Record<string, QualifiedSqlName>>>;
@@ -313,14 +457,44 @@ export type UniqueIndexOptions = {
313
457
  export type PartialIndexDef<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition> = IndexDef & {
314
458
  where(predicate: (c: PredicateBuilder<TColumns, TSchema>) => PredicateIR): IndexDef;
315
459
  };
316
- /** Index builder; column names are constrained to the model's own columns. */
317
- export type IndexBuilder<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition> = {
318
- on<T extends TColumns>(...columns: T[]): PartialIndexDef<TColumns, TSchema>;
319
- on<T extends TColumns>(columns: T[], options: IndexOptions<NoInfer<T>>): PartialIndexDef<TColumns, TSchema>;
460
+ /** Dialect-authored trusted scalar SQL; always requires a physical index map. */
461
+ export type RawIndexExpression = {
462
+ readonly __type: "rawIndexExpression";
463
+ readonly text: string;
464
+ };
465
+ /** Scalar or named key before direction is applied. */
466
+ export type UnsortedIndexKey<TColumns extends string = string> = TColumns | PredicateExpr | RawIndexExpression;
467
+ /** Direction belongs to the key, not its scalar expression data. */
468
+ export type SortedIndexKey<TColumns extends string = string> = {
469
+ readonly __type: "sortedIndexKey";
470
+ readonly key: UnsortedIndexKey<TColumns>;
471
+ readonly sort: "asc" | "desc";
472
+ };
473
+ /** Ordered mixed key accepted by ordinary and unique BTree builders. */
474
+ export type IndexKey<TColumns extends string = string> = UnsortedIndexKey<TColumns> | SortedIndexKey<TColumns>;
475
+ type StringIndexColumns<TColumns extends string, TSchema extends SchemaDefinition> = {
476
+ [C in TColumns]: "String" extends SchemaColumnRefKind<TSchema, C> ? C : "Id" extends SchemaColumnRefKind<TSchema, C> ? C : never;
477
+ }[TColumns];
478
+ /** Index builder; scalar helpers are separate from the terminal where builder. */
479
+ export type IndexBuilder<TColumns extends string = string, TSchema extends SchemaDefinition = SchemaDefinition> = Pick<PredicateBuilder<TColumns, TSchema>, "field" | "add" | "sub" | "mul"> & {
480
+ lower<C extends StringIndexColumns<TColumns, TSchema>>(column: C): PredicateExpr<"String">;
481
+ lower(expression: PredicateExpr<"String" | "Id">): PredicateExpr<"String">;
482
+ upper<C extends StringIndexColumns<TColumns, TSchema>>(column: C): PredicateExpr<"String">;
483
+ upper(expression: PredicateExpr<"String" | "Id">): PredicateExpr<"String">;
484
+ asc(key: UnsortedIndexKey<TColumns>): SortedIndexKey<TColumns>;
485
+ desc(key: UnsortedIndexKey<TColumns>): SortedIndexKey<TColumns>;
486
+ sql(params: {
487
+ readonly text: string;
488
+ }): RawIndexExpression;
489
+ on(...keys: IndexKey<TColumns>[]): PartialIndexDef<TColumns, TSchema>;
490
+ on<C extends TColumns>(keys: C[], options: IndexOptions<NoInfer<C>>): PartialIndexDef<TColumns, TSchema>;
491
+ on(keys: IndexKey<TColumns>[], options: Omit<IndexOptions, "ops"> & {
492
+ readonly ops?: never;
493
+ }): PartialIndexDef<TColumns, TSchema>;
320
494
  /** Alias of `on(...)` — reads better next to `unique(...)`. */
321
- index<T extends TColumns>(...columns: T[]): PartialIndexDef<TColumns, TSchema>;
322
- unique<T extends TColumns>(...columns: T[]): PartialIndexDef<TColumns, TSchema>;
323
- unique<T extends TColumns>(columns: T[], options: UniqueIndexOptions): PartialIndexDef<TColumns, TSchema>;
495
+ index(...keys: IndexKey<TColumns>[]): PartialIndexDef<TColumns, TSchema>;
496
+ unique(...keys: IndexKey<TColumns>[]): PartialIndexDef<TColumns, TSchema>;
497
+ unique(keys: IndexKey<TColumns>[], options: UniqueIndexOptions): PartialIndexDef<TColumns, TSchema>;
324
498
  gin<T extends TColumns>(...columns: T[]): PartialIndexDef<TColumns, TSchema>;
325
499
  gist<T extends TColumns>(...columns: T[]): PartialIndexDef<TColumns, TSchema>;
326
500
  hash<T extends TColumns>(...columns: T[]): PartialIndexDef<TColumns, TSchema>;
@@ -379,6 +553,17 @@ export type PredicateFieldExpr<TColumn extends string = string, TKind extends Co
379
553
  export type PredicateExpr<TKind extends ColumnRefKind = ColumnRefKind> = PredicateExpression & {
380
554
  readonly __kind: TKind;
381
555
  };
556
+ /** Numeric scalar families accepted by arithmetic, without implicit promotion. */
557
+ export type PredicateNumericKind = PredicateNumericType;
558
+ /** Exact literal carriers for each numeric scalar family. */
559
+ export type PredicateNumericLiteral = {
560
+ readonly Int: number;
561
+ readonly BigInt: bigint | number;
562
+ readonly Float: number;
563
+ readonly Decimal: string;
564
+ };
565
+ /** An arithmetic operand in the same numeric family as its left expression. */
566
+ export type PredicateNumericOperand<TKind extends PredicateNumericKind> = PredicateExpr<TKind> | PredicateNumericLiteral[TKind];
382
567
  type JsonPredicateColumns<TColumns extends string, TSchema extends SchemaDefinition> = {
383
568
  [C in TColumns]: "Json" extends SchemaColumnRefKind<TSchema, C> ? C : never;
384
569
  }[TColumns];
@@ -404,6 +589,25 @@ export type PredicateBuilder<TColumns extends string = string, TSchema extends S
404
589
  gte<TKind extends ColumnRefKind>(left: PredicateExpr<TKind>, right: ComparisonOperand<NoInfer<TKind>>): PredicateIR;
405
590
  lt<TKind extends ColumnRefKind>(left: PredicateExpr<TKind>, right: ComparisonOperand<NoInfer<TKind>>): PredicateIR;
406
591
  lte<TKind extends ColumnRefKind>(left: PredicateExpr<TKind>, right: ComparisonOperand<NoInfer<TKind>>): PredicateIR;
592
+ /** Add same-family numeric operands. SQL NULL behavior is unchanged. */
593
+ add<TKind extends PredicateNumericKind>(params: {
594
+ left: PredicateExpr<TKind>;
595
+ right: PredicateNumericOperand<NoInfer<TKind>>;
596
+ }): PredicateExpr<TKind>;
597
+ /** Subtract same-family numeric operands, preserving the authored grouping. */
598
+ sub<TKind extends PredicateNumericKind>(params: {
599
+ left: PredicateExpr<TKind>;
600
+ right: PredicateNumericOperand<NoInfer<TKind>>;
601
+ }): PredicateExpr<TKind>;
602
+ /** Multiply same-family numeric operands. SQLite refuses Decimal arithmetic. */
603
+ mul<TKind extends PredicateNumericKind>(params: {
604
+ left: PredicateExpr<TKind>;
605
+ right: PredicateNumericOperand<NoInfer<TKind>>;
606
+ }): PredicateExpr<TKind>;
607
+ /** Fold text to lowercase using the database's locale/collation behavior. */
608
+ lower(expression: PredicateExpr<"String" | "Id">): PredicateExpr<"String">;
609
+ /** Fold text to uppercase. Local dates and non-text Id storage refuse at build. */
610
+ upper(expression: PredicateExpr<"String" | "Id">): PredicateExpr<"String">;
407
611
  /** PostgreSQL/PGlite CHECK only. Extract text without asserting the JSON leaf's type. */
408
612
  jsonText(params: {
409
613
  field: JsonPredicateColumns<TColumns, TSchema>;
@@ -445,6 +649,8 @@ export type ModelDef<TSchema extends SchemaDefinition = SchemaDefinition> = {
445
649
  */
446
650
  readonly __exclusions?: readonly ExclusionSpec[] | ExclusionsCallback;
447
651
  readonly __checks?: readonly ModelCheckConfig[] | ChecksCallback;
652
+ /** Separate row-rule authoring callback, evaluated against finalized model fields. */
653
+ readonly __rowRules?: (r: RowRuleBuilder) => readonly RowRuleDeclaration[];
448
654
  /** Extension attributes declared on the model (`@@search`-style). */
449
655
  readonly __extensions?: readonly ExtensionAttributeUse[];
450
656
  /** `@@policy([field], context: "key")` — application-level row scoping. */
@@ -479,6 +685,8 @@ export type CreateModelConfig<TSchema extends SchemaDefinition = SchemaDefinitio
479
685
  */
480
686
  exclusions?: readonly ExclusionConfig<TSchema>[] | ((c: PredicateBuilder<SchemaColumnNames<TSchema>, TSchema>) => readonly ExclusionConfig<TSchema>[]);
481
687
  checks?: readonly ModelCheckConfig[] | ChecksCallback<SchemaColumnNames<TSchema>, TSchema>;
688
+ /** Database-enforced row invariants; every field slot is bound to this model. */
689
+ rowRules?: (r: RowRuleBuilder<SchemaColumnNames<TSchema>>) => readonly RowRuleDeclaration<SchemaColumnNames<TSchema>>[];
482
690
  /** Extension model attributes (`@@search`-style declarations). */
483
691
  extensions?: readonly ExtensionAttributeUse[];
484
692
  /**
@@ -503,6 +711,7 @@ export type ColumnRefKind = "String" | "Boolean" | "Int" | "BigInt" | "Float" |
503
711
  /** `ColumnType` → `ColumnRefKind`, as a table: one indexed access, never a conditional chain. */
504
712
  type RefKindByColumnType = {
505
713
  readonly string: "String";
714
+ readonly localDate: "String";
506
715
  readonly int: "Int";
507
716
  readonly bigint: "BigInt";
508
717
  readonly float: "Float";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeorm/dsl",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "TypeScript schema DSL for VibeORM v2 — compiles to canonical SchemaIR, with .prisma generation for interop",
5
5
  "keywords": [
6
6
  "orm",
@@ -43,8 +43,8 @@
43
43
  "bun": ">=1.2.0"
44
44
  },
45
45
  "dependencies": {
46
- "@vibeorm/schema": "4.1.0",
47
- "@vibeorm/sql": "4.1.0"
46
+ "@vibeorm/schema": "4.3.0",
47
+ "@vibeorm/sql": "4.3.0"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"