@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.
- package/dist/canonicalization-hooks.mjs +10 -5
- package/dist/canonicalization-hooks.mjs.map +1 -1
- package/dist/factories.d.mts +4 -2
- package/dist/factories.d.mts.map +1 -1
- package/dist/factories.mjs +3 -2
- package/dist/factories.mjs.map +1 -1
- package/dist/foreign-key-BATxB95l.d.mts +121 -0
- package/dist/foreign-key-BATxB95l.d.mts.map +1 -0
- package/dist/index-type-validation.d.mts +2 -2
- package/dist/index-type-validation.mjs +1 -1
- package/dist/index-type-validation.mjs.map +1 -1
- package/dist/{index-types-B1cf5N0F.d.mts → index-types-Czsyu7Iw.d.mts} +1 -1
- package/dist/{index-types-B1cf5N0F.d.mts.map → index-types-Czsyu7Iw.d.mts.map} +1 -1
- package/dist/index-types.d.mts +1 -1
- package/dist/pack-types.d.mts +1 -1
- package/dist/referential-action-sql.d.mts +12 -0
- package/dist/referential-action-sql.d.mts.map +1 -0
- package/dist/referential-action-sql.mjs +17 -0
- package/dist/referential-action-sql.mjs.map +1 -0
- package/dist/resolve-storage-table.d.mts +17 -0
- package/dist/resolve-storage-table.d.mts.map +1 -0
- package/dist/resolve-storage-table.mjs +26 -0
- package/dist/resolve-storage-table.mjs.map +1 -0
- package/dist/sql-storage-BQDI6Xrg.d.mts +312 -0
- package/dist/sql-storage-BQDI6Xrg.d.mts.map +1 -0
- package/dist/{types-YQrDHy-b.mjs → types-BulBf1Cm.mjs} +102 -26
- package/dist/types-BulBf1Cm.mjs.map +1 -0
- package/dist/types-CMsiBhc7.d.mts +208 -0
- package/dist/types-CMsiBhc7.d.mts.map +1 -0
- package/dist/types.d.mts +4 -2
- package/dist/types.mjs +2 -2
- package/dist/validators.d.mts +50 -14
- package/dist/validators.d.mts.map +1 -1
- package/dist/validators.mjs +87 -25
- package/dist/validators.mjs.map +1 -1
- package/package.json +11 -9
- package/src/canonicalization-hooks.ts +5 -5
- package/src/exports/referential-action-sql.ts +1 -0
- package/src/exports/resolve-storage-table.ts +1 -0
- package/src/exports/types.ts +6 -0
- package/src/factories.ts +2 -1
- package/src/index-type-validation.ts +1 -1
- package/src/ir/build-sql-namespace.ts +33 -19
- package/src/ir/check-constraint.ts +42 -0
- package/src/ir/foreign-key-reference.ts +23 -0
- package/src/ir/sql-storage.ts +44 -50
- package/src/ir/sql-unbound-namespace.ts +11 -4
- package/src/ir/storage-column.ts +4 -1
- package/src/ir/storage-table.ts +6 -0
- package/src/ir/storage-value-set.ts +42 -0
- package/src/referential-action-sql.ts +14 -0
- package/src/resolve-storage-table.ts +41 -0
- package/src/types.ts +13 -0
- package/src/validators.ts +122 -40
- package/dist/types-Cx_5A_L0.d.mts +0 -513
- package/dist/types-Cx_5A_L0.d.mts.map +0 -1
- 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].
|
|
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:
|
|
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 `'
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
'
|
|
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 (`
|
|
285
|
-
//
|
|
286
|
-
// `
|
|
287
|
-
//
|
|
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<
|
|
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.
|
|
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
|
-
'
|
|
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
|
|
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
|
|
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
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
|
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) {
|