@prisma-next/sql-contract 0.12.0 → 0.13.0-dev.10
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.d.mts.map +1 -1
- package/dist/canonicalization-hooks.mjs +10 -11
- 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 +20 -0
- package/dist/resolve-storage-table.d.mts.map +1 -0
- package/dist/resolve-storage-table.mjs +42 -0
- package/dist/resolve-storage-table.mjs.map +1 -0
- package/dist/sql-storage-C2IALLla.d.mts +314 -0
- package/dist/sql-storage-C2IALLla.d.mts.map +1 -0
- package/dist/types-C2A2nkzv.d.mts +208 -0
- package/dist/types-C2A2nkzv.d.mts.map +1 -0
- package/dist/{types-DPkj4y3_.mjs → types-DqhaAjCH.mjs} +109 -28
- package/dist/types-DqhaAjCH.mjs.map +1 -0
- package/dist/types.d.mts +4 -2
- package/dist/types.mjs +2 -2
- package/dist/validators.d.mts +51 -14
- package/dist/validators.d.mts.map +1 -1
- package/dist/validators.mjs +116 -32
- package/dist/validators.mjs.map +1 -1
- package/package.json +11 -9
- package/src/canonicalization-hooks.ts +5 -6
- 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/postgres-enum-storage-entry.ts +2 -0
- package/src/ir/sql-storage.ts +53 -50
- package/src/ir/sql-unbound-namespace.ts +11 -4
- package/src/ir/storage-column.ts +7 -1
- package/src/ir/storage-table.ts +10 -0
- package/src/ir/storage-type-instance.ts +5 -3
- package/src/ir/storage-value-set.ts +43 -0
- package/src/referential-action-sql.ts +14 -0
- package/src/resolve-storage-table.ts +61 -0
- package/src/types.ts +13 -0
- package/src/validators.ts +158 -46
- package/dist/types-ChlHcJCu.d.mts +0 -508
- package/dist/types-ChlHcJCu.d.mts.map +0 -1
- package/dist/types-DPkj4y3_.mjs.map +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { SqlNamespace, SqlStorage } from './ir/sql-storage';
|
|
2
|
+
import type { StorageTable } from './ir/storage-table';
|
|
3
|
+
|
|
4
|
+
export interface ResolvedStorageTable {
|
|
5
|
+
readonly namespaceId: string;
|
|
6
|
+
readonly table: StorageTable;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function tableInNamespace(
|
|
10
|
+
namespace: SqlNamespace | undefined,
|
|
11
|
+
tableName: string,
|
|
12
|
+
): StorageTable | undefined {
|
|
13
|
+
if (namespace === undefined) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const tables = namespace.entries.table;
|
|
17
|
+
if (!Object.hasOwn(tables, tableName)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
return tables[tableName];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a bare storage table name to its namespace coordinate and table IR.
|
|
25
|
+
*
|
|
26
|
+
* When `namespaceId` is supplied, the table is resolved strictly within that
|
|
27
|
+
* namespace (no scan). When omitted, a bare name unique across namespaces
|
|
28
|
+
* resolves to its sole namespace; a bare name declared in more than one
|
|
29
|
+
* namespace throws a fail-fast diagnostic naming the candidate namespaces
|
|
30
|
+
* rather than silently selecting the first match.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveStorageTable(
|
|
33
|
+
storage: SqlStorage,
|
|
34
|
+
tableName: string,
|
|
35
|
+
namespaceId?: string,
|
|
36
|
+
): ResolvedStorageTable | undefined {
|
|
37
|
+
if (namespaceId !== undefined) {
|
|
38
|
+
const table = tableInNamespace(storage.namespaces[namespaceId], tableName);
|
|
39
|
+
return table === undefined ? undefined : { namespaceId, table };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const matches: ResolvedStorageTable[] = [];
|
|
43
|
+
for (const candidateNamespaceId of Object.keys(storage.namespaces)) {
|
|
44
|
+
const table = tableInNamespace(storage.namespaces[candidateNamespaceId], tableName);
|
|
45
|
+
if (table !== undefined) {
|
|
46
|
+
matches.push({ namespaceId: candidateNamespaceId, table });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (matches.length > 1) {
|
|
51
|
+
const candidates = matches
|
|
52
|
+
.map((match) => match.namespaceId)
|
|
53
|
+
.sort()
|
|
54
|
+
.join(', ');
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Storage table "${tableName}" is ambiguous across namespaces [${candidates}]; qualify it with a namespace coordinate.`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return matches[0];
|
|
61
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import type { CodecTrait } from '@prisma-next/framework-components/codec';
|
|
2
|
+
import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
|
|
2
3
|
import type { ReferentialAction } from './ir/foreign-key';
|
|
3
4
|
|
|
5
|
+
export interface SqlControlDriverInstance<T extends string = string>
|
|
6
|
+
extends ControlDriverInstance<'sql', T> {
|
|
7
|
+
query<Row = Record<string, unknown>>(
|
|
8
|
+
sql: string,
|
|
9
|
+
params?: readonly unknown[],
|
|
10
|
+
): Promise<{ readonly rows: Row[] }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
export { buildSqlNamespace, buildSqlNamespaceMap } from './ir/build-sql-namespace';
|
|
14
|
+
export { CheckConstraint, type CheckConstraintInput } from './ir/check-constraint';
|
|
5
15
|
export {
|
|
6
16
|
ForeignKey,
|
|
7
17
|
type ForeignKeyInput,
|
|
@@ -24,6 +34,7 @@ export {
|
|
|
24
34
|
SqlStorage,
|
|
25
35
|
type SqlStorageInput,
|
|
26
36
|
type SqlStorageTypeEntry,
|
|
37
|
+
storageTableAt,
|
|
27
38
|
} from './ir/sql-storage';
|
|
28
39
|
export { SqlUnboundNamespace } from './ir/sql-unbound-namespace';
|
|
29
40
|
export { StorageColumn, type StorageColumnInput } from './ir/storage-column';
|
|
@@ -35,6 +46,7 @@ export {
|
|
|
35
46
|
type StorageTypeInstanceInput,
|
|
36
47
|
toStorageTypeInstance,
|
|
37
48
|
} from './ir/storage-type-instance';
|
|
49
|
+
export { StorageValueSet, type StorageValueSetInput } from './ir/storage-value-set';
|
|
38
50
|
export {
|
|
39
51
|
UniqueConstraint,
|
|
40
52
|
type UniqueConstraintInput,
|
|
@@ -54,6 +66,7 @@ export type SqlModelFieldStorage = {
|
|
|
54
66
|
|
|
55
67
|
export type SqlModelStorage = {
|
|
56
68
|
readonly table: string;
|
|
69
|
+
readonly namespaceId: string;
|
|
57
70
|
readonly fields: Record<string, SqlModelFieldStorage>;
|
|
58
71
|
};
|
|
59
72
|
|
package/src/validators.ts
CHANGED
|
@@ -33,6 +33,7 @@ type ColumnDefaultFunction = { readonly kind: 'function'; readonly expression: s
|
|
|
33
33
|
const literalKindSchema = type("'literal'");
|
|
34
34
|
const functionKindSchema = type("'function'");
|
|
35
35
|
const generatorKindSchema = type("'generator'");
|
|
36
|
+
const ControlPolicySchema = type("'managed' | 'tolerated' | 'external' | 'observed'");
|
|
36
37
|
const generatorIdSchema = type('string').narrow((value, ctx) => {
|
|
37
38
|
return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value) ? true : ctx.mustBe('a flat generator id');
|
|
38
39
|
});
|
|
@@ -76,6 +77,14 @@ const ExecutionSchema = type({
|
|
|
76
77
|
},
|
|
77
78
|
});
|
|
78
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
|
+
|
|
79
88
|
const StorageColumnSchema = type({
|
|
80
89
|
'+': 'reject',
|
|
81
90
|
nativeType: 'string',
|
|
@@ -84,6 +93,8 @@ const StorageColumnSchema = type({
|
|
|
84
93
|
'typeParams?': 'Record<string, unknown>',
|
|
85
94
|
'typeRef?': 'string',
|
|
86
95
|
'default?': ColumnDefaultSchema,
|
|
96
|
+
'control?': ControlPolicySchema,
|
|
97
|
+
'valueSet?': ValueSetRefSchema,
|
|
87
98
|
}).narrow((col, ctx) => {
|
|
88
99
|
if (col.typeParams !== undefined && col.typeRef !== undefined) {
|
|
89
100
|
return ctx.mustBe('a column with either typeParams or typeRef, not both');
|
|
@@ -103,11 +114,11 @@ const StorageTypeInstanceSchema = type
|
|
|
103
114
|
kind: "'codec-instance'",
|
|
104
115
|
codecId: 'string',
|
|
105
116
|
nativeType: 'string',
|
|
106
|
-
typeParams: 'Record<string, unknown>',
|
|
117
|
+
'typeParams?': 'Record<string, unknown>',
|
|
107
118
|
});
|
|
108
119
|
|
|
109
120
|
/**
|
|
110
|
-
* Postgres native enum entry under `storage.namespaces[namespaceId].
|
|
121
|
+
* Postgres native enum entry under `storage.namespaces[namespaceId].entries.type[name]`.
|
|
111
122
|
* Document-scoped `storage.types` carries codec aliases only
|
|
112
123
|
* (`DocumentScopedStorageTypeSchema`).
|
|
113
124
|
*/
|
|
@@ -116,11 +127,39 @@ const PostgresEnumTypeSchema = type({
|
|
|
116
127
|
'name?': 'string',
|
|
117
128
|
'nativeType?': 'string',
|
|
118
129
|
values: type.string.array().readonly(),
|
|
130
|
+
'control?': ControlPolicySchema,
|
|
119
131
|
});
|
|
120
132
|
|
|
121
133
|
/** Document-scoped `storage.types`: codec triples only. */
|
|
122
134
|
const DocumentScopedStorageTypeSchema = StorageTypeInstanceSchema;
|
|
123
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 | number | boolean | null | unknown[] | Record<string, unknown>')
|
|
144
|
+
.array()
|
|
145
|
+
.readonly(),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Domain enum entry under `domain.namespaces[id].enum[name]`.
|
|
150
|
+
* Carries the codec id and an ordered `members` array of `{name, value}` pairs.
|
|
151
|
+
*/
|
|
152
|
+
export const ContractEnumSchema = type({
|
|
153
|
+
'+': 'reject',
|
|
154
|
+
codecId: 'string',
|
|
155
|
+
members: type({
|
|
156
|
+
name: 'string',
|
|
157
|
+
value: 'string | number | boolean | null | unknown[] | Record<string, unknown>',
|
|
158
|
+
})
|
|
159
|
+
.array()
|
|
160
|
+
.readonly(),
|
|
161
|
+
});
|
|
162
|
+
|
|
124
163
|
const PrimaryKeySchema = type.declare<PrimaryKeyInput>().type({
|
|
125
164
|
columns: type.string.array().readonly(),
|
|
126
165
|
'name?': 'string',
|
|
@@ -143,6 +182,14 @@ export const ForeignKeyReferenceSchema = type({
|
|
|
143
182
|
namespaceId: 'string',
|
|
144
183
|
tableName: 'string',
|
|
145
184
|
columns: type.string.array().readonly(),
|
|
185
|
+
'spaceId?': 'string',
|
|
186
|
+
}) satisfies Type<ForeignKeyReferenceInput>;
|
|
187
|
+
|
|
188
|
+
export const ForeignKeySourceSchema = type({
|
|
189
|
+
'+': 'reject',
|
|
190
|
+
namespaceId: 'string',
|
|
191
|
+
tableName: 'string',
|
|
192
|
+
columns: type.string.array().readonly(),
|
|
146
193
|
}) satisfies Type<ForeignKeyReferenceInput>;
|
|
147
194
|
|
|
148
195
|
export const ReferentialActionSchema = type
|
|
@@ -150,7 +197,7 @@ export const ReferentialActionSchema = type
|
|
|
150
197
|
.type("'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault'");
|
|
151
198
|
|
|
152
199
|
export const ForeignKeySchema = type.declare<ForeignKeyInput>().type({
|
|
153
|
-
source:
|
|
200
|
+
source: ForeignKeySourceSchema,
|
|
154
201
|
target: ForeignKeyReferenceSchema,
|
|
155
202
|
'name?': 'string',
|
|
156
203
|
'onDelete?': ReferentialActionSchema,
|
|
@@ -159,6 +206,13 @@ export const ForeignKeySchema = type.declare<ForeignKeyInput>().type({
|
|
|
159
206
|
index: 'boolean',
|
|
160
207
|
});
|
|
161
208
|
|
|
209
|
+
export const CheckConstraintSchema = type({
|
|
210
|
+
'+': 'reject',
|
|
211
|
+
name: 'string',
|
|
212
|
+
column: 'string',
|
|
213
|
+
valueSet: ValueSetRefSchema,
|
|
214
|
+
});
|
|
215
|
+
|
|
162
216
|
const StorageTableSchema = type({
|
|
163
217
|
'+': 'reject',
|
|
164
218
|
columns: type({ '[string]': StorageColumnSchema }),
|
|
@@ -166,6 +220,8 @@ const StorageTableSchema = type({
|
|
|
166
220
|
uniques: UniqueConstraintSchema.array().readonly(),
|
|
167
221
|
indexes: IndexSchema.array().readonly(),
|
|
168
222
|
foreignKeys: ForeignKeySchema.array().readonly(),
|
|
223
|
+
'control?': ControlPolicySchema,
|
|
224
|
+
'checks?': CheckConstraintSchema.array().readonly(),
|
|
169
225
|
});
|
|
170
226
|
|
|
171
227
|
/**
|
|
@@ -236,7 +292,7 @@ function namespaceSlotEntrySchema(
|
|
|
236
292
|
* Builds the per-namespace entry schema for `storage.namespaces[id]`.
|
|
237
293
|
* Pack-contributed `validatorSchema` fragments — keyed by the
|
|
238
294
|
* descriptor's `discriminator` — validate each entry by matching the
|
|
239
|
-
* entry's `kind` field on the `'
|
|
295
|
+
* entry's `kind` field on the `'entries.type'` slot.
|
|
240
296
|
*/
|
|
241
297
|
export function createNamespaceEntrySchema(
|
|
242
298
|
fragments?: ReadonlyMap<string, Type<unknown>>,
|
|
@@ -245,9 +301,13 @@ export function createNamespaceEntrySchema(
|
|
|
245
301
|
'+': 'reject',
|
|
246
302
|
id: 'string',
|
|
247
303
|
'kind?': 'string',
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
'
|
|
304
|
+
entries: type({
|
|
305
|
+
'+': 'reject',
|
|
306
|
+
'table?': type({ '[string]': StorageTableSchema }),
|
|
307
|
+
'type?': type({
|
|
308
|
+
'[string]': namespaceSlotEntrySchema(PostgresEnumTypeSchema, 'postgres-enum', fragments),
|
|
309
|
+
}),
|
|
310
|
+
'valueSet?': type({ '[string]': StorageValueSetSchema }),
|
|
251
311
|
}),
|
|
252
312
|
}) as Type<unknown>;
|
|
253
313
|
}
|
|
@@ -277,20 +337,22 @@ export function createSqlStorageSchema(
|
|
|
277
337
|
|
|
278
338
|
const StorageSchema = createSqlStorageSchema();
|
|
279
339
|
|
|
280
|
-
// SQL-specific namespace walk shape (`
|
|
281
|
-
//
|
|
282
|
-
// `
|
|
283
|
-
//
|
|
284
|
-
// envelope variants that lose class identity.
|
|
340
|
+
// SQL-specific namespace walk shape (`entries.table` is the SQL family's
|
|
341
|
+
// idiom). The wider `object` table value keeps this helper structurally
|
|
342
|
+
// compatible with `SqlNamespace` and JSON envelope variants that lose class
|
|
343
|
+
// identity.
|
|
285
344
|
type NamespacedStorageWalk = {
|
|
286
345
|
readonly namespaces: Readonly<
|
|
287
|
-
Record<
|
|
346
|
+
Record<
|
|
347
|
+
string,
|
|
348
|
+
Namespace & { readonly entries: { readonly table: Readonly<Record<string, object>> } }
|
|
349
|
+
>
|
|
288
350
|
>;
|
|
289
351
|
};
|
|
290
352
|
|
|
291
353
|
function eachStorageTable(storage: NamespacedStorageWalk) {
|
|
292
354
|
return Object.entries(storage.namespaces).flatMap(([namespaceId, ns]) =>
|
|
293
|
-
Object.entries(ns.
|
|
355
|
+
Object.entries(ns.entries.table).map(([tableName, table]) => ({
|
|
294
356
|
namespaceId,
|
|
295
357
|
tableName,
|
|
296
358
|
table,
|
|
@@ -298,16 +360,6 @@ function eachStorageTable(storage: NamespacedStorageWalk) {
|
|
|
298
360
|
);
|
|
299
361
|
}
|
|
300
362
|
|
|
301
|
-
function findStorageTableByTableName(storage: NamespacedStorageWalk, tableName: string): unknown {
|
|
302
|
-
for (const ns of Object.values(storage.namespaces)) {
|
|
303
|
-
const t = ns.tables?.[tableName];
|
|
304
|
-
if (t !== undefined) {
|
|
305
|
-
return t;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return undefined;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
363
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
312
364
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
313
365
|
}
|
|
@@ -353,6 +405,7 @@ const ModelFieldSchema = type({
|
|
|
353
405
|
type: ContractFieldTypeSchema,
|
|
354
406
|
'many?': 'true',
|
|
355
407
|
'dict?': 'true',
|
|
408
|
+
'valueSet?': ValueSetRefSchema,
|
|
356
409
|
});
|
|
357
410
|
|
|
358
411
|
const ModelStorageFieldSchema = type({
|
|
@@ -363,20 +416,44 @@ const ModelStorageFieldSchema = type({
|
|
|
363
416
|
|
|
364
417
|
const ModelStorageSchema = type({
|
|
365
418
|
table: 'string',
|
|
419
|
+
namespaceId: 'string',
|
|
366
420
|
fields: type({ '[string]': ModelStorageFieldSchema }),
|
|
367
421
|
});
|
|
368
422
|
|
|
369
|
-
const
|
|
423
|
+
const ContractRelationThroughSchema = type({
|
|
424
|
+
'+': 'reject',
|
|
425
|
+
table: 'string',
|
|
426
|
+
namespaceId: 'string',
|
|
427
|
+
parentColumns: type.string.array().readonly(),
|
|
428
|
+
childColumns: type.string.array().readonly(),
|
|
429
|
+
targetColumns: type.string.array().readonly(),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const ContractRelationOnSchema = type({
|
|
433
|
+
'+': 'reject',
|
|
434
|
+
localFields: type.string.array().readonly(),
|
|
435
|
+
targetFields: type.string.array().readonly(),
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const ContractManyToManyRelationSchema = type({
|
|
439
|
+
'+': 'reject',
|
|
440
|
+
to: CrossReferenceSchema,
|
|
441
|
+
cardinality: "'N:M'",
|
|
442
|
+
on: ContractRelationOnSchema,
|
|
443
|
+
through: ContractRelationThroughSchema,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const ContractNonJunctionRelationSchema = type({
|
|
370
447
|
'+': 'reject',
|
|
371
448
|
to: CrossReferenceSchema,
|
|
372
449
|
cardinality: "'1:1' | '1:N' | 'N:1'",
|
|
373
|
-
on:
|
|
374
|
-
'+': 'reject',
|
|
375
|
-
localFields: type.string.array().readonly(),
|
|
376
|
-
targetFields: type.string.array().readonly(),
|
|
377
|
-
}),
|
|
450
|
+
on: ContractRelationOnSchema,
|
|
378
451
|
});
|
|
379
452
|
|
|
453
|
+
const ContractReferenceRelationSchema = ContractManyToManyRelationSchema.or(
|
|
454
|
+
ContractNonJunctionRelationSchema,
|
|
455
|
+
);
|
|
456
|
+
|
|
380
457
|
const ContractEmbedRelationSchema = type({
|
|
381
458
|
'+': 'reject',
|
|
382
459
|
to: CrossReferenceSchema,
|
|
@@ -417,12 +494,14 @@ export function createSqlContractSchema(
|
|
|
417
494
|
'capabilities?': 'Record<string, Record<string, boolean>>',
|
|
418
495
|
'extensionPacks?': 'Record<string, unknown>',
|
|
419
496
|
'meta?': ContractMetaSchema,
|
|
497
|
+
'defaultControlPolicy?': ControlPolicySchema,
|
|
420
498
|
'roots?': type({ '[string]': CrossReferenceSchema }),
|
|
421
499
|
domain: type({
|
|
422
500
|
namespaces: type({
|
|
423
501
|
'[string]': type({
|
|
424
502
|
models: type({ '[string]': ModelSchema }),
|
|
425
503
|
'valueObjects?': 'Record<string, unknown>',
|
|
504
|
+
'enum?': type({ '[string]': ContractEnumSchema }),
|
|
426
505
|
}),
|
|
427
506
|
}),
|
|
428
507
|
}),
|
|
@@ -557,6 +636,9 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
|
|
|
557
636
|
for (const fk of table.foreignKeys) {
|
|
558
637
|
registerNamedObject('foreign key', fk.name);
|
|
559
638
|
}
|
|
639
|
+
for (const check of table.checks ?? []) {
|
|
640
|
+
registerNamedObject('check constraint', check.name);
|
|
641
|
+
}
|
|
560
642
|
|
|
561
643
|
for (const [name, kinds] of namedObjects) {
|
|
562
644
|
if (kinds.length > 1) {
|
|
@@ -675,6 +757,18 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
|
|
|
675
757
|
}
|
|
676
758
|
}
|
|
677
759
|
}
|
|
760
|
+
|
|
761
|
+
const seenCheckDefinitions = new Set<string>();
|
|
762
|
+
for (const check of table.checks ?? []) {
|
|
763
|
+
const signature = JSON.stringify({ column: check.column, valueSet: check.valueSet });
|
|
764
|
+
if (seenCheckDefinitions.has(signature)) {
|
|
765
|
+
errors.push(
|
|
766
|
+
`Namespace "${namespaceId}" table "${tableName}": duplicate check constraint definition on column "${check.column}"`,
|
|
767
|
+
);
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
seenCheckDefinitions.add(signature);
|
|
771
|
+
}
|
|
678
772
|
}
|
|
679
773
|
|
|
680
774
|
return errors;
|
|
@@ -691,12 +785,19 @@ export function validateModelStorageReferences(contract: Contract<SqlStorage>):
|
|
|
691
785
|
const models = namespace.models as Record<string, ContractModel<SqlModelStorage>>;
|
|
692
786
|
for (const [modelName, model] of Object.entries(models)) {
|
|
693
787
|
const qualifiedName = `${namespaceId}:${modelName}`;
|
|
694
|
-
const
|
|
788
|
+
const storageNamespaceId = model.storage.namespaceId;
|
|
789
|
+
if (storageNamespaceId !== namespaceId) {
|
|
790
|
+
throw new ContractValidationError(
|
|
791
|
+
`Model "${qualifiedName}" storage.namespaceId "${storageNamespaceId}" does not match domain namespace "${namespaceId}"`,
|
|
792
|
+
'storage',
|
|
793
|
+
);
|
|
794
|
+
}
|
|
695
795
|
|
|
696
|
-
const
|
|
796
|
+
const storageTable = model.storage.table;
|
|
797
|
+
const rawTable = contract.storage.namespaces[storageNamespaceId]?.entries.table[storageTable];
|
|
697
798
|
if (rawTable === undefined) {
|
|
698
799
|
throw new ContractValidationError(
|
|
699
|
-
`Model "${qualifiedName}" references non-existent table "${storageTable}"`,
|
|
800
|
+
`Model "${qualifiedName}" references non-existent table "${storageNamespaceId}.${storageTable}"`,
|
|
700
801
|
'storage',
|
|
701
802
|
);
|
|
702
803
|
}
|
|
@@ -776,6 +877,15 @@ export function validateSqlStorageConsistency(contract: Contract<SqlStorage>): v
|
|
|
776
877
|
}
|
|
777
878
|
}
|
|
778
879
|
|
|
880
|
+
for (const check of table.checks ?? []) {
|
|
881
|
+
if (!columnNames.has(check.column)) {
|
|
882
|
+
throw new ContractValidationError(
|
|
883
|
+
`Namespace "${namespaceId}" table "${tableName}" check constraint "${check.name}" references non-existent column "${check.column}"`,
|
|
884
|
+
'storage',
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
779
889
|
for (const [colName, column] of Object.entries(table.columns)) {
|
|
780
890
|
if (!column.nullable && column.default?.kind === 'literal' && column.default.value === null) {
|
|
781
891
|
throw new ContractValidationError(
|
|
@@ -802,23 +912,25 @@ export function validateSqlStorageConsistency(contract: Contract<SqlStorage>): v
|
|
|
802
912
|
}
|
|
803
913
|
}
|
|
804
914
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
`Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent table "${fk.target.namespaceId}.${fk.target.tableName}"`,
|
|
810
|
-
'storage',
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
const referencedTable = referencedRaw as StorageTable;
|
|
814
|
-
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
815
|
-
for (const colName of fk.target.columns) {
|
|
816
|
-
if (!referencedColumnNames.has(colName)) {
|
|
915
|
+
if (fk.target.spaceId === undefined) {
|
|
916
|
+
const targetNamespace = contract.storage.namespaces[fk.target.namespaceId];
|
|
917
|
+
const referencedRaw = targetNamespace?.entries.table[fk.target.tableName];
|
|
918
|
+
if (referencedRaw === undefined) {
|
|
817
919
|
throw new ContractValidationError(
|
|
818
|
-
`Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent
|
|
920
|
+
`Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent table "${fk.target.namespaceId}.${fk.target.tableName}"`,
|
|
819
921
|
'storage',
|
|
820
922
|
);
|
|
821
923
|
}
|
|
924
|
+
const referencedTable = referencedRaw as StorageTable;
|
|
925
|
+
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
926
|
+
for (const colName of fk.target.columns) {
|
|
927
|
+
if (!referencedColumnNames.has(colName)) {
|
|
928
|
+
throw new ContractValidationError(
|
|
929
|
+
`Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.target.tableName}"`,
|
|
930
|
+
'storage',
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
822
934
|
}
|
|
823
935
|
|
|
824
936
|
if (fk.source.columns.length !== fk.target.columns.length) {
|