@prisma-next/family-sql 0.12.0-dev.5 → 0.12.0-dev.51
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/control-adapter-BnNdlGKS.d.mts +172 -0
- package/dist/control-adapter-BnNdlGKS.d.mts.map +1 -0
- package/dist/control-adapter.d.mts +2 -109
- package/dist/control.d.mts +118 -4
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +225 -40
- package/dist/control.mjs.map +1 -1
- package/dist/ir.d.mts +2 -2
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/runtime.d.mts +4 -2
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +3 -1
- package/dist/runtime.mjs.map +1 -1
- package/dist/schema-verify.d.mts +1 -0
- package/dist/schema-verify.d.mts.map +1 -1
- package/dist/schema-verify.mjs +1 -1
- package/dist/{sql-contract-serializer-8axtK4lg.mjs → sql-contract-serializer-nNw1yk9P.mjs} +14 -35
- package/dist/sql-contract-serializer-nNw1yk9P.mjs.map +1 -0
- package/dist/{types-CeeCStqw.d.mts → types-CfJQKaMJ.d.mts} +69 -15
- package/dist/types-CfJQKaMJ.d.mts.map +1 -0
- package/dist/verify-sql-schema-CN7pPoTC.d.mts.map +1 -1
- package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-CYlKme0a.mjs} +400 -317
- package/dist/verify-sql-schema-CYlKme0a.mjs.map +1 -0
- package/package.json +21 -21
- package/src/core/control-adapter.ts +105 -7
- package/src/core/control-instance.ts +151 -66
- package/src/core/default-namespace.ts +9 -0
- package/src/core/ir/sql-contract-serializer-base.ts +42 -57
- package/src/core/migrations/contract-to-schema-ir.ts +12 -8
- package/src/core/migrations/control-policy.ts +322 -0
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/plan-helpers.ts +16 -0
- package/src/core/migrations/types.ts +16 -6
- package/src/core/schema-verify/control-verify-emit.ts +46 -0
- package/src/core/schema-verify/verifier-disposition.ts +53 -0
- package/src/core/schema-verify/verify-helpers.ts +151 -110
- package/src/core/schema-verify/verify-sql-schema.ts +281 -178
- package/src/exports/control.ts +6 -0
- package/src/exports/runtime.ts +7 -0
- package/dist/control-adapter.d.mts.map +0 -1
- package/dist/sql-contract-serializer-8axtK4lg.mjs.map +0 -1
- package/dist/types-CeeCStqw.d.mts.map +0 -1
- package/dist/verify-sql-schema-CYLsGCFO.mjs.map +0 -1
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Contract,
|
|
3
|
+
ContractMarkerRecord,
|
|
4
|
+
LedgerEntryRecord,
|
|
5
|
+
} from '@prisma-next/contract/types';
|
|
2
6
|
import type {
|
|
3
7
|
TargetBoundComponentDescriptor,
|
|
4
8
|
TargetDescriptor,
|
|
5
9
|
} from '@prisma-next/framework-components/components';
|
|
6
10
|
import type {
|
|
7
|
-
ControlDriverInstance,
|
|
8
11
|
ControlFamilyInstance,
|
|
9
12
|
ControlStack,
|
|
10
13
|
CoreSchemaView,
|
|
@@ -28,17 +31,13 @@ import type { TypesImportSpec } from '@prisma-next/framework-components/emission
|
|
|
28
31
|
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
29
32
|
import { assertDescriptorSelfConsistency } from '@prisma-next/migration-tools/spaces';
|
|
30
33
|
import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';
|
|
31
|
-
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
34
|
+
import type { SqlControlDriverInstance, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
32
35
|
import type {
|
|
33
36
|
AnyQueryAst,
|
|
37
|
+
DdlNode,
|
|
34
38
|
LoweredStatement,
|
|
35
39
|
LowererContext,
|
|
36
40
|
} from '@prisma-next/sql-relational-core/ast';
|
|
37
|
-
import {
|
|
38
|
-
ensureSchemaStatement,
|
|
39
|
-
ensureTableStatement,
|
|
40
|
-
writeContractMarker,
|
|
41
|
-
} from '@prisma-next/sql-runtime';
|
|
42
41
|
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
43
42
|
import type { SqlSchemaIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
44
43
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
@@ -69,10 +68,10 @@ function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] {
|
|
|
69
68
|
) {
|
|
70
69
|
const namespaces = contract.storage.namespaces as Record<
|
|
71
70
|
string,
|
|
72
|
-
{ readonly
|
|
71
|
+
{ readonly entries: { readonly table: Readonly<Record<string, unknown>> } }
|
|
73
72
|
>;
|
|
74
73
|
for (const ns of Object.values(namespaces)) {
|
|
75
|
-
const tbls = ns.
|
|
74
|
+
const tbls = ns.entries.table;
|
|
76
75
|
if (typeof tbls !== 'object' || tbls === null) continue;
|
|
77
76
|
for (const table of Object.values(tbls)) {
|
|
78
77
|
if (
|
|
@@ -203,7 +202,7 @@ export interface SqlControlFamilyInstance
|
|
|
203
202
|
deserializeContract(contractJson: unknown): Contract;
|
|
204
203
|
|
|
205
204
|
verify(options: {
|
|
206
|
-
readonly driver:
|
|
205
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
207
206
|
readonly contract: unknown;
|
|
208
207
|
readonly expectedTargetId: string;
|
|
209
208
|
readonly contractPath: string;
|
|
@@ -228,43 +227,77 @@ export interface SqlControlFamilyInstance
|
|
|
228
227
|
}): VerifyDatabaseSchemaResult;
|
|
229
228
|
|
|
230
229
|
sign(options: {
|
|
231
|
-
readonly driver:
|
|
230
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
232
231
|
readonly contract: unknown;
|
|
233
232
|
readonly contractPath: string;
|
|
234
233
|
readonly configPath?: string;
|
|
235
234
|
}): Promise<SignDatabaseResult>;
|
|
236
235
|
|
|
237
236
|
introspect(options: {
|
|
238
|
-
readonly driver:
|
|
237
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
239
238
|
readonly contract?: unknown;
|
|
240
239
|
}): Promise<SqlSchemaIR>;
|
|
241
240
|
|
|
242
241
|
inferPslContract(schemaIR: SqlSchemaIR): PslDocumentAst;
|
|
243
242
|
|
|
244
|
-
lowerAst(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement;
|
|
243
|
+
lowerAst(ast: AnyQueryAst | DdlNode, context: LowererContext<unknown>): LoweredStatement;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Inserts the initial marker row for `space` (upsert on `space`).
|
|
247
|
+
* Delegates to the target control adapter's write SPI; see
|
|
248
|
+
* `SqlControlAdapter.initMarker`.
|
|
249
|
+
*/
|
|
250
|
+
initMarker(options: {
|
|
251
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
252
|
+
readonly space: string;
|
|
253
|
+
readonly destination: {
|
|
254
|
+
readonly storageHash: string;
|
|
255
|
+
readonly profileHash: string;
|
|
256
|
+
readonly invariants?: readonly string[];
|
|
257
|
+
};
|
|
258
|
+
}): Promise<void>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Compare-and-swap advance of the marker row for `space`. Returns `true`
|
|
262
|
+
* when the swap matched a row; see `SqlControlAdapter.updateMarker`.
|
|
263
|
+
*/
|
|
264
|
+
updateMarker(options: {
|
|
265
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
266
|
+
readonly space: string;
|
|
267
|
+
readonly expectedFrom: string;
|
|
268
|
+
readonly destination: {
|
|
269
|
+
readonly storageHash: string;
|
|
270
|
+
readonly profileHash: string;
|
|
271
|
+
readonly invariants?: readonly string[];
|
|
272
|
+
};
|
|
273
|
+
}): Promise<boolean>;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Appends a ledger entry for `space`; see
|
|
277
|
+
* `SqlControlAdapter.writeLedgerEntry`.
|
|
278
|
+
*/
|
|
279
|
+
writeLedgerEntry(options: {
|
|
280
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
281
|
+
readonly space: string;
|
|
282
|
+
readonly entry: {
|
|
283
|
+
readonly edgeId: string;
|
|
284
|
+
readonly from: string;
|
|
285
|
+
readonly to: string;
|
|
286
|
+
readonly migrationName: string;
|
|
287
|
+
readonly migrationHash: string;
|
|
288
|
+
readonly operations: readonly unknown[];
|
|
289
|
+
};
|
|
290
|
+
}): Promise<void>;
|
|
291
|
+
|
|
292
|
+
bootstrapControlTableQueries(): readonly DdlNode[];
|
|
293
|
+
|
|
294
|
+
bootstrapSignMarkerQueries(): readonly DdlNode[];
|
|
245
295
|
|
|
246
296
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview;
|
|
247
297
|
}
|
|
248
298
|
|
|
249
299
|
export type SqlFamilyInstance = SqlControlFamilyInstance;
|
|
250
300
|
|
|
251
|
-
function isSqlControlAdapter<TTargetId extends string>(
|
|
252
|
-
value: unknown,
|
|
253
|
-
): value is SqlControlAdapter<TTargetId> {
|
|
254
|
-
return (
|
|
255
|
-
typeof value === 'object' &&
|
|
256
|
-
value !== null &&
|
|
257
|
-
'introspect' in value &&
|
|
258
|
-
typeof (value as { introspect: unknown }).introspect === 'function' &&
|
|
259
|
-
'readMarker' in value &&
|
|
260
|
-
typeof (value as { readMarker: unknown }).readMarker === 'function' &&
|
|
261
|
-
'readAllMarkers' in value &&
|
|
262
|
-
typeof (value as { readAllMarkers: unknown }).readAllMarkers === 'function' &&
|
|
263
|
-
'lower' in value &&
|
|
264
|
-
typeof (value as { lower: unknown }).lower === 'function'
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
301
|
interface DescriptorWithStorageTypes {
|
|
269
302
|
readonly targetId?: string | undefined;
|
|
270
303
|
readonly types?:
|
|
@@ -361,19 +394,11 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
361
394
|
extensionPacks: extensions,
|
|
362
395
|
});
|
|
363
396
|
|
|
364
|
-
// Family-instance methods accept `
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
const getControlAdapter = () =>
|
|
369
|
-
const controlAdapter = adapter.create(stack);
|
|
370
|
-
if (!isSqlControlAdapter(controlAdapter)) {
|
|
371
|
-
throw new Error(
|
|
372
|
-
'Adapter does not implement SqlControlAdapter (missing introspect, readMarker, or readAllMarkers)',
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
return controlAdapter;
|
|
376
|
-
};
|
|
397
|
+
// Family-instance methods accept `SqlControlDriverInstance<string>` — the
|
|
398
|
+
// family API isn't generic on the target id. The adapter descriptor's `create`
|
|
399
|
+
// returns the concrete `SqlControlAdapter<TTargetId>`; widening the target id to
|
|
400
|
+
// `string` here matches the family-level driver type without a per-method probe.
|
|
401
|
+
const getControlAdapter = (): SqlControlAdapter<string> => adapter.create(stack);
|
|
377
402
|
|
|
378
403
|
const targetSerializer = (
|
|
379
404
|
target as unknown as {
|
|
@@ -396,7 +421,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
396
421
|
},
|
|
397
422
|
|
|
398
423
|
async verify(verifyOptions: {
|
|
399
|
-
readonly driver:
|
|
424
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
400
425
|
readonly contract: unknown;
|
|
401
426
|
readonly expectedTargetId: string;
|
|
402
427
|
readonly contractPath: string;
|
|
@@ -544,7 +569,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
544
569
|
});
|
|
545
570
|
},
|
|
546
571
|
async sign(options: {
|
|
547
|
-
readonly driver:
|
|
572
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
548
573
|
readonly contract: unknown;
|
|
549
574
|
readonly contractPath: string;
|
|
550
575
|
readonly configPath?: string;
|
|
@@ -561,24 +586,24 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
561
586
|
: contractStorageHash;
|
|
562
587
|
const contractTarget = contract.target;
|
|
563
588
|
|
|
564
|
-
|
|
565
|
-
|
|
589
|
+
const controlAdapter = getControlAdapter();
|
|
590
|
+
const lowererContext = { contract };
|
|
591
|
+
for (const query of controlAdapter.bootstrapSignMarkerQueries()) {
|
|
592
|
+
const lowered = controlAdapter.lower(query, lowererContext);
|
|
593
|
+
await driver.query(lowered.sql, lowered.params);
|
|
594
|
+
}
|
|
566
595
|
|
|
567
|
-
const existingMarker = await
|
|
596
|
+
const existingMarker = await controlAdapter.readMarker(driver, APP_SPACE_ID);
|
|
568
597
|
|
|
569
598
|
let markerCreated = false;
|
|
570
599
|
let markerUpdated = false;
|
|
571
600
|
let previousHashes: { storageHash?: string; profileHash?: string } | undefined;
|
|
572
601
|
|
|
573
602
|
if (!existingMarker) {
|
|
574
|
-
|
|
575
|
-
space: APP_SPACE_ID,
|
|
603
|
+
await controlAdapter.insertMarker(driver, APP_SPACE_ID, {
|
|
576
604
|
storageHash: contractStorageHash,
|
|
577
605
|
profileHash: contractProfileHash,
|
|
578
|
-
contractJson: contractInput,
|
|
579
|
-
canonicalVersion: 1,
|
|
580
606
|
});
|
|
581
|
-
await driver.query(write.insert.sql, write.insert.params);
|
|
582
607
|
markerCreated = true;
|
|
583
608
|
} else {
|
|
584
609
|
const existingStorageHash = existingMarker.storageHash;
|
|
@@ -592,14 +617,18 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
592
617
|
storageHash: existingStorageHash,
|
|
593
618
|
profileHash: existingProfileHash,
|
|
594
619
|
};
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
620
|
+
const updated = await controlAdapter.updateMarker(
|
|
621
|
+
driver,
|
|
622
|
+
APP_SPACE_ID,
|
|
623
|
+
existingStorageHash,
|
|
624
|
+
{
|
|
625
|
+
storageHash: contractStorageHash,
|
|
626
|
+
profileHash: contractProfileHash,
|
|
627
|
+
},
|
|
628
|
+
);
|
|
629
|
+
if (!updated) {
|
|
630
|
+
throw new Error('CAS conflict: marker was modified by another process during sign');
|
|
631
|
+
}
|
|
603
632
|
markerUpdated = true;
|
|
604
633
|
}
|
|
605
634
|
}
|
|
@@ -641,18 +670,66 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
641
670
|
};
|
|
642
671
|
},
|
|
643
672
|
async readMarker(options: {
|
|
644
|
-
readonly driver:
|
|
673
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
645
674
|
readonly space: string;
|
|
646
675
|
}): Promise<ContractMarkerRecord | null> {
|
|
647
676
|
return getControlAdapter().readMarker(options.driver, options.space);
|
|
648
677
|
},
|
|
649
678
|
async readAllMarkers(options: {
|
|
650
|
-
readonly driver:
|
|
679
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
651
680
|
}): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
652
681
|
return getControlAdapter().readAllMarkers(options.driver);
|
|
653
682
|
},
|
|
683
|
+
async readLedger(options: {
|
|
684
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
685
|
+
readonly space?: string;
|
|
686
|
+
}): Promise<readonly LedgerEntryRecord[]> {
|
|
687
|
+
return getControlAdapter().readLedger(options.driver, options.space);
|
|
688
|
+
},
|
|
689
|
+
async initMarker(options: {
|
|
690
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
691
|
+
readonly space: string;
|
|
692
|
+
readonly destination: {
|
|
693
|
+
readonly storageHash: string;
|
|
694
|
+
readonly profileHash: string;
|
|
695
|
+
readonly invariants?: readonly string[];
|
|
696
|
+
};
|
|
697
|
+
}): Promise<void> {
|
|
698
|
+
return getControlAdapter().initMarker(options.driver, options.space, options.destination);
|
|
699
|
+
},
|
|
700
|
+
async updateMarker(options: {
|
|
701
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
702
|
+
readonly space: string;
|
|
703
|
+
readonly expectedFrom: string;
|
|
704
|
+
readonly destination: {
|
|
705
|
+
readonly storageHash: string;
|
|
706
|
+
readonly profileHash: string;
|
|
707
|
+
readonly invariants?: readonly string[];
|
|
708
|
+
};
|
|
709
|
+
}): Promise<boolean> {
|
|
710
|
+
return getControlAdapter().updateMarker(
|
|
711
|
+
options.driver,
|
|
712
|
+
options.space,
|
|
713
|
+
options.expectedFrom,
|
|
714
|
+
options.destination,
|
|
715
|
+
);
|
|
716
|
+
},
|
|
717
|
+
async writeLedgerEntry(options: {
|
|
718
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
719
|
+
readonly space: string;
|
|
720
|
+
readonly entry: {
|
|
721
|
+
readonly edgeId: string;
|
|
722
|
+
readonly from: string;
|
|
723
|
+
readonly to: string;
|
|
724
|
+
readonly migrationName: string;
|
|
725
|
+
readonly migrationHash: string;
|
|
726
|
+
readonly operations: readonly unknown[];
|
|
727
|
+
};
|
|
728
|
+
}): Promise<void> {
|
|
729
|
+
return getControlAdapter().writeLedgerEntry(options.driver, options.space, options.entry);
|
|
730
|
+
},
|
|
654
731
|
async introspect(options: {
|
|
655
|
-
readonly driver:
|
|
732
|
+
readonly driver: SqlControlDriverInstance<string>;
|
|
656
733
|
readonly contract?: unknown;
|
|
657
734
|
}): Promise<SqlSchemaIR> {
|
|
658
735
|
return getControlAdapter().introspect(options.driver, options.contract);
|
|
@@ -662,10 +739,18 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
662
739
|
return sqlSchemaIrToPslAst(schemaIR);
|
|
663
740
|
},
|
|
664
741
|
|
|
665
|
-
lowerAst(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement {
|
|
742
|
+
lowerAst(ast: AnyQueryAst | DdlNode, context: LowererContext<unknown>): LoweredStatement {
|
|
666
743
|
return getControlAdapter().lower(ast, context);
|
|
667
744
|
},
|
|
668
745
|
|
|
746
|
+
bootstrapControlTableQueries(): readonly DdlNode[] {
|
|
747
|
+
return getControlAdapter().bootstrapControlTableQueries();
|
|
748
|
+
},
|
|
749
|
+
|
|
750
|
+
bootstrapSignMarkerQueries(): readonly DdlNode[] {
|
|
751
|
+
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
752
|
+
},
|
|
753
|
+
|
|
669
754
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
|
|
670
755
|
return sqlOperationsToPreview(operations);
|
|
671
756
|
},
|
|
@@ -29,16 +29,16 @@ import { type Type, type } from 'arktype';
|
|
|
29
29
|
const NamespaceRawSchema = type({
|
|
30
30
|
id: 'string',
|
|
31
31
|
'kind?': 'string',
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
entries: type({
|
|
33
|
+
'+': 'ignore',
|
|
34
|
+
}),
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
38
38
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export type SqlEntityHydrationFactory = (entry: unknown) =>
|
|
41
|
+
export type SqlEntityHydrationFactory = (entry: unknown) => unknown;
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* SQL family `ContractSerializer` abstract base. Carries the SQL-shared
|
|
@@ -70,7 +70,10 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
70
70
|
private readonly contractSchema: Type<unknown> | undefined;
|
|
71
71
|
|
|
72
72
|
constructor(
|
|
73
|
-
|
|
73
|
+
protected readonly entityTypeRegistry: ReadonlyMap<
|
|
74
|
+
string,
|
|
75
|
+
SqlEntityHydrationFactory
|
|
76
|
+
> = new Map(),
|
|
74
77
|
validatorFragments?: ReadonlyMap<string, Type<unknown>>,
|
|
75
78
|
) {
|
|
76
79
|
// Only build a fragments-aware contract schema when pack contributions
|
|
@@ -158,7 +161,12 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
158
161
|
const namespaceMaterialised =
|
|
159
162
|
namespaceHydrated instanceof NamespaceBase
|
|
160
163
|
? namespaceHydrated
|
|
161
|
-
: buildSqlNamespace(
|
|
164
|
+
: buildSqlNamespace(
|
|
165
|
+
blindCast<
|
|
166
|
+
SqlNamespaceTablesInput,
|
|
167
|
+
'hydrateSqlNamespaceEntry returns SqlNamespaceTablesInput when raw is not a NamespaceBase'
|
|
168
|
+
>(namespaceHydrated),
|
|
169
|
+
);
|
|
162
170
|
return [nsId, namespaceMaterialised];
|
|
163
171
|
}),
|
|
164
172
|
);
|
|
@@ -172,69 +180,43 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
172
180
|
return raw;
|
|
173
181
|
}
|
|
174
182
|
const rawRecord = isPlainRecord(raw) ? raw : {};
|
|
183
|
+
if (
|
|
184
|
+
Object.hasOwn(rawRecord, 'tables') ||
|
|
185
|
+
Object.hasOwn(rawRecord, 'enum') ||
|
|
186
|
+
Object.hasOwn(rawRecord, 'collections')
|
|
187
|
+
) {
|
|
188
|
+
throw new ContractValidationError(
|
|
189
|
+
'Namespace envelope uses deprecated flat slot keys; expected `entries: { table? }`',
|
|
190
|
+
'structural',
|
|
191
|
+
);
|
|
192
|
+
}
|
|
175
193
|
const id = typeof rawRecord['id'] === 'string' ? rawRecord['id'] : nsId;
|
|
176
194
|
const parsed = NamespaceRawSchema({ ...rawRecord, id });
|
|
177
195
|
if (parsed instanceof type.errors) {
|
|
178
196
|
const messages = parsed.map((p: { message: string }) => p.message).join('; ');
|
|
179
197
|
throw new ContractValidationError(`Namespace hydration failed: ${messages}`, 'structural');
|
|
180
198
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
Object.entries(slotValue as Record<string, unknown>).map(([tableName, table]) => [
|
|
199
|
+
// Default to empty table; overwritten below if raw entries carry a table slot.
|
|
200
|
+
const entriesInput: { table: Record<string, StorageTable> } = { table: {} };
|
|
201
|
+
const entriesRaw = parsed.entries;
|
|
202
|
+
if (entriesRaw !== undefined && typeof entriesRaw === 'object' && entriesRaw !== null) {
|
|
203
|
+
const tableSlot = (entriesRaw as Record<string, unknown>)['table'];
|
|
204
|
+
if (tableSlot !== null && typeof tableSlot === 'object' && !Array.isArray(tableSlot)) {
|
|
205
|
+
entriesInput.table = Object.fromEntries(
|
|
206
|
+
Object.entries(tableSlot as Record<string, unknown>).map(([tableName, table]) => [
|
|
190
207
|
tableName,
|
|
191
208
|
table instanceof StorageTable ? table : new StorageTable(table as StorageTableInput),
|
|
192
209
|
]),
|
|
193
210
|
);
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const hydratedSlot = Object.fromEntries(
|
|
198
|
-
Object.entries(slotValue as Record<string, unknown>).map(([entryName, entry]) => {
|
|
199
|
-
if (typeof entry !== 'object' || entry === null) {
|
|
200
|
-
return [entryName, entry];
|
|
201
|
-
}
|
|
202
|
-
const kind = (entry as { kind?: unknown }).kind;
|
|
203
|
-
if (typeof kind === 'string') {
|
|
204
|
-
const factory = this.entityTypeRegistry.get(kind);
|
|
205
|
-
if (factory !== undefined) {
|
|
206
|
-
return [entryName, factory(entry)];
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return [entryName, entry];
|
|
210
|
-
}),
|
|
211
|
-
);
|
|
212
|
-
if (Object.keys(hydratedSlot).length > 0) {
|
|
213
|
-
result[propertyKey] = hydratedSlot;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const enumRaw = rawRecord['enum'];
|
|
218
|
-
if (enumRaw !== undefined && typeof enumRaw === 'object' && enumRaw !== null) {
|
|
219
|
-
for (const entry of Object.values(enumRaw as Record<string, unknown>)) {
|
|
220
|
-
if (typeof entry !== 'object' || entry === null) continue;
|
|
221
|
-
const kind = (entry as { kind?: unknown }).kind;
|
|
222
|
-
if (typeof kind === 'string' && this.entityTypeRegistry.get(kind) === undefined) {
|
|
223
|
-
throw new ContractValidationError(
|
|
224
|
-
`Entry kind '${kind}' has no registered hydration factory.`,
|
|
225
|
-
'structural',
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
211
|
}
|
|
212
|
+
// Target-specific slots (e.g. postgres `type`) are left for target
|
|
213
|
+
// overrides to extract from the original `raw` parameter.
|
|
229
214
|
}
|
|
230
215
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
tables,
|
|
236
|
-
...(enumSlot !== undefined ? { enum: enumSlot } : {}),
|
|
237
|
-
} as SqlNamespaceTablesInput;
|
|
216
|
+
return blindCast<SqlNamespaceTablesInput, 'hydrated namespace tables input'>({
|
|
217
|
+
id,
|
|
218
|
+
entries: entriesInput,
|
|
219
|
+
});
|
|
238
220
|
}
|
|
239
221
|
|
|
240
222
|
protected hydrateStorageTypeEntry(entry: SqlStorageTypeEntry): SqlStorageTypeEntry {
|
|
@@ -249,7 +231,10 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
249
231
|
if (factory === undefined) {
|
|
250
232
|
return entry;
|
|
251
233
|
}
|
|
252
|
-
return
|
|
234
|
+
return blindCast<
|
|
235
|
+
SqlStorageTypeEntry,
|
|
236
|
+
'entity registry factory returns SqlStorageTypeEntry for storage.types entries'
|
|
237
|
+
>(factory(entry));
|
|
253
238
|
}
|
|
254
239
|
|
|
255
240
|
protected constructTargetContract(hydrated: Contract<SqlStorage>): TContract {
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
SqlTableIR,
|
|
25
25
|
SqlUniqueIR,
|
|
26
26
|
} from '@prisma-next/sql-schema-ir/types';
|
|
27
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
27
28
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -250,11 +251,11 @@ export function detectDestructiveChanges(
|
|
|
250
251
|
for (const namespaceId of namespaceIds) {
|
|
251
252
|
const fromNs = from.namespaces[namespaceId];
|
|
252
253
|
const toNs = to.namespaces[namespaceId];
|
|
253
|
-
const fromTables = fromNs?.
|
|
254
|
+
const fromTables = fromNs?.entries.table;
|
|
254
255
|
if (!fromTables) continue;
|
|
255
256
|
|
|
256
257
|
for (const tableName of Object.keys(fromTables)) {
|
|
257
|
-
const toTableRaw = toNs?.
|
|
258
|
+
const toTableRaw = toNs?.entries.table[tableName];
|
|
258
259
|
if (!(toTableRaw instanceof StorageTable)) {
|
|
259
260
|
conflicts.push({
|
|
260
261
|
kind: 'tableRemoved',
|
|
@@ -327,20 +328,23 @@ export function contractToSchemaIR(
|
|
|
327
328
|
...((storage.types ?? {}) as ResolvedStorageTypes),
|
|
328
329
|
};
|
|
329
330
|
for (const ns of Object.values(storage.namespaces)) {
|
|
330
|
-
const nsEnums =
|
|
331
|
+
const nsEnums = ns.entries['type'];
|
|
331
332
|
if (nsEnums) {
|
|
332
333
|
for (const [k, v] of Object.entries(nsEnums)) {
|
|
333
|
-
allTypes[k] =
|
|
334
|
+
allTypes[k] = blindCast<
|
|
335
|
+
PostgresEnumStorageEntry | StorageTypeInstance,
|
|
336
|
+
'entries.type holds postgres-specific enum entries at runtime'
|
|
337
|
+
>(v);
|
|
334
338
|
}
|
|
335
339
|
}
|
|
336
340
|
}
|
|
337
341
|
const storageTypes = allTypes as ResolvedStorageTypes;
|
|
338
342
|
const tables: Record<string, SqlTableIR> = {};
|
|
339
343
|
for (const ns of Object.values(storage.namespaces)) {
|
|
340
|
-
for (const [tableName, tableDefRaw] of Object.entries(ns.
|
|
344
|
+
for (const [tableName, tableDefRaw] of Object.entries(ns.entries.table)) {
|
|
341
345
|
if (!(tableDefRaw instanceof StorageTable)) {
|
|
342
346
|
throw new Error(
|
|
343
|
-
`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.
|
|
347
|
+
`contractToSchemaIR: expected StorageTable at namespaces.${ns.id}.entries.table.${tableName}`,
|
|
344
348
|
);
|
|
345
349
|
}
|
|
346
350
|
const tableDef = tableDefRaw;
|
|
@@ -395,7 +399,7 @@ function deriveAnnotations(
|
|
|
395
399
|
|
|
396
400
|
// Top-level `storage.types`: codec-typed entries (vector, decimal, …) keyed
|
|
397
401
|
// by bare `nativeType` (unchanged). Post-S1.B enums live in
|
|
398
|
-
// `namespaces[*].
|
|
402
|
+
// `namespaces[*].entries.type`, not here; a defensive top-level enum is still
|
|
399
403
|
// namespace/schema-qualified via the resolver under the unbound coordinate
|
|
400
404
|
// so it never collides on a bare name.
|
|
401
405
|
for (const typeInstance of Object.values((storage.types ?? {}) as ResolvedStorageTypes)) {
|
|
@@ -415,7 +419,7 @@ function deriveAnnotations(
|
|
|
415
419
|
// `readExistingEnumValues` read side, so two namespaces sharing an enum name
|
|
416
420
|
// (or native type) resolve to distinct live-database types.
|
|
417
421
|
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
418
|
-
const nsEnums =
|
|
422
|
+
const nsEnums = ns.entries['type'];
|
|
419
423
|
if (!nsEnums) continue;
|
|
420
424
|
for (const entry of Object.values(nsEnums)) {
|
|
421
425
|
if (!isPostgresEnumStorageEntry(entry)) continue;
|