@prisma-next/sql-contract 0.12.0-dev.7 → 0.12.0-dev.70

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.
Files changed (57) hide show
  1. package/dist/canonicalization-hooks.mjs +10 -5
  2. package/dist/canonicalization-hooks.mjs.map +1 -1
  3. package/dist/factories.d.mts +4 -2
  4. package/dist/factories.d.mts.map +1 -1
  5. package/dist/factories.mjs +3 -2
  6. package/dist/factories.mjs.map +1 -1
  7. package/dist/foreign-key-BATxB95l.d.mts +121 -0
  8. package/dist/foreign-key-BATxB95l.d.mts.map +1 -0
  9. package/dist/index-type-validation.d.mts +2 -2
  10. package/dist/index-type-validation.mjs +1 -1
  11. package/dist/index-type-validation.mjs.map +1 -1
  12. package/dist/{index-types-B1cf5N0F.d.mts → index-types-Czsyu7Iw.d.mts} +1 -1
  13. package/dist/{index-types-B1cf5N0F.d.mts.map → index-types-Czsyu7Iw.d.mts.map} +1 -1
  14. package/dist/index-types.d.mts +1 -1
  15. package/dist/pack-types.d.mts +1 -1
  16. package/dist/referential-action-sql.d.mts +12 -0
  17. package/dist/referential-action-sql.d.mts.map +1 -0
  18. package/dist/referential-action-sql.mjs +17 -0
  19. package/dist/referential-action-sql.mjs.map +1 -0
  20. package/dist/resolve-storage-table.d.mts +17 -0
  21. package/dist/resolve-storage-table.d.mts.map +1 -0
  22. package/dist/resolve-storage-table.mjs +26 -0
  23. package/dist/resolve-storage-table.mjs.map +1 -0
  24. package/dist/sql-storage-BQDI6Xrg.d.mts +312 -0
  25. package/dist/sql-storage-BQDI6Xrg.d.mts.map +1 -0
  26. package/dist/{types-YQrDHy-b.mjs → types-BulBf1Cm.mjs} +102 -26
  27. package/dist/types-BulBf1Cm.mjs.map +1 -0
  28. package/dist/types-CMsiBhc7.d.mts +208 -0
  29. package/dist/types-CMsiBhc7.d.mts.map +1 -0
  30. package/dist/types.d.mts +4 -2
  31. package/dist/types.mjs +2 -2
  32. package/dist/validators.d.mts +50 -14
  33. package/dist/validators.d.mts.map +1 -1
  34. package/dist/validators.mjs +87 -25
  35. package/dist/validators.mjs.map +1 -1
  36. package/package.json +11 -9
  37. package/src/canonicalization-hooks.ts +5 -5
  38. package/src/exports/referential-action-sql.ts +1 -0
  39. package/src/exports/resolve-storage-table.ts +1 -0
  40. package/src/exports/types.ts +6 -0
  41. package/src/factories.ts +2 -1
  42. package/src/index-type-validation.ts +1 -1
  43. package/src/ir/build-sql-namespace.ts +33 -19
  44. package/src/ir/check-constraint.ts +42 -0
  45. package/src/ir/foreign-key-reference.ts +23 -0
  46. package/src/ir/sql-storage.ts +44 -50
  47. package/src/ir/sql-unbound-namespace.ts +11 -4
  48. package/src/ir/storage-column.ts +4 -1
  49. package/src/ir/storage-table.ts +6 -0
  50. package/src/ir/storage-value-set.ts +42 -0
  51. package/src/referential-action-sql.ts +14 -0
  52. package/src/resolve-storage-table.ts +41 -0
  53. package/src/types.ts +13 -0
  54. package/src/validators.ts +122 -40
  55. package/dist/types-Cx_5A_L0.d.mts +0 -513
  56. package/dist/types-Cx_5A_L0.d.mts.map +0 -1
  57. package/dist/types-YQrDHy-b.mjs.map +0 -1
package/src/validators.ts CHANGED
@@ -77,6 +77,14 @@ const ExecutionSchema = type({
77
77
  },
78
78
  });
79
79
 
80
+ const ValueSetRefSchema = type({
81
+ plane: "'domain' | 'storage'",
82
+ namespaceId: 'string',
83
+ entityKind: "'enum' | 'value-set'",
84
+ name: 'string',
85
+ 'spaceId?': 'string',
86
+ });
87
+
80
88
  const StorageColumnSchema = type({
81
89
  '+': 'reject',
82
90
  nativeType: 'string',
@@ -86,6 +94,7 @@ const StorageColumnSchema = type({
86
94
  'typeRef?': 'string',
87
95
  'default?': ColumnDefaultSchema,
88
96
  'control?': ControlPolicySchema,
97
+ 'valueSet?': ValueSetRefSchema,
89
98
  }).narrow((col, ctx) => {
90
99
  if (col.typeParams !== undefined && col.typeRef !== undefined) {
91
100
  return ctx.mustBe('a column with either typeParams or typeRef, not both');
@@ -109,7 +118,7 @@ const StorageTypeInstanceSchema = type
109
118
  });
110
119
 
111
120
  /**
112
- * Postgres native enum entry under `storage.namespaces[namespaceId].enum[name]`.
121
+ * Postgres native enum entry under `storage.namespaces[namespaceId].entries.type[name]`.
113
122
  * Document-scoped `storage.types` carries codec aliases only
114
123
  * (`DocumentScopedStorageTypeSchema`).
115
124
  */
@@ -124,6 +133,31 @@ const PostgresEnumTypeSchema = type({
124
133
  /** Document-scoped `storage.types`: codec triples only. */
125
134
  const DocumentScopedStorageTypeSchema = StorageTypeInstanceSchema;
126
135
 
136
+ /**
137
+ * Storage value-set entry under `storage.namespaces[id].entries.valueSet[name]`.
138
+ * Carries a `kind: 'value-set'` discriminator (enumerable, survives JSON) and an
139
+ * ordered `values` array of codec-encoded permitted values.
140
+ */
141
+ export const StorageValueSetSchema = type({
142
+ kind: "'value-set'",
143
+ values: type.string.array().readonly(),
144
+ });
145
+
146
+ /**
147
+ * Domain enum entry under `domain.namespaces[id].enum[name]`.
148
+ * Carries the codec id and an ordered `members` array of `{name, value}` pairs.
149
+ */
150
+ export const ContractEnumSchema = type({
151
+ '+': 'reject',
152
+ codecId: 'string',
153
+ members: type({
154
+ name: 'string',
155
+ value: 'string',
156
+ })
157
+ .array()
158
+ .readonly(),
159
+ });
160
+
127
161
  const PrimaryKeySchema = type.declare<PrimaryKeyInput>().type({
128
162
  columns: type.string.array().readonly(),
129
163
  'name?': 'string',
@@ -146,6 +180,14 @@ export const ForeignKeyReferenceSchema = type({
146
180
  namespaceId: 'string',
147
181
  tableName: 'string',
148
182
  columns: type.string.array().readonly(),
183
+ 'spaceId?': 'string',
184
+ }) satisfies Type<ForeignKeyReferenceInput>;
185
+
186
+ export const ForeignKeySourceSchema = type({
187
+ '+': 'reject',
188
+ namespaceId: 'string',
189
+ tableName: 'string',
190
+ columns: type.string.array().readonly(),
149
191
  }) satisfies Type<ForeignKeyReferenceInput>;
150
192
 
151
193
  export const ReferentialActionSchema = type
@@ -153,7 +195,7 @@ export const ReferentialActionSchema = type
153
195
  .type("'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault'");
154
196
 
155
197
  export const ForeignKeySchema = type.declare<ForeignKeyInput>().type({
156
- source: ForeignKeyReferenceSchema,
198
+ source: ForeignKeySourceSchema,
157
199
  target: ForeignKeyReferenceSchema,
158
200
  'name?': 'string',
159
201
  'onDelete?': ReferentialActionSchema,
@@ -162,6 +204,13 @@ export const ForeignKeySchema = type.declare<ForeignKeyInput>().type({
162
204
  index: 'boolean',
163
205
  });
164
206
 
207
+ export const CheckConstraintSchema = type({
208
+ '+': 'reject',
209
+ name: 'string',
210
+ column: 'string',
211
+ valueSet: ValueSetRefSchema,
212
+ });
213
+
165
214
  const StorageTableSchema = type({
166
215
  '+': 'reject',
167
216
  columns: type({ '[string]': StorageColumnSchema }),
@@ -170,6 +219,7 @@ const StorageTableSchema = type({
170
219
  indexes: IndexSchema.array().readonly(),
171
220
  foreignKeys: ForeignKeySchema.array().readonly(),
172
221
  'control?': ControlPolicySchema,
222
+ 'checks?': CheckConstraintSchema.array().readonly(),
173
223
  });
174
224
 
175
225
  /**
@@ -240,7 +290,7 @@ function namespaceSlotEntrySchema(
240
290
  * Builds the per-namespace entry schema for `storage.namespaces[id]`.
241
291
  * Pack-contributed `validatorSchema` fragments — keyed by the
242
292
  * descriptor's `discriminator` — validate each entry by matching the
243
- * entry's `kind` field on the `'enum?'` slot.
293
+ * entry's `kind` field on the `'entries.type'` slot.
244
294
  */
245
295
  export function createNamespaceEntrySchema(
246
296
  fragments?: ReadonlyMap<string, Type<unknown>>,
@@ -249,9 +299,13 @@ export function createNamespaceEntrySchema(
249
299
  '+': 'reject',
250
300
  id: 'string',
251
301
  'kind?': 'string',
252
- 'tables?': type({ '[string]': StorageTableSchema }),
253
- 'enum?': type({
254
- '[string]': namespaceSlotEntrySchema(PostgresEnumTypeSchema, 'postgres-enum', fragments),
302
+ entries: type({
303
+ '+': 'reject',
304
+ 'table?': type({ '[string]': StorageTableSchema }),
305
+ 'type?': type({
306
+ '[string]': namespaceSlotEntrySchema(PostgresEnumTypeSchema, 'postgres-enum', fragments),
307
+ }),
308
+ 'valueSet?': type({ '[string]': StorageValueSetSchema }),
255
309
  }),
256
310
  }) as Type<unknown>;
257
311
  }
@@ -281,20 +335,22 @@ export function createSqlStorageSchema(
281
335
 
282
336
  const StorageSchema = createSqlStorageSchema();
283
337
 
284
- // SQL-specific namespace walk shape (`tables` is the SQL family's idiom —
285
- // the framework `Namespace` interface no longer carries it). The wider
286
- // `object` table value keeps this helper structurally compatible with
287
- // `SqlNamespace` (whose tables narrow to `StorageTable`) and the JSON
288
- // envelope variants that lose class identity.
338
+ // SQL-specific namespace walk shape (`entries.table` is the SQL family's
339
+ // idiom). The wider `object` table value keeps this helper structurally
340
+ // compatible with `SqlNamespace` and JSON envelope variants that lose class
341
+ // identity.
289
342
  type NamespacedStorageWalk = {
290
343
  readonly namespaces: Readonly<
291
- Record<string, Namespace & { readonly tables?: Readonly<Record<string, object>> }>
344
+ Record<
345
+ string,
346
+ Namespace & { readonly entries: { readonly table: Readonly<Record<string, object>> } }
347
+ >
292
348
  >;
293
349
  };
294
350
 
295
351
  function eachStorageTable(storage: NamespacedStorageWalk) {
296
352
  return Object.entries(storage.namespaces).flatMap(([namespaceId, ns]) =>
297
- Object.entries(ns.tables ?? {}).map(([tableName, table]) => ({
353
+ Object.entries(ns.entries.table).map(([tableName, table]) => ({
298
354
  namespaceId,
299
355
  tableName,
300
356
  table,
@@ -302,16 +358,6 @@ function eachStorageTable(storage: NamespacedStorageWalk) {
302
358
  );
303
359
  }
304
360
 
305
- function findStorageTableByTableName(storage: NamespacedStorageWalk, tableName: string): unknown {
306
- for (const ns of Object.values(storage.namespaces)) {
307
- const t = ns.tables?.[tableName];
308
- if (t !== undefined) {
309
- return t;
310
- }
311
- }
312
- return undefined;
313
- }
314
-
315
361
  function isPlainRecord(value: unknown): value is Record<string, unknown> {
316
362
  return typeof value === 'object' && value !== null && !Array.isArray(value);
317
363
  }
@@ -357,6 +403,7 @@ const ModelFieldSchema = type({
357
403
  type: ContractFieldTypeSchema,
358
404
  'many?': 'true',
359
405
  'dict?': 'true',
406
+ 'valueSet?': ValueSetRefSchema,
360
407
  });
361
408
 
362
409
  const ModelStorageFieldSchema = type({
@@ -367,6 +414,7 @@ const ModelStorageFieldSchema = type({
367
414
 
368
415
  const ModelStorageSchema = type({
369
416
  table: 'string',
417
+ namespaceId: 'string',
370
418
  fields: type({ '[string]': ModelStorageFieldSchema }),
371
419
  });
372
420
 
@@ -421,13 +469,14 @@ export function createSqlContractSchema(
421
469
  'capabilities?': 'Record<string, Record<string, boolean>>',
422
470
  'extensionPacks?': 'Record<string, unknown>',
423
471
  'meta?': ContractMetaSchema,
424
- 'defaultControl?': ControlPolicySchema,
472
+ 'defaultControlPolicy?': ControlPolicySchema,
425
473
  'roots?': type({ '[string]': CrossReferenceSchema }),
426
474
  domain: type({
427
475
  namespaces: type({
428
476
  '[string]': type({
429
477
  models: type({ '[string]': ModelSchema }),
430
478
  'valueObjects?': 'Record<string, unknown>',
479
+ 'enum?': type({ '[string]': ContractEnumSchema }),
431
480
  }),
432
481
  }),
433
482
  }),
@@ -562,6 +611,9 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
562
611
  for (const fk of table.foreignKeys) {
563
612
  registerNamedObject('foreign key', fk.name);
564
613
  }
614
+ for (const check of table.checks ?? []) {
615
+ registerNamedObject('check constraint', check.name);
616
+ }
565
617
 
566
618
  for (const [name, kinds] of namedObjects) {
567
619
  if (kinds.length > 1) {
@@ -680,6 +732,18 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
680
732
  }
681
733
  }
682
734
  }
735
+
736
+ const seenCheckDefinitions = new Set<string>();
737
+ for (const check of table.checks ?? []) {
738
+ const signature = JSON.stringify({ column: check.column, valueSet: check.valueSet });
739
+ if (seenCheckDefinitions.has(signature)) {
740
+ errors.push(
741
+ `Namespace "${namespaceId}" table "${tableName}": duplicate check constraint definition on column "${check.column}"`,
742
+ );
743
+ continue;
744
+ }
745
+ seenCheckDefinitions.add(signature);
746
+ }
683
747
  }
684
748
 
685
749
  return errors;
@@ -696,12 +760,19 @@ export function validateModelStorageReferences(contract: Contract<SqlStorage>):
696
760
  const models = namespace.models as Record<string, ContractModel<SqlModelStorage>>;
697
761
  for (const [modelName, model] of Object.entries(models)) {
698
762
  const qualifiedName = `${namespaceId}:${modelName}`;
699
- const storageTable = model.storage.table;
763
+ const storageNamespaceId = model.storage.namespaceId;
764
+ if (storageNamespaceId !== namespaceId) {
765
+ throw new ContractValidationError(
766
+ `Model "${qualifiedName}" storage.namespaceId "${storageNamespaceId}" does not match domain namespace "${namespaceId}"`,
767
+ 'storage',
768
+ );
769
+ }
700
770
 
701
- const rawTable = findStorageTableByTableName(contract.storage, storageTable);
771
+ const storageTable = model.storage.table;
772
+ const rawTable = contract.storage.namespaces[storageNamespaceId]?.entries.table[storageTable];
702
773
  if (rawTable === undefined) {
703
774
  throw new ContractValidationError(
704
- `Model "${qualifiedName}" references non-existent table "${storageTable}"`,
775
+ `Model "${qualifiedName}" references non-existent table "${storageNamespaceId}.${storageTable}"`,
705
776
  'storage',
706
777
  );
707
778
  }
@@ -781,6 +852,15 @@ export function validateSqlStorageConsistency(contract: Contract<SqlStorage>): v
781
852
  }
782
853
  }
783
854
 
855
+ for (const check of table.checks ?? []) {
856
+ if (!columnNames.has(check.column)) {
857
+ throw new ContractValidationError(
858
+ `Namespace "${namespaceId}" table "${tableName}" check constraint "${check.name}" references non-existent column "${check.column}"`,
859
+ 'storage',
860
+ );
861
+ }
862
+ }
863
+
784
864
  for (const [colName, column] of Object.entries(table.columns)) {
785
865
  if (!column.nullable && column.default?.kind === 'literal' && column.default.value === null) {
786
866
  throw new ContractValidationError(
@@ -807,23 +887,25 @@ export function validateSqlStorageConsistency(contract: Contract<SqlStorage>): v
807
887
  }
808
888
  }
809
889
 
810
- const targetNamespace = contract.storage.namespaces[fk.target.namespaceId];
811
- const referencedRaw = targetNamespace?.tables?.[fk.target.tableName];
812
- if (referencedRaw === undefined) {
813
- throw new ContractValidationError(
814
- `Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent table "${fk.target.namespaceId}.${fk.target.tableName}"`,
815
- 'storage',
816
- );
817
- }
818
- const referencedTable = referencedRaw as StorageTable;
819
- const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
820
- for (const colName of fk.target.columns) {
821
- if (!referencedColumnNames.has(colName)) {
890
+ if (fk.target.spaceId === undefined) {
891
+ const targetNamespace = contract.storage.namespaces[fk.target.namespaceId];
892
+ const referencedRaw = targetNamespace?.entries.table[fk.target.tableName];
893
+ if (referencedRaw === undefined) {
822
894
  throw new ContractValidationError(
823
- `Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.target.tableName}"`,
895
+ `Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent table "${fk.target.namespaceId}.${fk.target.tableName}"`,
824
896
  'storage',
825
897
  );
826
898
  }
899
+ const referencedTable = referencedRaw as StorageTable;
900
+ const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
901
+ for (const colName of fk.target.columns) {
902
+ if (!referencedColumnNames.has(colName)) {
903
+ throw new ContractValidationError(
904
+ `Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.target.tableName}"`,
905
+ 'storage',
906
+ );
907
+ }
908
+ }
827
909
  }
828
910
 
829
911
  if (fk.source.columns.length !== fk.target.columns.length) {