@prisma-next/sql-contract-ts 0.13.0 → 0.14.0

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/package.json CHANGED
@@ -1,27 +1,27 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract-ts",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "SQL-specific TypeScript contract authoring surface for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/config": "0.13.0",
10
- "@prisma-next/contract": "0.13.0",
11
- "@prisma-next/contract-authoring": "0.13.0",
12
- "@prisma-next/framework-components": "0.13.0",
13
- "@prisma-next/sql-contract": "0.13.0",
14
- "@prisma-next/utils": "0.13.0",
9
+ "@prisma-next/config": "0.14.0",
10
+ "@prisma-next/contract": "0.14.0",
11
+ "@prisma-next/contract-authoring": "0.14.0",
12
+ "@prisma-next/framework-components": "0.14.0",
13
+ "@prisma-next/sql-contract": "0.14.0",
14
+ "@prisma-next/utils": "0.14.0",
15
15
  "arktype": "^2.2.0",
16
16
  "pathe": "^2.0.3",
17
17
  "ts-toolbelt": "^9.6.0"
18
18
  },
19
19
  "devDependencies": {
20
- "@prisma-next/test-utils": "0.13.0",
21
- "@prisma-next/tsconfig": "0.13.0",
20
+ "@prisma-next/test-utils": "0.14.0",
21
+ "@prisma-next/tsconfig": "0.14.0",
22
22
  "@types/pg": "8.20.0",
23
23
  "pg": "8.21.0",
24
- "@prisma-next/tsdown": "0.13.0",
24
+ "@prisma-next/tsdown": "0.14.0",
25
25
  "tsdown": "0.22.1",
26
26
  "typescript": "5.9.3",
27
27
  "vitest": "4.1.8"
@@ -6,7 +6,6 @@ import {
6
6
  import {
7
7
  asNamespaceId,
8
8
  type ColumnDefault,
9
- type ColumnDefaultLiteralInputValue,
10
9
  type Contract,
11
10
  type ContractEnum,
12
11
  type ContractField,
@@ -36,13 +35,10 @@ import {
36
35
  applyFkDefaults,
37
36
  buildSqlNamespace,
38
37
  type CheckConstraintInput,
39
- isPostgresEnumStorageEntry,
40
- type PostgresEnumStorageEntry,
41
38
  type SqlNamespaceTablesInput,
42
39
  SqlStorage,
43
40
  type SqlStorageInput,
44
41
  type StorageColumn,
45
- StorageTable,
46
42
  type StorageTableInput,
47
43
  type StorageTypeInstance,
48
44
  type StorageValueSetInput,
@@ -63,16 +59,15 @@ type DomainFieldRef =
63
59
  | { readonly kind: 'scalar'; readonly many?: boolean }
64
60
  | { readonly kind: 'valueObject'; readonly name: string; readonly many?: boolean };
65
61
 
66
- function encodeDefaultLiteralValue(
67
- value: ColumnDefaultLiteralInputValue,
68
- codecId: string,
69
- codecLookup?: CodecLookup,
70
- ): JsonValue {
62
+ function encodeViaCodec(value: unknown, codecId: string, codecLookup?: CodecLookup): JsonValue {
71
63
  const codec = codecLookup?.get(codecId);
72
64
  if (codec) {
73
65
  return codec.encodeJson(value);
74
66
  }
75
- return value as JsonValue;
67
+ return blindCast<
68
+ JsonValue,
69
+ 'no codec lookup at build time: literal/enum member value is already JSON-safe'
70
+ >(value);
76
71
  }
77
72
 
78
73
  function encodeColumnDefault(
@@ -85,7 +80,7 @@ function encodeColumnDefault(
85
80
  }
86
81
  return {
87
82
  kind: 'literal',
88
- value: encodeDefaultLiteralValue(defaultInput.value, codecId, codecLookup),
83
+ value: encodeViaCodec(defaultInput.value, codecId, codecLookup),
89
84
  };
90
85
  }
91
86
 
@@ -185,13 +180,18 @@ function buildThroughDescriptor(
185
180
  targetModel: ModelNode,
186
181
  modelName: string,
187
182
  fieldName: string,
183
+ defaultNamespaceId: string,
188
184
  ): ContractRelationThrough {
189
- const namespaceId = tableNamespaceByName.get(through.table);
190
- if (namespaceId === undefined) {
185
+ if (!tableNamespaceByName.has(through.table)) {
191
186
  throw new Error(
192
187
  `buildSqlContractFromDefinition: junction table "${through.table}" for relation "${modelName}.${fieldName}" is not a declared model.`,
193
188
  );
194
189
  }
190
+ // Junction table names are unique per namespace, not globally. Prefer the
191
+ // junction's own declared namespace (carried on the through node); fall back to
192
+ // the target's default namespace. Resolving by bare table name would pick the
193
+ // wrong namespace when the same junction table name exists in two namespaces.
194
+ const namespaceId = through.namespaceId ?? defaultNamespaceId;
195
195
 
196
196
  return {
197
197
  table: through.table,
@@ -322,55 +322,6 @@ function ensureUnboundNamespaceSlot(
322
322
  });
323
323
  }
324
324
 
325
- const POSTGRES_ENUM_NAMESPACE_ID = 'public';
326
-
327
- function partitionStorageTypesForTarget(
328
- targetId: string,
329
- types: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
330
- namespaceTypes?: Readonly<Record<string, Readonly<Record<string, PostgresEnumStorageEntry>>>>,
331
- ): {
332
- readonly documentTypes: Record<string, StorageTypeInstance>;
333
- readonly namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>>;
334
- } {
335
- const documentTypes: Record<string, StorageTypeInstance> = {};
336
- const namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>> = {};
337
- for (const [name, entry] of Object.entries(types)) {
338
- if (isPostgresEnumStorageEntry(entry)) {
339
- if (targetId !== 'postgres') {
340
- throw new Error(
341
- `buildSqlContractFromDefinition: postgres enum "${name}" is only valid when target is "postgres" (got "${targetId}").`,
342
- );
343
- }
344
- let slot = namespaceEnumTypesById[POSTGRES_ENUM_NAMESPACE_ID];
345
- if (slot === undefined) {
346
- slot = {};
347
- namespaceEnumTypesById[POSTGRES_ENUM_NAMESPACE_ID] = slot;
348
- }
349
- slot[name] = entry;
350
- continue;
351
- }
352
- documentTypes[name] = entry;
353
- }
354
- if (namespaceTypes !== undefined) {
355
- for (const [nsId, enumsInNs] of Object.entries(namespaceTypes)) {
356
- for (const [name, entry] of Object.entries(enumsInNs)) {
357
- if (targetId !== 'postgres') {
358
- throw new Error(
359
- `buildSqlContractFromDefinition: postgres enum "${name}" is only valid when target is "postgres" (got "${targetId}").`,
360
- );
361
- }
362
- let slot = namespaceEnumTypesById[nsId];
363
- if (slot === undefined) {
364
- slot = {};
365
- namespaceEnumTypesById[nsId] = slot;
366
- }
367
- slot[name] = entry;
368
- }
369
- }
370
- }
371
- return { documentTypes, namespaceEnumTypesById };
372
- }
373
-
374
325
  export function buildSqlContractFromDefinition(
375
326
  definition: ContractDefinition,
376
327
  codecLookup?: CodecLookup,
@@ -391,7 +342,7 @@ export function buildSqlContractFromDefinition(
391
342
  definition.models.map((m) => [`${resolveNamespaceId(m)}:${m.modelName}`, m]),
392
343
  );
393
344
 
394
- const tablesByNamespace: Record<string, Record<string, StorageTable>> = {};
345
+ const tablesByNamespace: Record<string, Record<string, StorageTableInput>> = {};
395
346
  const modelNameToNamespaceId = new Map<string, string>();
396
347
  const executionDefaults: ExecutionMutationDefault[] = [];
397
348
  const modelsByNamespace: Record<string, Record<string, ContractModel>> = {};
@@ -451,9 +402,9 @@ export function buildSqlContractFromDefinition(
451
402
  enumHandle !== undefined
452
403
  ? {
453
404
  plane: 'storage',
454
- entityKind: 'value-set',
405
+ entityKind: 'valueSet',
455
406
  namespaceId: defaultNamespaceId,
456
- name: enumHandle.enumName,
407
+ entityName: enumHandle.enumName,
457
408
  }
458
409
  : undefined;
459
410
  const domainValueSetRef: ValueSetRef | undefined =
@@ -462,7 +413,7 @@ export function buildSqlContractFromDefinition(
462
413
  plane: 'domain',
463
414
  entityKind: 'enum',
464
415
  namespaceId: defaultNamespaceId,
465
- name: enumHandle.enumName,
416
+ entityName: enumHandle.enumName,
466
417
  }
467
418
  : undefined;
468
419
 
@@ -484,7 +435,7 @@ export function buildSqlContractFromDefinition(
484
435
 
485
436
  if (executionDefaultPhases) {
486
437
  executionDefaults.push({
487
- ref: { table: tableName, column: field.columnName },
438
+ ref: { namespace: namespaceId, table: tableName, column: field.columnName },
488
439
  ...ifDefined('onCreate', executionDefaultPhases.onCreate),
489
440
  ...ifDefined('onUpdate', executionDefaultPhases.onUpdate),
490
441
  });
@@ -604,7 +555,7 @@ export function buildSqlContractFromDefinition(
604
555
  `buildSqlContractFromDefinition: duplicate table "${tableName}" in namespace "${namespaceId}".`,
605
556
  );
606
557
  }
607
- nsTables[tableName] = new StorageTable(tableInput);
558
+ nsTables[tableName] = tableInput;
608
559
  }
609
560
 
610
561
  // --- Build contract model ---
@@ -678,6 +629,7 @@ export function buildSqlContractFromDefinition(
678
629
  targetModel,
679
630
  semanticModel.modelName,
680
631
  relation.fieldName,
632
+ defaultNamespaceId,
681
633
  ),
682
634
  };
683
635
  } else {
@@ -725,13 +677,9 @@ export function buildSqlContractFromDefinition(
725
677
  // discriminator shape before hashing so the storageHash matches the
726
678
  // persisted JSON envelope produced from the SqlStorage class instance
727
679
  // (which always carries the discriminator).
728
- const rawStorageTypes = (definition.storageTypes ?? {}) as Record<
729
- string,
730
- StorageTypeInstance | PostgresEnumStorageEntry
731
- >;
732
- const storageTypes = Object.fromEntries(
680
+ const rawStorageTypes = definition.storageTypes ?? {};
681
+ const documentTypes: Record<string, StorageTypeInstance> = Object.fromEntries(
733
682
  Object.entries(rawStorageTypes).map(([name, entry]) => {
734
- if (isPostgresEnumStorageEntry(entry)) return [name, entry];
735
683
  if ((entry as { kind?: unknown }).kind === 'codec-instance') return [name, entry];
736
684
  return [
737
685
  name,
@@ -743,15 +691,7 @@ export function buildSqlContractFromDefinition(
743
691
  ];
744
692
  }),
745
693
  );
746
- const { documentTypes, namespaceEnumTypesById } = partitionStorageTypesForTarget(
747
- target,
748
- storageTypes,
749
- definition.namespaceTypes,
750
- );
751
694
  const namespaceCoordinateIds = collectStorageNamespaceCoordinateIds(definition);
752
- for (const id of Object.keys(namespaceEnumTypesById)) {
753
- namespaceCoordinateIds.add(id);
754
- }
755
695
 
756
696
  // Build per-namespace registries for `enumType()` handles.
757
697
  // All authored enums target the contract's default namespace.
@@ -771,7 +711,10 @@ export function buildSqlContractFromDefinition(
771
711
  }
772
712
  domainSlot[enumName] = {
773
713
  codecId: handle.codecId,
774
- members: handle.enumMembers,
714
+ members: handle.enumMembers.map((m) => ({
715
+ name: m.name,
716
+ value: encodeViaCodec(m.value, handle.codecId, codecLookup),
717
+ })),
775
718
  };
776
719
 
777
720
  let storageSlot = storageValueSetsByNs[nsId];
@@ -780,8 +723,8 @@ export function buildSqlContractFromDefinition(
780
723
  storageValueSetsByNs[nsId] = storageSlot;
781
724
  }
782
725
  storageSlot[enumName] = {
783
- kind: 'value-set',
784
- values: handle.values,
726
+ kind: 'valueSet',
727
+ values: handle.values.map((v) => encodeViaCodec(v, handle.codecId, codecLookup)),
785
728
  };
786
729
  }
787
730
 
@@ -792,7 +735,6 @@ export function buildSqlContractFromDefinition(
792
735
  >(
793
736
  Object.fromEntries(
794
737
  [...namespaceCoordinateIds].sort().map((id) => {
795
- const enumTypes = namespaceEnumTypesById[id];
796
738
  const valueSetEntries = storageValueSetsByNs[id];
797
739
  const nsInput: SqlNamespaceTablesInput = {
798
740
  id,
@@ -803,16 +745,16 @@ export function buildSqlContractFromDefinition(
803
745
  : {}),
804
746
  },
805
747
  };
806
- return [
807
- id,
808
- createNamespace ? createNamespace(nsInput, enumTypes) : buildSqlNamespace(nsInput),
809
- ];
748
+ return [id, createNamespace ? createNamespace(nsInput) : buildSqlNamespace(nsInput)];
810
749
  }),
811
750
  ),
812
751
  );
813
752
  const storageWithoutHash = {
814
753
  ...(Object.keys(documentTypes).length > 0 ? { types: documentTypes } : {}),
815
- namespaces: ensureUnboundNamespaceSlot(namespaces, createNamespace),
754
+ namespaces:
755
+ defaultNamespaceId === UNBOUND_NAMESPACE_ID
756
+ ? ensureUnboundNamespaceSlot(namespaces, createNamespace)
757
+ : namespaces,
816
758
  };
817
759
  const storageHash: StorageHashBase<string> = definition.storageHash
818
760
  ? coreHash(definition.storageHash)
@@ -7,11 +7,7 @@ import type {
7
7
  TargetPackRef,
8
8
  } from '@prisma-next/framework-components/components';
9
9
  import type { Namespace } from '@prisma-next/framework-components/ir';
10
- import type {
11
- PostgresEnumStorageEntry,
12
- SqlNamespaceTablesInput,
13
- StorageTypeInstance,
14
- } from '@prisma-next/sql-contract/types';
10
+ import type { SqlNamespaceTablesInput, StorageTypeInstance } from '@prisma-next/sql-contract/types';
15
11
  import { blindCast } from '@prisma-next/utils/casts';
16
12
  import { ifDefined } from '@prisma-next/utils/defined';
17
13
  import { buildSqlContractFromDefinition } from './build-contract';
@@ -55,13 +51,14 @@ type ModelLike = {
55
51
  type ContractDefinition<
56
52
  Family extends FamilyPackRef<string>,
57
53
  Target extends TargetPackRef<'sql', string>,
58
- Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
54
+ Types extends Record<string, StorageTypeInstance>,
59
55
  Models extends Record<string, ModelLike>,
60
56
  ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined,
61
57
  Naming extends ContractInput['naming'] | undefined,
62
58
  StorageHash extends string | undefined,
63
59
  ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined,
64
60
  Namespaces extends readonly string[] | undefined = undefined,
61
+ Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
65
62
  > = {
66
63
  readonly family: Family;
67
64
  readonly target: Target;
@@ -75,7 +72,7 @@ type ContractDefinition<
75
72
  readonly types?: Types;
76
73
  readonly models?: Models;
77
74
  readonly codecLookup?: CodecLookup;
78
- readonly enums?: Record<string, EnumTypeHandle>;
75
+ readonly enums?: Enums;
79
76
  };
80
77
 
81
78
  type ContractScaffold<
@@ -86,6 +83,7 @@ type ContractScaffold<
86
83
  StorageHash extends string | undefined,
87
84
  ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined,
88
85
  Namespaces extends readonly string[] | undefined = undefined,
86
+ Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
89
87
  > = {
90
88
  readonly family: Family;
91
89
  readonly target: Target;
@@ -99,18 +97,20 @@ type ContractScaffold<
99
97
  readonly types?: never;
100
98
  readonly models?: never;
101
99
  readonly codecLookup?: CodecLookup;
102
- readonly enums?: Record<string, EnumTypeHandle>;
100
+ readonly enums?: Enums;
103
101
  };
104
102
 
105
103
  type ContractFactory<
106
104
  Family extends FamilyPackRef<string>,
107
105
  Target extends TargetPackRef<'sql', string>,
108
- Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
106
+ Types extends Record<string, StorageTypeInstance>,
109
107
  Models extends Record<string, ModelLike>,
110
108
  ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined,
109
+ Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
111
110
  > = (helpers: ComposedAuthoringHelpers<Family, Target, ExtensionPacks>) => {
112
111
  readonly types?: Types;
113
112
  readonly models?: Models;
113
+ readonly enums?: Enums;
114
114
  };
115
115
 
116
116
  function validateTargetPackRef(
@@ -300,10 +300,7 @@ function buildContractFromDsl<Definition extends ContractInput>(
300
300
  // Input for buildBoundContract — all fields from ContractInput except family/target
301
301
  // (those are injected by the builder, pre-bound at the call site).
302
302
  type BoundDefinitionInput<
303
- Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = Record<
304
- never,
305
- never
306
- >,
303
+ Types extends Record<string, StorageTypeInstance> = Record<never, never>,
307
304
  Models extends Record<string, ModelLike> = Record<never, never>,
308
305
  ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined = undefined,
309
306
  Naming extends ContractInput['naming'] | undefined = undefined,
@@ -324,6 +321,20 @@ type BoundDefinitionInput<
324
321
  readonly enums?: Record<string, EnumTypeHandle>;
325
322
  };
326
323
 
324
+ // A bare `Record<string, EnumTypeHandle>` (no literal keys) is the widened
325
+ // default for a side that declared no enums; drop it so the merge keeps only
326
+ // literally-authored enum handles.
327
+ type LiteralEnums<E extends Record<string, EnumTypeHandle>> = string extends keyof E
328
+ ? Record<never, never>
329
+ : E;
330
+
331
+ // Merges enum handles authored on the scaffold definition with those returned
332
+ // from the factory callback. Either side may be the widened default (empty).
333
+ export type MergeEnums<
334
+ ScaffoldEnums extends Record<string, EnumTypeHandle>,
335
+ FactoryEnums extends Record<string, EnumTypeHandle>,
336
+ > = LiteralEnums<ScaffoldEnums> & LiteralEnums<FactoryEnums>;
337
+
327
338
  // Merges a bound input with the pre-bound family/target to produce a full ContractDefinition.
328
339
  type WithFamilyTarget<
329
340
  Input,
@@ -343,7 +354,7 @@ export function buildBoundContract<
343
354
  const F extends FamilyPackRef<string>,
344
355
  const T extends TargetPackRef<'sql', string>,
345
356
  const Definition extends BoundDefinitionInput<
346
- Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
357
+ Record<string, StorageTypeInstance>,
347
358
  Record<string, ModelLike>,
348
359
  Record<string, ExtensionPackRef<'sql', string>> | undefined,
349
360
  ContractInput['naming'] | undefined,
@@ -364,7 +375,7 @@ export function buildBoundContract<
364
375
  const F extends FamilyPackRef<string>,
365
376
  const T extends TargetPackRef<'sql', string>,
366
377
  const Definition extends BoundDefinitionInput<
367
- Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
378
+ Record<string, StorageTypeInstance>,
368
379
  Record<string, ModelLike>,
369
380
  Record<string, ExtensionPackRef<'sql', string>> | undefined,
370
381
  ContractInput['naming'] | undefined,
@@ -373,8 +384,9 @@ export function buildBoundContract<
373
384
  readonly string[] | undefined
374
385
  >,
375
386
  const Built extends {
376
- readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
387
+ readonly types?: Record<string, StorageTypeInstance>;
377
388
  readonly models?: Record<string, ModelLike>;
389
+ readonly enums?: Record<string, EnumTypeHandle>;
378
390
  },
379
391
  >(
380
392
  family: F,
@@ -397,8 +409,9 @@ export function buildBoundContract(
397
409
  Record<string, ExtensionPackRef<'sql', string>> | undefined
398
410
  >,
399
411
  ) => {
400
- readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
412
+ readonly types?: Record<string, StorageTypeInstance>;
401
413
  readonly models?: Record<string, ModelLike>;
414
+ readonly enums?: Record<string, EnumTypeHandle>;
402
415
  })
403
416
  | undefined,
404
417
  ) {
@@ -412,10 +425,12 @@ export function buildBoundContract(
412
425
  extensionPacks: definition.extensionPacks,
413
426
  }),
414
427
  );
428
+ const mergedEnums = { ...(definition.enums ?? {}), ...built.enums };
415
429
  return buildContractFromDsl({
416
430
  ...full,
417
431
  ...ifDefined('types', built.types),
418
432
  ...ifDefined('models', built.models),
433
+ ...ifDefined('enums', Object.keys(mergedEnums).length > 0 ? mergedEnums : undefined),
419
434
  });
420
435
  }
421
436
 
@@ -425,10 +440,7 @@ export function buildBoundContract(
425
440
  export function defineContract<
426
441
  const Family extends FamilyPackRef<string>,
427
442
  const Target extends TargetPackRef<'sql', string>,
428
- const Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = Record<
429
- never,
430
- never
431
- >,
443
+ const Types extends Record<string, StorageTypeInstance> = Record<never, never>,
432
444
  const Models extends Record<string, ModelLike> = Record<never, never>,
433
445
  const ExtensionPacks extends
434
446
  | Record<string, ExtensionPackRef<'sql', string>>
@@ -437,6 +449,7 @@ export function defineContract<
437
449
  const StorageHash extends string | undefined = undefined,
438
450
  const ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
439
451
  const Namespaces extends readonly string[] | undefined = undefined,
452
+ const Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
440
453
  >(
441
454
  definition: ContractDefinition<
442
455
  Family,
@@ -447,7 +460,8 @@ export function defineContract<
447
460
  Naming,
448
461
  StorageHash,
449
462
  ForeignKeyDefaults,
450
- Namespaces
463
+ Namespaces,
464
+ Enums
451
465
  >,
452
466
  ): SqlContractResult<
453
467
  ContractDefinition<
@@ -459,16 +473,14 @@ export function defineContract<
459
473
  Naming,
460
474
  StorageHash,
461
475
  ForeignKeyDefaults,
462
- Namespaces
476
+ Namespaces,
477
+ Enums
463
478
  >
464
479
  >;
465
480
  export function defineContract<
466
481
  const Family extends FamilyPackRef<string>,
467
482
  const Target extends TargetPackRef<'sql', string>,
468
- const Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = Record<
469
- never,
470
- never
471
- >,
483
+ const Types extends Record<string, StorageTypeInstance> = Record<never, never>,
472
484
  const Models extends Record<string, ModelLike> = Record<never, never>,
473
485
  const ExtensionPacks extends
474
486
  | Record<string, ExtensionPackRef<'sql', string>>
@@ -477,6 +489,8 @@ export function defineContract<
477
489
  const StorageHash extends string | undefined = undefined,
478
490
  const ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
479
491
  const Namespaces extends readonly string[] | undefined = undefined,
492
+ const ScaffoldEnums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
493
+ const FactoryEnums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
480
494
  >(
481
495
  definition: ContractScaffold<
482
496
  Family,
@@ -485,9 +499,10 @@ export function defineContract<
485
499
  Naming,
486
500
  StorageHash,
487
501
  ForeignKeyDefaults,
488
- Namespaces
502
+ Namespaces,
503
+ ScaffoldEnums
489
504
  >,
490
- factory: ContractFactory<Family, Target, Types, Models, ExtensionPacks>,
505
+ factory: ContractFactory<Family, Target, Types, Models, ExtensionPacks, FactoryEnums>,
491
506
  ): SqlContractResult<
492
507
  ContractDefinition<
493
508
  Family,
@@ -498,7 +513,8 @@ export function defineContract<
498
513
  Naming,
499
514
  StorageHash,
500
515
  ForeignKeyDefaults,
501
- Namespaces
516
+ Namespaces,
517
+ MergeEnums<ScaffoldEnums, FactoryEnums>
502
518
  >
503
519
  >;
504
520
  export function defineContract(
@@ -506,7 +522,7 @@ export function defineContract(
506
522
  factory?: ContractFactory<
507
523
  FamilyPackRef<string>,
508
524
  TargetPackRef<'sql', string>,
509
- Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
525
+ Record<string, StorageTypeInstance>,
510
526
  Record<string, ModelLike>,
511
527
  Record<string, ExtensionPackRef<'sql', string>> | undefined
512
528
  >,
@@ -8,7 +8,6 @@ import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/cod
8
8
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
9
9
  import type { Namespace } from '@prisma-next/framework-components/ir';
10
10
  import type {
11
- PostgresEnumStorageEntry,
12
11
  ReferentialAction,
13
12
  SqlNamespaceTablesInput,
14
13
  StorageTypeInstance,
@@ -105,6 +104,14 @@ export interface RelationNode {
105
104
  };
106
105
  readonly through?: {
107
106
  readonly table: string;
107
+ /**
108
+ * Namespace the junction table lives in. Set from the junction model's
109
+ * declared namespace at lowering time; junction table names are unique per
110
+ * namespace, not globally, so this disambiguates a junction whose bare table
111
+ * name also exists in another namespace. Omitted for a junction in the
112
+ * default namespace (resolved to the target's default at build time).
113
+ */
114
+ readonly namespaceId?: string;
108
115
  readonly parentColumns: readonly string[];
109
116
  readonly childColumns: readonly string[];
110
117
  };
@@ -162,33 +169,14 @@ export interface ContractDefinition {
162
169
  readonly extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>;
163
170
  readonly storageHash?: string;
164
171
  readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
165
- readonly storageTypes?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
166
- /**
167
- * Enum types declared inside a named `namespace { enum … }` block,
168
- * keyed first by namespace id then by type name. These are routed to
169
- * `storage.namespaces[nsId].entries.type` rather than the implicit fallback
170
- * namespace used for top-level `storageTypes` enums.
171
- */
172
- readonly namespaceTypes?: Readonly<
173
- Record<string, Readonly<Record<string, PostgresEnumStorageEntry>>>
174
- >;
172
+ readonly storageTypes?: Record<string, StorageTypeInstance>;
175
173
  /**
176
174
  * Declared namespace coordinates for this contract — populates
177
175
  * `SqlStorage.namespaces` together with `createNamespace`.
178
176
  */
179
177
  readonly namespaces?: readonly string[];
180
- /**
181
- * Target-supplied factory that materialises a `Namespace` concretion
182
- * for a declared namespace coordinate. Mirrors
183
- * `ContractInput.createNamespace`.
184
- *
185
- * The optional second argument carries target-specific enum types for the
186
- * namespace (e.g. postgres enum registrations keyed by type name).
187
- */
188
- readonly createNamespace?: (
189
- input: SqlNamespaceTablesInput,
190
- enumTypes?: Readonly<Record<string, PostgresEnumStorageEntry>>,
191
- ) => Namespace;
178
+ /** Target-supplied factory that materialises a `Namespace` concretion for a declared namespace coordinate. */
179
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
192
180
  readonly models: readonly ModelNode[];
193
181
  readonly valueObjects?: readonly ValueObjectNode[];
194
182
  /**