@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.
Files changed (45) hide show
  1. package/dist/control-adapter-BnNdlGKS.d.mts +172 -0
  2. package/dist/control-adapter-BnNdlGKS.d.mts.map +1 -0
  3. package/dist/control-adapter.d.mts +2 -109
  4. package/dist/control.d.mts +118 -4
  5. package/dist/control.d.mts.map +1 -1
  6. package/dist/control.mjs +225 -40
  7. package/dist/control.mjs.map +1 -1
  8. package/dist/ir.d.mts +2 -2
  9. package/dist/ir.d.mts.map +1 -1
  10. package/dist/ir.mjs +1 -1
  11. package/dist/migration.d.mts +1 -1
  12. package/dist/runtime.d.mts +4 -2
  13. package/dist/runtime.d.mts.map +1 -1
  14. package/dist/runtime.mjs +3 -1
  15. package/dist/runtime.mjs.map +1 -1
  16. package/dist/schema-verify.d.mts +1 -0
  17. package/dist/schema-verify.d.mts.map +1 -1
  18. package/dist/schema-verify.mjs +1 -1
  19. package/dist/{sql-contract-serializer-8axtK4lg.mjs → sql-contract-serializer-nNw1yk9P.mjs} +14 -35
  20. package/dist/sql-contract-serializer-nNw1yk9P.mjs.map +1 -0
  21. package/dist/{types-CeeCStqw.d.mts → types-CfJQKaMJ.d.mts} +69 -15
  22. package/dist/types-CfJQKaMJ.d.mts.map +1 -0
  23. package/dist/verify-sql-schema-CN7pPoTC.d.mts.map +1 -1
  24. package/dist/{verify-sql-schema-CYLsGCFO.mjs → verify-sql-schema-CYlKme0a.mjs} +400 -317
  25. package/dist/verify-sql-schema-CYlKme0a.mjs.map +1 -0
  26. package/package.json +21 -21
  27. package/src/core/control-adapter.ts +105 -7
  28. package/src/core/control-instance.ts +151 -66
  29. package/src/core/default-namespace.ts +9 -0
  30. package/src/core/ir/sql-contract-serializer-base.ts +42 -57
  31. package/src/core/migrations/contract-to-schema-ir.ts +12 -8
  32. package/src/core/migrations/control-policy.ts +322 -0
  33. package/src/core/migrations/field-event-planner.ts +2 -2
  34. package/src/core/migrations/plan-helpers.ts +16 -0
  35. package/src/core/migrations/types.ts +16 -6
  36. package/src/core/schema-verify/control-verify-emit.ts +46 -0
  37. package/src/core/schema-verify/verifier-disposition.ts +53 -0
  38. package/src/core/schema-verify/verify-helpers.ts +151 -110
  39. package/src/core/schema-verify/verify-sql-schema.ts +281 -178
  40. package/src/exports/control.ts +6 -0
  41. package/src/exports/runtime.ts +7 -0
  42. package/dist/control-adapter.d.mts.map +0 -1
  43. package/dist/sql-contract-serializer-8axtK4lg.mjs.map +0 -1
  44. package/dist/types-CeeCStqw.d.mts.map +0 -1
  45. package/dist/verify-sql-schema-CYLsGCFO.mjs.map +0 -1
@@ -1,10 +1,13 @@
1
- import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
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 tables?: Readonly<Record<string, unknown>> }
71
+ { readonly entries: { readonly table: Readonly<Record<string, unknown>> } }
73
72
  >;
74
73
  for (const ns of Object.values(namespaces)) {
75
- const tbls = ns.tables;
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: ControlDriverInstance<'sql', string>;
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: ControlDriverInstance<'sql', string>;
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: ControlDriverInstance<'sql', string>;
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 `ControlDriverInstance<'sql', string>` —
365
- // the family API isn't generic on the target id. Letting `isSqlControlAdapter`
366
- // default its type parameter narrows the adapter to `SqlControlAdapter<string>`,
367
- // which matches the family-level driver type without any cast at call sites.
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: ControlDriverInstance<'sql', string>;
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: ControlDriverInstance<'sql', string>;
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
- await driver.query(ensureSchemaStatement.sql, ensureSchemaStatement.params);
565
- await driver.query(ensureTableStatement.sql, ensureTableStatement.params);
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 getControlAdapter().readMarker(driver, APP_SPACE_ID);
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
- const write = writeContractMarker({
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 write = writeContractMarker({
596
- space: APP_SPACE_ID,
597
- storageHash: contractStorageHash,
598
- profileHash: contractProfileHash,
599
- contractJson: contractInput,
600
- canonicalVersion: existingMarker.canonicalVersion ?? 1,
601
- });
602
- await driver.query(write.update.sql, write.update.params);
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: ControlDriverInstance<'sql', string>;
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: ControlDriverInstance<'sql', string>;
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: ControlDriverInstance<'sql', string>;
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
  },
@@ -0,0 +1,9 @@
1
+ export {
2
+ type ResolvedDomainModel,
3
+ resolveDomainModel,
4
+ UNBOUND_DOMAIN_NAMESPACE_ID,
5
+ } from '@prisma-next/contract/types';
6
+ export {
7
+ type ResolvedStorageTable,
8
+ resolveStorageTable,
9
+ } from '@prisma-next/sql-contract/resolve-storage-table';
@@ -29,16 +29,16 @@ import { type Type, type } from 'arktype';
29
29
  const NamespaceRawSchema = type({
30
30
  id: 'string',
31
31
  'kind?': 'string',
32
- // Undeclared keys (`tables`, `enum`, and any pack-contributed slot maps)
33
- // intentionally pass through; the slot loop below iterates them by name.
34
- '+': 'ignore',
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) => SqlStorageTypeEntry;
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
- private readonly entityTypeRegistry: ReadonlyMap<string, SqlEntityHydrationFactory> = new Map(),
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(namespaceHydrated);
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
- const result: Record<string, unknown> = { id };
182
-
183
- for (const [propertyKey, slotValue] of Object.entries(parsed)) {
184
- if (propertyKey === 'id') continue;
185
- if (slotValue === null || typeof slotValue !== 'object') continue;
186
-
187
- if (propertyKey === 'tables') {
188
- result['tables'] = Object.fromEntries(
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
- const tables = (result['tables'] ?? {}) as Record<string, StorageTable>;
232
- const enumSlot = result['enum'] as NonNullable<SqlNamespaceTablesInput['enum']> | undefined;
233
- return {
234
- ...result,
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 factory(entry);
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?.tables;
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?.tables[tableName];
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 = (ns as { enum?: Record<string, PostgresEnumStorageEntry> }).enum;
331
+ const nsEnums = ns.entries['type'];
331
332
  if (nsEnums) {
332
333
  for (const [k, v] of Object.entries(nsEnums)) {
333
- allTypes[k] = v;
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.tables)) {
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}.tables.${tableName}`,
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[*].enum`, not here; a defensive top-level enum is still
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 = (ns as { enum?: Record<string, PostgresEnumStorageEntry> }).enum;
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;