@prisma-next/sql-contract-ts 0.9.0 → 0.10.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.9.0",
3
+ "version": "0.10.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.9.0",
10
- "@prisma-next/contract": "0.9.0",
11
- "@prisma-next/contract-authoring": "0.9.0",
12
- "@prisma-next/framework-components": "0.9.0",
13
- "@prisma-next/sql-contract": "0.9.0",
14
- "@prisma-next/utils": "0.9.0",
9
+ "@prisma-next/config": "0.10.0",
10
+ "@prisma-next/contract": "0.10.0",
11
+ "@prisma-next/contract-authoring": "0.10.0",
12
+ "@prisma-next/framework-components": "0.10.0",
13
+ "@prisma-next/sql-contract": "0.10.0",
14
+ "@prisma-next/utils": "0.10.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.9.0",
21
- "@prisma-next/tsconfig": "0.9.0",
20
+ "@prisma-next/test-utils": "0.10.0",
21
+ "@prisma-next/tsconfig": "0.10.0",
22
22
  "@types/pg": "8.20.0",
23
23
  "pg": "8.20.0",
24
- "@prisma-next/tsdown": "0.9.0",
24
+ "@prisma-next/tsdown": "0.10.0",
25
25
  "tsdown": "0.22.0",
26
26
  "typescript": "5.9.3",
27
27
  "vitest": "4.1.6"
@@ -17,7 +17,7 @@ import {
17
17
  type StorageHashBase,
18
18
  } from '@prisma-next/contract/types';
19
19
  import type { CodecLookup } from '@prisma-next/framework-components/codec';
20
- import { UNSPECIFIED_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
20
+ import { type Namespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
21
21
  import { validateIndexTypes } from '@prisma-next/sql-contract/index-type-validation';
22
22
  import {
23
23
  createIndexTypeRegistry,
@@ -28,10 +28,12 @@ import {
28
28
  applyFkDefaults,
29
29
  isPostgresEnumStorageEntry,
30
30
  type PostgresEnumStorageEntry,
31
+ type SqlNamespaceTablesInput,
31
32
  SqlStorage,
32
- SqlUnspecifiedNamespace,
33
+ type SqlStorageInput,
33
34
  type StorageColumn,
34
- type StorageTable,
35
+ StorageTable,
36
+ type StorageTableInput,
35
37
  type StorageTypeInstance,
36
38
  toStorageTypeInstance,
37
39
  } from '@prisma-next/sql-contract/types';
@@ -209,6 +211,71 @@ function buildDomainField(
209
211
  };
210
212
  }
211
213
 
214
+ function collectStorageNamespaceCoordinateIds(definition: ContractDefinition): Set<string> {
215
+ const ids = new Set<string>();
216
+ ids.add(UNBOUND_NAMESPACE_ID);
217
+ for (const id of definition.namespaces ?? []) {
218
+ if (id.length > 0) {
219
+ ids.add(id);
220
+ }
221
+ }
222
+ for (const model of definition.models) {
223
+ if (model.namespaceId !== undefined && model.namespaceId.length > 0) {
224
+ ids.add(model.namespaceId);
225
+ }
226
+ }
227
+ return ids;
228
+ }
229
+
230
+ const POSTGRES_ENUM_NAMESPACE_ID = 'public';
231
+
232
+ function partitionStorageTypesForTarget(
233
+ targetId: string,
234
+ types: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
235
+ namespaceTypes?: Readonly<Record<string, Readonly<Record<string, PostgresEnumStorageEntry>>>>,
236
+ ): {
237
+ readonly documentTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
238
+ readonly namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>>;
239
+ } {
240
+ const documentTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = {};
241
+ const namespaceEnumTypesById: Record<string, Record<string, PostgresEnumStorageEntry>> = {};
242
+ for (const [name, entry] of Object.entries(types)) {
243
+ if (isPostgresEnumStorageEntry(entry)) {
244
+ if (targetId !== 'postgres') {
245
+ throw new Error(
246
+ `buildSqlContractFromDefinition: postgres enum "${name}" is only valid when target is "postgres" (got "${targetId}").`,
247
+ );
248
+ }
249
+ let slot = namespaceEnumTypesById[POSTGRES_ENUM_NAMESPACE_ID];
250
+ if (slot === undefined) {
251
+ slot = {};
252
+ namespaceEnumTypesById[POSTGRES_ENUM_NAMESPACE_ID] = slot;
253
+ }
254
+ slot[name] = entry;
255
+ continue;
256
+ }
257
+ documentTypes[name] = entry;
258
+ }
259
+ if (namespaceTypes !== undefined) {
260
+ for (const [nsId, enumsInNs] of Object.entries(namespaceTypes)) {
261
+ for (const [name, entry] of Object.entries(enumsInNs)) {
262
+ if (targetId !== 'postgres') {
263
+ throw new Error(
264
+ `buildSqlContractFromDefinition: postgres enum "${name}" is only valid when target is "postgres" (got "${targetId}").`,
265
+ );
266
+ }
267
+ let slot = namespaceEnumTypesById[nsId];
268
+ if (slot === undefined) {
269
+ slot = {};
270
+ namespaceEnumTypesById[nsId] = slot;
271
+ }
272
+ slot[name] = entry;
273
+ }
274
+ }
275
+ }
276
+ return { documentTypes, namespaceEnumTypesById };
277
+ }
278
+
212
279
  export function buildSqlContractFromDefinition(
213
280
  definition: ContractDefinition,
214
281
  codecLookup?: CodecLookup,
@@ -217,7 +284,8 @@ export function buildSqlContractFromDefinition(
217
284
  const targetFamily = 'sql';
218
285
  const modelsByName = new Map(definition.models.map((m) => [m.modelName, m]));
219
286
 
220
- const storageTables: Record<string, StorageTable> = {};
287
+ const tablesByNamespace: Record<string, Record<string, StorageTable>> = {};
288
+ const tableNameToNamespaceId = new Map<string, string>();
221
289
  const executionDefaults: ExecutionMutationDefault[] = [];
222
290
  const models: Record<string, ContractModel> = {};
223
291
  const roots: Record<string, string> = {};
@@ -276,6 +344,11 @@ export function buildSqlContractFromDefinition(
276
344
  }
277
345
  }
278
346
 
347
+ const namespaceId =
348
+ semanticModel.namespaceId !== undefined && semanticModel.namespaceId.length > 0
349
+ ? semanticModel.namespaceId
350
+ : UNBOUND_NAMESPACE_ID;
351
+
279
352
  const foreignKeys = (semanticModel.foreignKeys ?? []).map((fk) => {
280
353
  const targetModel = assertKnownTargetModel(
281
354
  modelsByName,
@@ -289,9 +362,18 @@ export function buildSqlContractFromDefinition(
289
362
  fk.references.table,
290
363
  'Foreign key',
291
364
  );
365
+ const targetNamespaceId =
366
+ fk.references.namespaceId ??
367
+ (targetModel.namespaceId !== undefined && targetModel.namespaceId.length > 0
368
+ ? targetModel.namespaceId
369
+ : UNBOUND_NAMESPACE_ID);
292
370
  return {
293
- columns: fk.columns,
294
- references: { table: fk.references.table, columns: fk.references.columns },
371
+ source: { namespaceId, tableName, columns: fk.columns },
372
+ target: {
373
+ namespaceId: targetNamespaceId,
374
+ tableName: fk.references.table,
375
+ columns: fk.references.columns,
376
+ },
295
377
  ...applyFkDefaults(
296
378
  {
297
379
  ...ifDefined('constraint', fk.constraint),
@@ -305,7 +387,15 @@ export function buildSqlContractFromDefinition(
305
387
  };
306
388
  });
307
389
 
308
- storageTables[tableName] = {
390
+ const existingNs = tableNameToNamespaceId.get(tableName);
391
+ if (existingNs !== undefined && existingNs !== namespaceId) {
392
+ throw new Error(
393
+ `buildSqlContractFromDefinition: table "${tableName}" is mapped in namespace "${namespaceId}" but already exists in namespace "${existingNs}".`,
394
+ );
395
+ }
396
+ tableNameToNamespaceId.set(tableName, namespaceId);
397
+
398
+ const tableInput: StorageTableInput = {
309
399
  columns,
310
400
  uniques: (semanticModel.uniques ?? []).map((u) => ({
311
401
  columns: u.columns,
@@ -328,6 +418,18 @@ export function buildSqlContractFromDefinition(
328
418
  : {}),
329
419
  };
330
420
 
421
+ let nsTables = tablesByNamespace[namespaceId];
422
+ if (nsTables === undefined) {
423
+ nsTables = {};
424
+ tablesByNamespace[namespaceId] = nsTables;
425
+ }
426
+ if (nsTables[tableName] !== undefined) {
427
+ throw new Error(
428
+ `buildSqlContractFromDefinition: duplicate table "${tableName}" in namespace "${namespaceId}".`,
429
+ );
430
+ }
431
+ nsTables[tableName] = new StorageTable(tableInput);
432
+
331
433
  // --- Build contract model ---
332
434
 
333
435
  const storageFields: Record<string, { readonly column: string }> = {};
@@ -414,15 +516,39 @@ export function buildSqlContractFromDefinition(
414
516
  ];
415
517
  }),
416
518
  );
519
+ const { documentTypes, namespaceEnumTypesById } = partitionStorageTypesForTarget(
520
+ target,
521
+ storageTypes,
522
+ definition.namespaceTypes,
523
+ );
524
+ const namespaceCoordinateIds = collectStorageNamespaceCoordinateIds(definition);
525
+ for (const id of Object.keys(namespaceEnumTypesById)) {
526
+ namespaceCoordinateIds.add(id);
527
+ }
528
+ const { createNamespace } = definition;
529
+ const namespaces: Record<string, SqlNamespaceTablesInput | Namespace> = Object.fromEntries(
530
+ [...namespaceCoordinateIds].sort().map((id) => {
531
+ const enumTypes = namespaceEnumTypesById[id];
532
+ const nsInput: SqlNamespaceTablesInput = {
533
+ id,
534
+ tables: tablesByNamespace[id] ?? {},
535
+ ...ifDefined('types', enumTypes),
536
+ };
537
+ return [id, createNamespace ? createNamespace(nsInput) : nsInput];
538
+ }),
539
+ );
417
540
  const storageWithoutHash = {
418
- tables: storageTables,
419
- types: storageTypes,
420
- namespaces: { [UNSPECIFIED_NAMESPACE_ID]: SqlUnspecifiedNamespace.instance },
541
+ types: documentTypes,
542
+ namespaces,
421
543
  };
422
544
  const storageHash: StorageHashBase<string> = definition.storageHash
423
545
  ? coreHash(definition.storageHash)
424
- : computeStorageHash({ target, targetFamily, storage: storageWithoutHash });
425
- const storage = new SqlStorage({ ...storageWithoutHash, storageHash });
546
+ : computeStorageHash({
547
+ target,
548
+ targetFamily,
549
+ storage: storageWithoutHash as Record<string, unknown>,
550
+ });
551
+ const storage = new SqlStorage({ ...storageWithoutHash, storageHash } as SqlStorageInput); // Builder types are wider than SqlStorageInput until SqlStorage normalises document types.
426
552
 
427
553
  const executionSection =
428
554
  executionDefaults.length > 0
@@ -119,7 +119,7 @@ type PackAwareModel<IndexTypes extends IndexTypeMap> = {
119
119
  Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
120
120
  >(
121
121
  modelName: ModelName,
122
- input: { readonly fields: Fields; readonly relations?: Relations },
122
+ input: { readonly fields: Fields; readonly relations?: Relations; readonly namespace?: string },
123
123
  ): ContractModelBuilder<ModelName, Fields, Relations, undefined, undefined, IndexTypes>;
124
124
  <
125
125
  Fields extends Record<string, ScalarFieldBuilder>,
@@ -127,6 +127,7 @@ type PackAwareModel<IndexTypes extends IndexTypeMap> = {
127
127
  >(input: {
128
128
  readonly fields: Fields;
129
129
  readonly relations?: Relations;
130
+ readonly namespace?: string;
130
131
  }): ContractModelBuilder<undefined, Fields, Relations, undefined, undefined, IndexTypes>;
131
132
  };
132
133
 
@@ -5,8 +5,10 @@ import type {
5
5
  FamilyPackRef,
6
6
  TargetPackRef,
7
7
  } from '@prisma-next/framework-components/components';
8
+ import type { Namespace } from '@prisma-next/framework-components/ir';
8
9
  import type {
9
10
  PostgresEnumStorageEntry,
11
+ SqlNamespaceTablesInput,
10
12
  StorageTypeInstance,
11
13
  } from '@prisma-next/sql-contract/types';
12
14
  import { buildSqlContractFromDefinition } from './build-contract';
@@ -35,6 +37,7 @@ export { buildSqlContractFromDefinition } from './build-contract';
35
37
  type ModelLike = {
36
38
  readonly stageOne: {
37
39
  readonly modelName?: string;
40
+ readonly namespace?: string;
38
41
  readonly fields: Record<string, ScalarFieldBuilder>;
39
42
  readonly relations: Record<string, RelationBuilder<RelationState>>;
40
43
  };
@@ -54,6 +57,7 @@ type ContractDefinition<
54
57
  Naming extends ContractInput['naming'] | undefined,
55
58
  StorageHash extends string | undefined,
56
59
  ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined,
60
+ Namespaces extends readonly string[] | undefined = undefined,
57
61
  > = {
58
62
  readonly family: Family;
59
63
  readonly target: Target;
@@ -62,6 +66,8 @@ type ContractDefinition<
62
66
  readonly storageHash?: StorageHash;
63
67
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
64
68
  readonly capabilities?: Capabilities;
69
+ readonly namespaces?: Namespaces;
70
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
65
71
  readonly types?: Types;
66
72
  readonly models?: Models;
67
73
  readonly codecLookup?: CodecLookup;
@@ -75,6 +81,7 @@ type ContractScaffold<
75
81
  Naming extends ContractInput['naming'] | undefined,
76
82
  StorageHash extends string | undefined,
77
83
  ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined,
84
+ Namespaces extends readonly string[] | undefined = undefined,
78
85
  > = {
79
86
  readonly family: Family;
80
87
  readonly target: Target;
@@ -83,6 +90,8 @@ type ContractScaffold<
83
90
  readonly storageHash?: StorageHash;
84
91
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
85
92
  readonly capabilities?: Capabilities;
93
+ readonly namespaces?: Namespaces;
94
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
86
95
  readonly codecLookup?: CodecLookup;
87
96
  };
88
97
 
@@ -114,6 +123,126 @@ function validateTargetPackRef(
114
123
  }
115
124
  }
116
125
 
126
+ /**
127
+ * Per-target reserved namespace names enforced by `defineContract` for
128
+ * SQL family contracts. Two categories:
129
+ *
130
+ * 1. **IR sentinels** (`__unbound__`, `__unspecified__`) — reserved on
131
+ * every SQL target. The double-underscore decoration marks them as
132
+ * framework-reserved coordinates; user code must not declare them
133
+ * explicitly.
134
+ * 2. **Target-specific PSL keywords** — Postgres reserves the bare
135
+ * `unbound` identifier for the late-binding opt-in
136
+ * (`namespace unbound { … }`) so the TS surface must reject it from
137
+ * `defineContract({ namespaces })` lists. SQLite has no schema
138
+ * concept and rejects every non-empty namespaces list outright;
139
+ * callers should declare `namespaces: []` or omit the field.
140
+ */
141
+ function validateNamespaceDeclarations(
142
+ target: TargetPackRef<'sql', string>,
143
+ namespaces: readonly string[] | undefined,
144
+ ): void {
145
+ if (!namespaces) {
146
+ return;
147
+ }
148
+
149
+ if (target.targetId === 'sqlite' && namespaces.length > 0) {
150
+ throw new Error(
151
+ `defineContract: SQLite contracts cannot declare namespaces (SQLite has no schema concept; emitted DDL is always unqualified). Received namespaces: [${namespaces
152
+ .map((name) => `"${name}"`)
153
+ .join(', ')}].`,
154
+ );
155
+ }
156
+
157
+ const seen = new Set<string>();
158
+ for (const namespace of namespaces) {
159
+ if (namespace.length === 0) {
160
+ throw new Error('defineContract: namespace names cannot be empty.');
161
+ }
162
+ if (namespace.trim().length === 0) {
163
+ throw new Error(`defineContract: namespace name "${namespace}" cannot be whitespace-only.`);
164
+ }
165
+ if (namespace === '__unbound__' || namespace === '__unspecified__') {
166
+ throw new Error(
167
+ `defineContract: namespace name "${namespace}" is a reserved IR sentinel and cannot appear in the declared namespaces list.`,
168
+ );
169
+ }
170
+ if (target.targetId === 'postgres' && namespace === 'unbound') {
171
+ throw new Error(
172
+ `defineContract: namespace name "unbound" is reserved by Postgres for the late-binding opt-in (use \`namespace unbound { … }\` in PSL instead of declaring it as a regular schema).`,
173
+ );
174
+ }
175
+ if (seen.has(namespace)) {
176
+ throw new Error(`defineContract: namespaces list contains duplicate entry "${namespace}".`);
177
+ }
178
+ seen.add(namespace);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Per-model `namespace` validation paired with
184
+ * {@link validateNamespaceDeclarations}. Mirrors the reserved-name
185
+ * rules so the per-model surface stays consistent with the contract-
186
+ * level surface:
187
+ *
188
+ * - `__unbound__` / `__unspecified__` — reserved IR sentinels on
189
+ * every SQL target.
190
+ * - `unbound` on Postgres — reserved for the PSL
191
+ * `namespace unbound { … }` opt-in.
192
+ *
193
+ * Additionally enforces that each per-model `namespace` either
194
+ * references an entry in the contract's declared `namespaces` list or
195
+ * names the Postgres late-binding keyword (`unbound`) — the latter is
196
+ * not a "declared namespace" but is a legal opt-in only via PSL today,
197
+ * so the TS surface also rejects it on the per-model side and points
198
+ * authors at the PSL `namespace unbound { … }` block.
199
+ *
200
+ * The SQLite per-model `namespace` field is rejected outright (SQLite
201
+ * has no schema concept).
202
+ */
203
+ function validatePerModelNamespaces(
204
+ target: TargetPackRef<'sql', string>,
205
+ namespaces: readonly string[] | undefined,
206
+ models: Record<string, ModelLike>,
207
+ ): void {
208
+ const declaredNamespaces = new Set<string>(namespaces ?? []);
209
+
210
+ for (const [modelKey, modelBuilder] of Object.entries(models)) {
211
+ const perModelNamespace = modelBuilder.stageOne.namespace;
212
+ if (perModelNamespace === undefined) {
213
+ continue;
214
+ }
215
+
216
+ if (target.targetId === 'sqlite') {
217
+ throw new Error(
218
+ `defineContract: model "${modelKey}" sets \`namespace: "${perModelNamespace}"\` but the target is SQLite (SQLite has no schema concept; remove the per-model \`namespace\` field).`,
219
+ );
220
+ }
221
+
222
+ if (perModelNamespace === '__unbound__' || perModelNamespace === '__unspecified__') {
223
+ throw new Error(
224
+ `defineContract: model "${modelKey}" sets \`namespace: "${perModelNamespace}"\` but that name is a reserved IR sentinel and cannot appear in user code.`,
225
+ );
226
+ }
227
+
228
+ if (target.targetId === 'postgres' && perModelNamespace === 'unbound') {
229
+ throw new Error(
230
+ `defineContract: model "${modelKey}" sets \`namespace: "unbound"\` but that name is reserved by Postgres for the late-binding opt-in (use \`namespace unbound { … }\` in PSL instead — there is no equivalent surface in the TS builder today).`,
231
+ );
232
+ }
233
+
234
+ if (!declaredNamespaces.has(perModelNamespace)) {
235
+ const hint =
236
+ declaredNamespaces.size > 0
237
+ ? ` Declared namespaces: [${[...declaredNamespaces].map((name) => `"${name}"`).join(', ')}].`
238
+ : ' The contract does not declare any namespaces; add `namespaces: ["…"]` to `defineContract` first.';
239
+ throw new Error(
240
+ `defineContract: model "${modelKey}" references namespace "${perModelNamespace}" but that name does not appear in the contract's declared \`namespaces\` list.${hint}`,
241
+ );
242
+ }
243
+ }
244
+ }
245
+
117
246
  function validateExtensionPackRefs(
118
247
  target: TargetPackRef<'sql', string>,
119
248
  extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>,
@@ -152,6 +281,12 @@ function buildContractFromDsl(
152
281
  ): ReturnType<typeof buildSqlContractFromDefinition> {
153
282
  validateTargetPackRef(definition.family, definition.target);
154
283
  validateExtensionPackRefs(definition.target, definition.extensionPacks);
284
+ validateNamespaceDeclarations(definition.target, definition.namespaces);
285
+ validatePerModelNamespaces(
286
+ definition.target,
287
+ definition.namespaces,
288
+ (definition.models ?? {}) as Record<string, ModelLike>,
289
+ );
155
290
 
156
291
  return buildSqlContractFromDefinition(
157
292
  buildContractDefinition(definition),
@@ -174,6 +309,7 @@ export function defineContract<
174
309
  const Naming extends ContractInput['naming'] | undefined = undefined,
175
310
  const StorageHash extends string | undefined = undefined,
176
311
  const ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
312
+ const Namespaces extends readonly string[] | undefined = undefined,
177
313
  >(
178
314
  definition: ContractDefinition<
179
315
  Family,
@@ -184,7 +320,8 @@ export function defineContract<
184
320
  Capabilities,
185
321
  Naming,
186
322
  StorageHash,
187
- ForeignKeyDefaults
323
+ ForeignKeyDefaults,
324
+ Namespaces
188
325
  >,
189
326
  ): SqlContractResult<
190
327
  ContractDefinition<
@@ -196,7 +333,8 @@ export function defineContract<
196
333
  Capabilities,
197
334
  Naming,
198
335
  StorageHash,
199
- ForeignKeyDefaults
336
+ ForeignKeyDefaults,
337
+ Namespaces
200
338
  >
201
339
  >;
202
340
  export function defineContract<
@@ -214,6 +352,7 @@ export function defineContract<
214
352
  const Naming extends ContractInput['naming'] | undefined = undefined,
215
353
  const StorageHash extends string | undefined = undefined,
216
354
  const ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
355
+ const Namespaces extends readonly string[] | undefined = undefined,
217
356
  >(
218
357
  definition: ContractScaffold<
219
358
  Family,
@@ -222,7 +361,8 @@ export function defineContract<
222
361
  Capabilities,
223
362
  Naming,
224
363
  StorageHash,
225
- ForeignKeyDefaults
364
+ ForeignKeyDefaults,
365
+ Namespaces
226
366
  >,
227
367
  factory: ContractFactory<Family, Target, Types, Models, ExtensionPacks>,
228
368
  ): SqlContractResult<
@@ -235,7 +375,8 @@ export function defineContract<
235
375
  Capabilities,
236
376
  Naming,
237
377
  StorageHash,
238
- ForeignKeyDefaults
378
+ ForeignKeyDefaults,
379
+ Namespaces
239
380
  >
240
381
  >;
241
382
  export function defineContract(
@@ -2,9 +2,11 @@ import type { ColumnDefault, ExecutionMutationDefaultPhases } from '@prisma-next
2
2
  import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
3
3
  import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
4
4
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
5
+ import type { Namespace } from '@prisma-next/framework-components/ir';
5
6
  import type {
6
7
  PostgresEnumStorageEntry,
7
8
  ReferentialAction,
9
+ SqlNamespaceTablesInput,
8
10
  StorageTypeInstance,
9
11
  } from '@prisma-next/sql-contract/types';
10
12
 
@@ -43,6 +45,13 @@ export interface ForeignKeyNode {
43
45
  readonly model: string;
44
46
  readonly table: string;
45
47
  readonly columns: readonly string[];
48
+ /**
49
+ * Namespace coordinate of the referenced table. When omitted the
50
+ * assembler resolves the coordinate from the referenced model node's
51
+ * own `namespaceId`; the field exists so authoring paths that already
52
+ * know the target namespace can stamp it explicitly.
53
+ */
54
+ readonly namespaceId?: string;
46
55
  };
47
56
  readonly name?: string;
48
57
  readonly onDelete?: ReferentialAction;
@@ -87,6 +96,17 @@ export interface ValueObjectNode {
87
96
  export interface ModelNode {
88
97
  readonly modelName: string;
89
98
  readonly tableName: string;
99
+ /**
100
+ * Resolved namespace coordinate for this model — the key into the
101
+ * parent contract's `SqlStorage.namespaces` map. Omitting the field
102
+ * (or setting it to the framework's `UNBOUND_NAMESPACE_ID` sentinel)
103
+ * selects the late-bound slot, which renders as unqualified DDL.
104
+ *
105
+ * Populated by per-target PSL interpreters from the resolved
106
+ * `namespace { … }` AST bucket; the TS builder also sets it from the
107
+ * per-model `namespace` field once that authoring surface lands.
108
+ */
109
+ readonly namespaceId?: string;
90
110
  readonly fields: readonly (FieldNode | ValueObjectFieldNode)[];
91
111
  readonly id?: PrimaryKeyNode;
92
112
  readonly uniques?: readonly UniqueConstraintNode[];
@@ -102,6 +122,26 @@ export interface ContractDefinition {
102
122
  readonly storageHash?: string;
103
123
  readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
104
124
  readonly storageTypes?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
125
+ /**
126
+ * Enum types declared inside a named `namespace { enum … }` block,
127
+ * keyed first by namespace id then by type name. These are routed to
128
+ * `storage.namespaces[nsId].types` rather than the implicit fallback
129
+ * namespace used for top-level `storageTypes` enums.
130
+ */
131
+ readonly namespaceTypes?: Readonly<
132
+ Record<string, Readonly<Record<string, PostgresEnumStorageEntry>>>
133
+ >;
134
+ /**
135
+ * Declared namespace coordinates for this contract — populates
136
+ * `SqlStorage.namespaces` together with `createNamespace`.
137
+ */
138
+ readonly namespaces?: readonly string[];
139
+ /**
140
+ * Target-supplied factory that materialises a `Namespace` concretion
141
+ * for a declared namespace coordinate. Mirrors
142
+ * `ContractInput.createNamespace`.
143
+ */
144
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
105
145
  readonly models: readonly ModelNode[];
106
146
  readonly valueObjects?: readonly ValueObjectNode[];
107
147
  }