@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.
@@ -14,8 +14,10 @@ import type {
14
14
  FamilyPackRef,
15
15
  TargetPackRef,
16
16
  } from '@prisma-next/framework-components/components';
17
+ import type { Namespace } from '@prisma-next/framework-components/ir';
17
18
  import type {
18
19
  PostgresEnumStorageEntry,
20
+ SqlNamespaceTablesInput,
19
21
  StorageTypeInstance,
20
22
  } from '@prisma-next/sql-contract/types';
21
23
  import { ifDefined } from '@prisma-next/utils/defined';
@@ -998,6 +1000,7 @@ export class ContractModelBuilder<
998
1000
  constructor(
999
1001
  readonly stageOne: {
1000
1002
  readonly modelName?: ModelName;
1003
+ readonly namespace?: string;
1001
1004
  readonly fields: Fields;
1002
1005
  readonly relations: Relations;
1003
1006
  },
@@ -1215,6 +1218,51 @@ export type ContractInput<
1215
1218
  readonly storageHash?: string;
1216
1219
  readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
1217
1220
  readonly capabilities?: Capabilities;
1221
+ /**
1222
+ * Declared namespace coordinates the contract recognises. Per-model
1223
+ * `namespace` references must reference an entry in this list (or the
1224
+ * Postgres-specific late-binding keyword `unbound`). Reserved values:
1225
+ *
1226
+ * - `__unbound__` — IR sentinel for the late-binding slot.
1227
+ * - `__unspecified__` — parser-synthesised AST bucket for top-level
1228
+ * declarations (not a real namespace).
1229
+ * - `unbound` — Postgres-specific reserved keyword (the PSL surface
1230
+ * uses `namespace unbound { … }` to opt into late binding).
1231
+ *
1232
+ * SQLite contracts must declare an empty list (or omit the field) —
1233
+ * SQLite has no schema concept and emits unqualified DDL.
1234
+ *
1235
+ * Populates `SqlStorage.namespaces` together with the
1236
+ * {@link ContractInput.createNamespace} factory: each declared name
1237
+ * (plus the framework-reserved `UNBOUND_NAMESPACE_ID` sentinel) is
1238
+ * resolved through `createNamespace` and stored as the matching slot
1239
+ * value. Models reference declared namespaces via their per-model
1240
+ * `namespace` coordinate; entries that go unreferenced still occupy a
1241
+ * slot so contracts that pre-declare schemas surface them on the live
1242
+ * storage walk.
1243
+ */
1244
+ readonly namespaces?: readonly string[];
1245
+ /**
1246
+ * Target-supplied factory that materialises a `Namespace` concretion
1247
+ * for a declared namespace coordinate. The SQL family layer is
1248
+ * target-agnostic and cannot import concretions like
1249
+ * `PostgresSchema` or `SqliteUnboundDatabase`; the factory is the
1250
+ * seam by which target packs hand the family the right runtime
1251
+ * representation.
1252
+ *
1253
+ * Called once per distinct namespace id discovered in the contract:
1254
+ * each entry of {@link ContractInput.namespaces}, every
1255
+ * `StorageTable.namespaceId` referenced by a model, and the
1256
+ * framework `UNBOUND_NAMESPACE_ID` sentinel (always present so the
1257
+ * late-bound slot stays available regardless of authoring choices).
1258
+ *
1259
+ * When omitted, the family layer falls back to its placeholder
1260
+ * `SqlUnboundNamespace` singleton for the unbound slot and rejects
1261
+ * any non-unbound coordinate — single-namespace contracts authored
1262
+ * before targets ship their factory stay byte-stable; multi-namespace
1263
+ * contracts must pass the factory through.
1264
+ */
1265
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
1218
1266
  readonly types?: Types;
1219
1267
  readonly models?: Models;
1220
1268
  readonly codecLookup?: CodecLookup;
@@ -1229,6 +1277,7 @@ export function model<
1229
1277
  input: {
1230
1278
  readonly fields: Fields;
1231
1279
  readonly relations?: Relations;
1280
+ readonly namespace?: string;
1232
1281
  },
1233
1282
  ): ContractModelBuilder<ModelName, Fields, Relations>;
1234
1283
 
@@ -1238,6 +1287,7 @@ export function model<
1238
1287
  >(input: {
1239
1288
  readonly fields: Fields;
1240
1289
  readonly relations?: Relations;
1290
+ readonly namespace?: string;
1241
1291
  }): ContractModelBuilder<undefined, Fields, Relations>;
1242
1292
 
1243
1293
  export function model<
@@ -1250,10 +1300,12 @@ export function model<
1250
1300
  | {
1251
1301
  readonly fields: Fields;
1252
1302
  readonly relations?: Relations;
1303
+ readonly namespace?: string;
1253
1304
  },
1254
1305
  maybeInput?: {
1255
1306
  readonly fields: Fields;
1256
1307
  readonly relations?: Relations;
1308
+ readonly namespace?: string;
1257
1309
  },
1258
1310
  ): ContractModelBuilder<ModelName | undefined, Fields, Relations> {
1259
1311
  const input = typeof modelNameOrInput === 'string' ? maybeInput : modelNameOrInput;
@@ -1264,6 +1316,7 @@ export function model<
1264
1316
 
1265
1317
  return new ContractModelBuilder({
1266
1318
  ...(typeof modelNameOrInput === 'string' ? { modelName: modelNameOrInput } : {}),
1319
+ ...(input.namespace !== undefined ? { namespace: input.namespace } : {}),
1267
1320
  fields: input.fields,
1268
1321
  relations: (input.relations ?? {}) as Relations,
1269
1322
  });
@@ -47,6 +47,7 @@ type RuntimeModel = ContractModelBuilder<
47
47
  type RuntimeModelSpec = {
48
48
  readonly modelName: string;
49
49
  readonly tableName: string;
50
+ readonly namespace: string | undefined;
50
51
  readonly fieldBuilders: Record<string, ScalarFieldBuilder>;
51
52
  readonly fieldToColumn: Record<string, string>;
52
53
  readonly relations: Record<string, RelationBuilder<RelationState>>;
@@ -595,6 +596,7 @@ function resolveModelNode(
595
596
  return {
596
597
  modelName: spec.modelName,
597
598
  tableName: spec.tableName,
599
+ ...(spec.namespace !== undefined ? { namespaceId: spec.namespace } : {}),
598
600
  fields,
599
601
  ...(idConstraint
600
602
  ? {
@@ -668,6 +670,7 @@ function collectRuntimeModelSpecs(definition: ContractInput): RuntimeCollection
668
670
  modelSpecs.set(modelName, {
669
671
  modelName,
670
672
  tableName,
673
+ namespace: modelDefinition.stageOne.namespace,
671
674
  fieldBuilders,
672
675
  fieldToColumn,
673
676
  relations: modelDefinition.stageOne.relations,
@@ -711,6 +714,8 @@ export function buildContractDefinition(definition: ContractInput): ContractDefi
711
714
  ...(Object.keys(collection.storageTypes).length > 0
712
715
  ? { storageTypes: collection.storageTypes }
713
716
  : {}),
717
+ ...(definition.namespaces ? { namespaces: definition.namespaces } : {}),
718
+ ...(definition.createNamespace ? { createNamespace: definition.createNamespace } : {}),
714
719
  models,
715
720
  };
716
721
  }
@@ -5,7 +5,6 @@ import type {
5
5
  StorageHashBase,
6
6
  } from '@prisma-next/contract/types';
7
7
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
8
- import type { Namespace } from '@prisma-next/framework-components/ir';
9
8
  import type { IndexTypeRegistration } from '@prisma-next/sql-contract/index-types';
10
9
  import type {
11
10
  ContractWithTypeMaps,
@@ -107,6 +106,16 @@ type DefinitionModels<Definition> = Definition extends {
107
106
  : Record<never, never>
108
107
  : Record<never, never>;
109
108
 
109
+ type DefinitionNamespaces<Definition> = Definition extends {
110
+ readonly namespaces?: infer Names extends readonly string[];
111
+ }
112
+ ? string[] extends Names
113
+ ? never
114
+ : readonly string[] extends Names
115
+ ? never
116
+ : Names[number]
117
+ : never;
118
+
110
119
  type DefinitionTypes<Definition> = Definition extends {
111
120
  readonly types?: unknown;
112
121
  }
@@ -499,8 +508,16 @@ type BuiltStorageTables<Definition> = {
499
508
  }>;
500
509
  readonly indexes: ReadonlyArray<Index>;
501
510
  readonly foreignKeys: ReadonlyArray<{
502
- readonly columns: readonly string[];
503
- readonly references: { readonly table: string; readonly columns: readonly string[] };
511
+ readonly source: {
512
+ readonly namespaceId: string;
513
+ readonly tableName: string;
514
+ readonly columns: readonly string[];
515
+ };
516
+ readonly target: {
517
+ readonly namespaceId: string;
518
+ readonly tableName: string;
519
+ readonly columns: readonly string[];
520
+ };
504
521
  readonly name?: string;
505
522
  readonly onDelete?: ReferentialAction;
506
523
  readonly onUpdate?: ReferentialAction;
@@ -521,11 +538,39 @@ type BuiltStorageTables<Definition> = {
521
538
  : Record<string, never>);
522
539
  };
523
540
 
541
+ type BuiltStorageTypes<Definition> = {
542
+ readonly [K in keyof DefinitionTypes<Definition> as DefinitionTypes<Definition>[K] extends PostgresEnumStorageEntry
543
+ ? never
544
+ : K]: DefinitionTypes<Definition>[K];
545
+ };
546
+
524
547
  type BuiltStorage<Definition> = {
525
548
  readonly storageHash: StorageHashBase<string>;
526
- readonly tables: BuiltStorageTables<Definition>;
527
- readonly types: DefinitionTypes<Definition>;
528
- readonly namespaces: Readonly<Record<string, Namespace>>;
549
+ readonly types: BuiltStorageTypes<Definition>;
550
+ // SQL contracts always carry a literal `__unbound__` namespace whose tables
551
+ // slot is narrowed to the actual built table shape so downstream DSL
552
+ // surfaces (TableProxyContract, Ref, SelectBuilder) keep literal-keyed
553
+ // access without an optional-narrowing dance. The shape is described
554
+ // inline (rather than intersecting with `SqlStorage['namespaces']`) so
555
+ // its `Readonly<Record<string, Namespace>>` index signature doesn't
556
+ // collapse `keyof tables` to `string`. The literal object is still
557
+ // structurally assignable to `SqlStorage['namespaces']` because every
558
+ // value satisfies the framework `Namespace` interface.
559
+ readonly namespaces: {
560
+ readonly __unbound__: {
561
+ readonly id: '__unbound__';
562
+ readonly kind?: string;
563
+ readonly tables: BuiltStorageTables<Definition>;
564
+ readonly types?: Readonly<Record<string, PostgresEnumStorageEntry>>;
565
+ };
566
+ } & {
567
+ readonly [Ns in Exclude<DefinitionNamespaces<Definition>, '__unbound__'>]: {
568
+ readonly id: Ns;
569
+ readonly kind?: string;
570
+ readonly tables: Record<never, never>;
571
+ readonly types?: Readonly<Record<string, PostgresEnumStorageEntry>>;
572
+ };
573
+ };
529
574
  };
530
575
 
531
576
  type FieldOutputType<