@prisma-next/family-sql 0.12.0-dev.4 → 0.12.0-dev.41
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-uYnmu04p.d.mts +181 -0
- package/dist/control-adapter-uYnmu04p.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 +226 -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 +2 -1
- 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-DxNgxFkF.d.mts} +61 -7
- package/dist/types-DxNgxFkF.d.mts.map +1 -0
- package/dist/{verify-sql-schema-CN7pPoTC.d.mts → verify-sql-schema-Cj3c2t5Z.d.mts} +8 -2
- package/dist/verify-sql-schema-Cj3c2t5Z.d.mts.map +1 -0
- package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-DV_d2XCG.mjs} +410 -323
- package/dist/verify-sql-schema-DV_d2XCG.mjs.map +1 -0
- package/package.json +21 -21
- package/src/core/control-adapter.ts +112 -3
- package/src/core/control-instance.ts +143 -56
- 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 +12 -2
- 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 +305 -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-CN7pPoTC.d.mts.map +0 -1
- package/dist/verify-sql-schema-CYLsGCFO.mjs.map +0 -1
|
@@ -1,4 +1,8 @@
|
|
|
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,
|
|
@@ -31,14 +35,10 @@ import { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/cano
|
|
|
31
35
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
32
36
|
import type {
|
|
33
37
|
AnyQueryAst,
|
|
38
|
+
DdlNode,
|
|
34
39
|
LoweredStatement,
|
|
35
40
|
LowererContext,
|
|
36
41
|
} from '@prisma-next/sql-relational-core/ast';
|
|
37
|
-
import {
|
|
38
|
-
ensureSchemaStatement,
|
|
39
|
-
ensureTableStatement,
|
|
40
|
-
writeContractMarker,
|
|
41
|
-
} from '@prisma-next/sql-runtime';
|
|
42
42
|
import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
|
|
43
43
|
import type { SqlSchemaIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
44
44
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
@@ -69,10 +69,10 @@ function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] {
|
|
|
69
69
|
) {
|
|
70
70
|
const namespaces = contract.storage.namespaces as Record<
|
|
71
71
|
string,
|
|
72
|
-
{ readonly
|
|
72
|
+
{ readonly entries: { readonly table: Readonly<Record<string, unknown>> } }
|
|
73
73
|
>;
|
|
74
74
|
for (const ns of Object.values(namespaces)) {
|
|
75
|
-
const tbls = ns.
|
|
75
|
+
const tbls = ns.entries.table;
|
|
76
76
|
if (typeof tbls !== 'object' || tbls === null) continue;
|
|
77
77
|
for (const table of Object.values(tbls)) {
|
|
78
78
|
if (
|
|
@@ -241,30 +241,64 @@ export interface SqlControlFamilyInstance
|
|
|
241
241
|
|
|
242
242
|
inferPslContract(schemaIR: SqlSchemaIR): PslDocumentAst;
|
|
243
243
|
|
|
244
|
-
lowerAst(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement;
|
|
244
|
+
lowerAst(ast: AnyQueryAst | DdlNode, context: LowererContext<unknown>): LoweredStatement;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Inserts the initial marker row for `space` (upsert on `space`).
|
|
248
|
+
* Delegates to the target control adapter's write SPI; see
|
|
249
|
+
* `SqlControlAdapter.initMarker`.
|
|
250
|
+
*/
|
|
251
|
+
initMarker(options: {
|
|
252
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
253
|
+
readonly space: string;
|
|
254
|
+
readonly destination: {
|
|
255
|
+
readonly storageHash: string;
|
|
256
|
+
readonly profileHash: string;
|
|
257
|
+
readonly invariants?: readonly string[];
|
|
258
|
+
};
|
|
259
|
+
}): Promise<void>;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Compare-and-swap advance of the marker row for `space`. Returns `true`
|
|
263
|
+
* when the swap matched a row; see `SqlControlAdapter.updateMarker`.
|
|
264
|
+
*/
|
|
265
|
+
updateMarker(options: {
|
|
266
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
267
|
+
readonly space: string;
|
|
268
|
+
readonly expectedFrom: string;
|
|
269
|
+
readonly destination: {
|
|
270
|
+
readonly storageHash: string;
|
|
271
|
+
readonly profileHash: string;
|
|
272
|
+
readonly invariants?: readonly string[];
|
|
273
|
+
};
|
|
274
|
+
}): Promise<boolean>;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Appends a ledger entry for `space`; see
|
|
278
|
+
* `SqlControlAdapter.writeLedgerEntry`.
|
|
279
|
+
*/
|
|
280
|
+
writeLedgerEntry(options: {
|
|
281
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
282
|
+
readonly space: string;
|
|
283
|
+
readonly entry: {
|
|
284
|
+
readonly edgeId: string;
|
|
285
|
+
readonly from: string;
|
|
286
|
+
readonly to: string;
|
|
287
|
+
readonly migrationName: string;
|
|
288
|
+
readonly migrationHash: string;
|
|
289
|
+
readonly operations: readonly unknown[];
|
|
290
|
+
};
|
|
291
|
+
}): Promise<void>;
|
|
292
|
+
|
|
293
|
+
bootstrapControlTableQueries(): readonly DdlNode[];
|
|
294
|
+
|
|
295
|
+
bootstrapSignMarkerQueries(): readonly DdlNode[];
|
|
245
296
|
|
|
246
297
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview;
|
|
247
298
|
}
|
|
248
299
|
|
|
249
300
|
export type SqlFamilyInstance = SqlControlFamilyInstance;
|
|
250
301
|
|
|
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
302
|
interface DescriptorWithStorageTypes {
|
|
269
303
|
readonly targetId?: string | undefined;
|
|
270
304
|
readonly types?:
|
|
@@ -361,19 +395,11 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
361
395
|
extensionPacks: extensions,
|
|
362
396
|
});
|
|
363
397
|
|
|
364
|
-
// Family-instance methods accept `ControlDriverInstance<'sql', string>` —
|
|
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
|
-
};
|
|
398
|
+
// Family-instance methods accept `ControlDriverInstance<'sql', string>` — the
|
|
399
|
+
// family API isn't generic on the target id. The adapter descriptor's `create`
|
|
400
|
+
// returns the concrete `SqlControlAdapter<TTargetId>`; widening the target id to
|
|
401
|
+
// `string` here matches the family-level driver type without a per-method probe.
|
|
402
|
+
const getControlAdapter = (): SqlControlAdapter<string> => adapter.create(stack);
|
|
377
403
|
|
|
378
404
|
const targetSerializer = (
|
|
379
405
|
target as unknown as {
|
|
@@ -540,6 +566,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
540
566
|
frameworkComponents: options.frameworkComponents,
|
|
541
567
|
...ifDefined('normalizeDefault', controlAdapter.normalizeDefault),
|
|
542
568
|
...ifDefined('normalizeNativeType', controlAdapter.normalizeNativeType),
|
|
569
|
+
...ifDefined('columnsCompatible', controlAdapter.columnsCompatible),
|
|
543
570
|
...ifDefined('resolveExistingEnumValues', resolveExistingEnumValues),
|
|
544
571
|
});
|
|
545
572
|
},
|
|
@@ -561,24 +588,24 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
561
588
|
: contractStorageHash;
|
|
562
589
|
const contractTarget = contract.target;
|
|
563
590
|
|
|
564
|
-
|
|
565
|
-
|
|
591
|
+
const controlAdapter = getControlAdapter();
|
|
592
|
+
const lowererContext = { contract };
|
|
593
|
+
for (const query of controlAdapter.bootstrapSignMarkerQueries()) {
|
|
594
|
+
const lowered = controlAdapter.lower(query, lowererContext);
|
|
595
|
+
await driver.query(lowered.sql, lowered.params);
|
|
596
|
+
}
|
|
566
597
|
|
|
567
|
-
const existingMarker = await
|
|
598
|
+
const existingMarker = await controlAdapter.readMarker(driver, APP_SPACE_ID);
|
|
568
599
|
|
|
569
600
|
let markerCreated = false;
|
|
570
601
|
let markerUpdated = false;
|
|
571
602
|
let previousHashes: { storageHash?: string; profileHash?: string } | undefined;
|
|
572
603
|
|
|
573
604
|
if (!existingMarker) {
|
|
574
|
-
|
|
575
|
-
space: APP_SPACE_ID,
|
|
605
|
+
await controlAdapter.insertMarker(driver, APP_SPACE_ID, {
|
|
576
606
|
storageHash: contractStorageHash,
|
|
577
607
|
profileHash: contractProfileHash,
|
|
578
|
-
contractJson: contractInput,
|
|
579
|
-
canonicalVersion: 1,
|
|
580
608
|
});
|
|
581
|
-
await driver.query(write.insert.sql, write.insert.params);
|
|
582
609
|
markerCreated = true;
|
|
583
610
|
} else {
|
|
584
611
|
const existingStorageHash = existingMarker.storageHash;
|
|
@@ -592,14 +619,18 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
592
619
|
storageHash: existingStorageHash,
|
|
593
620
|
profileHash: existingProfileHash,
|
|
594
621
|
};
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
622
|
+
const updated = await controlAdapter.updateMarker(
|
|
623
|
+
driver,
|
|
624
|
+
APP_SPACE_ID,
|
|
625
|
+
existingStorageHash,
|
|
626
|
+
{
|
|
627
|
+
storageHash: contractStorageHash,
|
|
628
|
+
profileHash: contractProfileHash,
|
|
629
|
+
},
|
|
630
|
+
);
|
|
631
|
+
if (!updated) {
|
|
632
|
+
throw new Error('CAS conflict: marker was modified by another process during sign');
|
|
633
|
+
}
|
|
603
634
|
markerUpdated = true;
|
|
604
635
|
}
|
|
605
636
|
}
|
|
@@ -651,6 +682,54 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
651
682
|
}): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
652
683
|
return getControlAdapter().readAllMarkers(options.driver);
|
|
653
684
|
},
|
|
685
|
+
async readLedger(options: {
|
|
686
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
687
|
+
readonly space?: string;
|
|
688
|
+
}): Promise<readonly LedgerEntryRecord[]> {
|
|
689
|
+
return getControlAdapter().readLedger(options.driver, options.space);
|
|
690
|
+
},
|
|
691
|
+
async initMarker(options: {
|
|
692
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
693
|
+
readonly space: string;
|
|
694
|
+
readonly destination: {
|
|
695
|
+
readonly storageHash: string;
|
|
696
|
+
readonly profileHash: string;
|
|
697
|
+
readonly invariants?: readonly string[];
|
|
698
|
+
};
|
|
699
|
+
}): Promise<void> {
|
|
700
|
+
return getControlAdapter().initMarker(options.driver, options.space, options.destination);
|
|
701
|
+
},
|
|
702
|
+
async updateMarker(options: {
|
|
703
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
704
|
+
readonly space: string;
|
|
705
|
+
readonly expectedFrom: string;
|
|
706
|
+
readonly destination: {
|
|
707
|
+
readonly storageHash: string;
|
|
708
|
+
readonly profileHash: string;
|
|
709
|
+
readonly invariants?: readonly string[];
|
|
710
|
+
};
|
|
711
|
+
}): Promise<boolean> {
|
|
712
|
+
return getControlAdapter().updateMarker(
|
|
713
|
+
options.driver,
|
|
714
|
+
options.space,
|
|
715
|
+
options.expectedFrom,
|
|
716
|
+
options.destination,
|
|
717
|
+
);
|
|
718
|
+
},
|
|
719
|
+
async writeLedgerEntry(options: {
|
|
720
|
+
readonly driver: ControlDriverInstance<'sql', string>;
|
|
721
|
+
readonly space: string;
|
|
722
|
+
readonly entry: {
|
|
723
|
+
readonly edgeId: string;
|
|
724
|
+
readonly from: string;
|
|
725
|
+
readonly to: string;
|
|
726
|
+
readonly migrationName: string;
|
|
727
|
+
readonly migrationHash: string;
|
|
728
|
+
readonly operations: readonly unknown[];
|
|
729
|
+
};
|
|
730
|
+
}): Promise<void> {
|
|
731
|
+
return getControlAdapter().writeLedgerEntry(options.driver, options.space, options.entry);
|
|
732
|
+
},
|
|
654
733
|
async introspect(options: {
|
|
655
734
|
readonly driver: ControlDriverInstance<'sql', string>;
|
|
656
735
|
readonly contract?: unknown;
|
|
@@ -662,10 +741,18 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
662
741
|
return sqlSchemaIrToPslAst(schemaIR);
|
|
663
742
|
},
|
|
664
743
|
|
|
665
|
-
lowerAst(ast: AnyQueryAst, context: LowererContext<unknown>): LoweredStatement {
|
|
744
|
+
lowerAst(ast: AnyQueryAst | DdlNode, context: LowererContext<unknown>): LoweredStatement {
|
|
666
745
|
return getControlAdapter().lower(ast, context);
|
|
667
746
|
},
|
|
668
747
|
|
|
748
|
+
bootstrapControlTableQueries(): readonly DdlNode[] {
|
|
749
|
+
return getControlAdapter().bootstrapControlTableQueries();
|
|
750
|
+
},
|
|
751
|
+
|
|
752
|
+
bootstrapSignMarkerQueries(): readonly DdlNode[] {
|
|
753
|
+
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
754
|
+
},
|
|
755
|
+
|
|
669
756
|
toOperationPreview(operations: readonly MigrationPlanOperation[]): OperationPreview {
|
|
670
757
|
return sqlOperationsToPreview(operations);
|
|
671
758
|
},
|
|
@@ -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;
|