@prisma-next/sql-contract 0.12.0-dev.9 → 0.13.0-dev.1
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-CXf9xjAL.d.mts +314 -0
- package/dist/sql-storage-CXf9xjAL.d.mts.map +1 -0
- package/dist/types-DEnWD3xB.d.mts +208 -0
- package/dist/types-DEnWD3xB.d.mts.map +1 -0
- package/dist/{types-YQrDHy-b.mjs → types-DqhaAjCH.mjs} +107 -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 +50 -14
- package/dist/validators.d.mts.map +1 -1
- package/dist/validators.mjs +111 -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/sql-storage.ts +53 -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-type-instance.ts +5 -3
- package/src/ir/storage-value-set.ts +42 -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 +152 -47
- 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/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
|
@@ -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');
|
|
@@ -105,11 +114,11 @@ const StorageTypeInstanceSchema = type
|
|
|
105
114
|
kind: "'codec-instance'",
|
|
106
115
|
codecId: 'string',
|
|
107
116
|
nativeType: 'string',
|
|
108
|
-
typeParams: 'Record<string, unknown>',
|
|
117
|
+
'typeParams?': 'Record<string, unknown>',
|
|
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,20 +414,44 @@ 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
|
|
|
373
|
-
const
|
|
421
|
+
const ContractRelationThroughSchema = type({
|
|
422
|
+
'+': 'reject',
|
|
423
|
+
table: 'string',
|
|
424
|
+
namespaceId: 'string',
|
|
425
|
+
parentColumns: type.string.array().readonly(),
|
|
426
|
+
childColumns: type.string.array().readonly(),
|
|
427
|
+
targetColumns: type.string.array().readonly(),
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const ContractRelationOnSchema = type({
|
|
431
|
+
'+': 'reject',
|
|
432
|
+
localFields: type.string.array().readonly(),
|
|
433
|
+
targetFields: type.string.array().readonly(),
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const ContractManyToManyRelationSchema = type({
|
|
437
|
+
'+': 'reject',
|
|
438
|
+
to: CrossReferenceSchema,
|
|
439
|
+
cardinality: "'N:M'",
|
|
440
|
+
on: ContractRelationOnSchema,
|
|
441
|
+
through: ContractRelationThroughSchema,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const ContractNonJunctionRelationSchema = type({
|
|
374
445
|
'+': 'reject',
|
|
375
446
|
to: CrossReferenceSchema,
|
|
376
447
|
cardinality: "'1:1' | '1:N' | 'N:1'",
|
|
377
|
-
on:
|
|
378
|
-
'+': 'reject',
|
|
379
|
-
localFields: type.string.array().readonly(),
|
|
380
|
-
targetFields: type.string.array().readonly(),
|
|
381
|
-
}),
|
|
448
|
+
on: ContractRelationOnSchema,
|
|
382
449
|
});
|
|
383
450
|
|
|
451
|
+
const ContractReferenceRelationSchema = ContractManyToManyRelationSchema.or(
|
|
452
|
+
ContractNonJunctionRelationSchema,
|
|
453
|
+
);
|
|
454
|
+
|
|
384
455
|
const ContractEmbedRelationSchema = type({
|
|
385
456
|
'+': 'reject',
|
|
386
457
|
to: CrossReferenceSchema,
|
|
@@ -421,13 +492,14 @@ export function createSqlContractSchema(
|
|
|
421
492
|
'capabilities?': 'Record<string, Record<string, boolean>>',
|
|
422
493
|
'extensionPacks?': 'Record<string, unknown>',
|
|
423
494
|
'meta?': ContractMetaSchema,
|
|
424
|
-
'
|
|
495
|
+
'defaultControlPolicy?': ControlPolicySchema,
|
|
425
496
|
'roots?': type({ '[string]': CrossReferenceSchema }),
|
|
426
497
|
domain: type({
|
|
427
498
|
namespaces: type({
|
|
428
499
|
'[string]': type({
|
|
429
500
|
models: type({ '[string]': ModelSchema }),
|
|
430
501
|
'valueObjects?': 'Record<string, unknown>',
|
|
502
|
+
'enum?': type({ '[string]': ContractEnumSchema }),
|
|
431
503
|
}),
|
|
432
504
|
}),
|
|
433
505
|
}),
|
|
@@ -562,6 +634,9 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
|
|
|
562
634
|
for (const fk of table.foreignKeys) {
|
|
563
635
|
registerNamedObject('foreign key', fk.name);
|
|
564
636
|
}
|
|
637
|
+
for (const check of table.checks ?? []) {
|
|
638
|
+
registerNamedObject('check constraint', check.name);
|
|
639
|
+
}
|
|
565
640
|
|
|
566
641
|
for (const [name, kinds] of namedObjects) {
|
|
567
642
|
if (kinds.length > 1) {
|
|
@@ -680,6 +755,18 @@ export function validateStorageSemantics(storage: SqlStorage): string[] {
|
|
|
680
755
|
}
|
|
681
756
|
}
|
|
682
757
|
}
|
|
758
|
+
|
|
759
|
+
const seenCheckDefinitions = new Set<string>();
|
|
760
|
+
for (const check of table.checks ?? []) {
|
|
761
|
+
const signature = JSON.stringify({ column: check.column, valueSet: check.valueSet });
|
|
762
|
+
if (seenCheckDefinitions.has(signature)) {
|
|
763
|
+
errors.push(
|
|
764
|
+
`Namespace "${namespaceId}" table "${tableName}": duplicate check constraint definition on column "${check.column}"`,
|
|
765
|
+
);
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
seenCheckDefinitions.add(signature);
|
|
769
|
+
}
|
|
683
770
|
}
|
|
684
771
|
|
|
685
772
|
return errors;
|
|
@@ -696,12 +783,19 @@ export function validateModelStorageReferences(contract: Contract<SqlStorage>):
|
|
|
696
783
|
const models = namespace.models as Record<string, ContractModel<SqlModelStorage>>;
|
|
697
784
|
for (const [modelName, model] of Object.entries(models)) {
|
|
698
785
|
const qualifiedName = `${namespaceId}:${modelName}`;
|
|
699
|
-
const
|
|
786
|
+
const storageNamespaceId = model.storage.namespaceId;
|
|
787
|
+
if (storageNamespaceId !== namespaceId) {
|
|
788
|
+
throw new ContractValidationError(
|
|
789
|
+
`Model "${qualifiedName}" storage.namespaceId "${storageNamespaceId}" does not match domain namespace "${namespaceId}"`,
|
|
790
|
+
'storage',
|
|
791
|
+
);
|
|
792
|
+
}
|
|
700
793
|
|
|
701
|
-
const
|
|
794
|
+
const storageTable = model.storage.table;
|
|
795
|
+
const rawTable = contract.storage.namespaces[storageNamespaceId]?.entries.table[storageTable];
|
|
702
796
|
if (rawTable === undefined) {
|
|
703
797
|
throw new ContractValidationError(
|
|
704
|
-
`Model "${qualifiedName}" references non-existent table "${storageTable}"`,
|
|
798
|
+
`Model "${qualifiedName}" references non-existent table "${storageNamespaceId}.${storageTable}"`,
|
|
705
799
|
'storage',
|
|
706
800
|
);
|
|
707
801
|
}
|
|
@@ -781,6 +875,15 @@ export function validateSqlStorageConsistency(contract: Contract<SqlStorage>): v
|
|
|
781
875
|
}
|
|
782
876
|
}
|
|
783
877
|
|
|
878
|
+
for (const check of table.checks ?? []) {
|
|
879
|
+
if (!columnNames.has(check.column)) {
|
|
880
|
+
throw new ContractValidationError(
|
|
881
|
+
`Namespace "${namespaceId}" table "${tableName}" check constraint "${check.name}" references non-existent column "${check.column}"`,
|
|
882
|
+
'storage',
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
784
887
|
for (const [colName, column] of Object.entries(table.columns)) {
|
|
785
888
|
if (!column.nullable && column.default?.kind === 'literal' && column.default.value === null) {
|
|
786
889
|
throw new ContractValidationError(
|
|
@@ -807,23 +910,25 @@ export function validateSqlStorageConsistency(contract: Contract<SqlStorage>): v
|
|
|
807
910
|
}
|
|
808
911
|
}
|
|
809
912
|
|
|
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)) {
|
|
913
|
+
if (fk.target.spaceId === undefined) {
|
|
914
|
+
const targetNamespace = contract.storage.namespaces[fk.target.namespaceId];
|
|
915
|
+
const referencedRaw = targetNamespace?.entries.table[fk.target.tableName];
|
|
916
|
+
if (referencedRaw === undefined) {
|
|
822
917
|
throw new ContractValidationError(
|
|
823
|
-
`Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent
|
|
918
|
+
`Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent table "${fk.target.namespaceId}.${fk.target.tableName}"`,
|
|
824
919
|
'storage',
|
|
825
920
|
);
|
|
826
921
|
}
|
|
922
|
+
const referencedTable = referencedRaw as StorageTable;
|
|
923
|
+
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
924
|
+
for (const colName of fk.target.columns) {
|
|
925
|
+
if (!referencedColumnNames.has(colName)) {
|
|
926
|
+
throw new ContractValidationError(
|
|
927
|
+
`Namespace "${namespaceId}" table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.target.tableName}"`,
|
|
928
|
+
'storage',
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
827
932
|
}
|
|
828
933
|
|
|
829
934
|
if (fk.source.columns.length !== fk.target.columns.length) {
|