@prisma-next/sql-contract-ts 0.12.0 → 0.13.0-dev.2

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.
@@ -1,6 +1,7 @@
1
1
  import { pathToFileURL } from 'node:url';
2
2
  import type { ContractConfig } from '@prisma-next/config/config-types';
3
- import type { Contract } from '@prisma-next/contract/types';
3
+ import { applySpecifierDefaultControlPolicy } from '@prisma-next/contract/apply-specifier-default-control-policy';
4
+ import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
4
5
  import type { TargetPackRef } from '@prisma-next/framework-components/components';
5
6
  import { ifDefined } from '@prisma-next/utils/defined';
6
7
  import { ok } from '@prisma-next/utils/result';
@@ -19,22 +20,35 @@ function defaultOutputFromContractPath(contractPath: string): string {
19
20
  return `${contractPath.slice(0, -ext.length)}.json`;
20
21
  }
21
22
 
23
+ export interface TypeScriptContractSpecifierOptions {
24
+ readonly defaultControlPolicy?: ControlPolicy;
25
+ }
26
+
22
27
  export function emptyContract(options: {
23
28
  readonly output?: string;
24
29
  readonly target: TargetPackRef<'sql', string>;
30
+ readonly defaultControlPolicy?: ControlPolicy;
25
31
  }): ContractConfig {
26
32
  return {
27
33
  source: {
28
- load: async () => ok(buildSqlContractFromDefinition({ target: options.target, models: [] })),
34
+ load: async () => {
35
+ const built = buildSqlContractFromDefinition({ target: options.target, models: [] });
36
+ return ok(applySpecifierDefaultControlPolicy(built, options.defaultControlPolicy));
37
+ },
29
38
  },
30
39
  ...ifDefined('output', options.output),
31
40
  };
32
41
  }
33
42
 
34
- export function typescriptContract(contract: Contract, output?: string): ContractConfig {
43
+ export function typescriptContract(
44
+ contract: Contract,
45
+ output?: string,
46
+ options?: TypeScriptContractSpecifierOptions,
47
+ ): ContractConfig {
35
48
  return {
36
49
  source: {
37
- load: async () => ok(contract),
50
+ load: async () =>
51
+ ok(applySpecifierDefaultControlPolicy(contract, options?.defaultControlPolicy)),
38
52
  },
39
53
  // The in-memory variant has no input path to anchor on; fall through to
40
54
  // the global default in `normalizeContractConfig` when caller doesn't pin it.
@@ -42,7 +56,11 @@ export function typescriptContract(contract: Contract, output?: string): Contrac
42
56
  };
43
57
  }
44
58
 
45
- export function typescriptContractFromPath(contractPath: string, output?: string): ContractConfig {
59
+ export function typescriptContractFromPath(
60
+ contractPath: string,
61
+ output?: string,
62
+ options?: TypeScriptContractSpecifierOptions,
63
+ ): ContractConfig {
46
64
  return {
47
65
  source: {
48
66
  inputs: [contractPath],
@@ -60,7 +78,7 @@ export function typescriptContractFromPath(contractPath: string, output?: string
60
78
  `typescriptContractFromPath: module at "${absolutePath}" has no "default" or "contract" export.`,
61
79
  );
62
80
  }
63
- return ok(contract);
81
+ return ok(applySpecifierDefaultControlPolicy(contract, options?.defaultControlPolicy));
64
82
  },
65
83
  },
66
84
  output: output ?? defaultOutputFromContractPath(contractPath),
@@ -1,3 +1,4 @@
1
+ import type { ControlPolicy } from '@prisma-next/contract/types';
1
2
  import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
2
3
  import type { CodecLookup } from '@prisma-next/framework-components/codec';
3
4
  import type {
@@ -12,6 +13,7 @@ import type {
12
13
  StorageTypeInstance,
13
14
  } from '@prisma-next/sql-contract/types';
14
15
  import { blindCast } from '@prisma-next/utils/casts';
16
+ import { ifDefined } from '@prisma-next/utils/defined';
15
17
  import { buildSqlContractFromDefinition } from './build-contract';
16
18
  import {
17
19
  type ComposedAuthoringHelpers,
@@ -20,6 +22,7 @@ import {
20
22
  import {
21
23
  type ContractInput,
22
24
  type ContractModelBuilder,
25
+ extensionModel,
23
26
  field,
24
27
  isContractInput,
25
28
  type ModelAttributesSpec,
@@ -32,6 +35,7 @@ import {
32
35
  } from './contract-dsl';
33
36
  import { buildContractDefinition } from './contract-lowering';
34
37
  import type { SqlContractResult } from './contract-types';
38
+ import type { EnumTypeHandle } from './enum-type';
35
39
 
36
40
  export { buildSqlContractFromDefinition } from './build-contract';
37
41
 
@@ -65,11 +69,13 @@ type ContractDefinition<
65
69
  readonly naming?: Naming;
66
70
  readonly storageHash?: StorageHash;
67
71
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
72
+ readonly defaultControlPolicy?: ControlPolicy;
68
73
  readonly namespaces?: Namespaces;
69
74
  readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
70
75
  readonly types?: Types;
71
76
  readonly models?: Models;
72
77
  readonly codecLookup?: CodecLookup;
78
+ readonly enums?: Record<string, EnumTypeHandle>;
73
79
  };
74
80
 
75
81
  type ContractScaffold<
@@ -87,11 +93,13 @@ type ContractScaffold<
87
93
  readonly naming?: Naming;
88
94
  readonly storageHash?: StorageHash;
89
95
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
96
+ readonly defaultControlPolicy?: ControlPolicy;
90
97
  readonly namespaces?: Namespaces;
91
98
  readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
92
99
  readonly types?: never;
93
100
  readonly models?: never;
94
101
  readonly codecLookup?: CodecLookup;
102
+ readonly enums?: Record<string, EnumTypeHandle>;
95
103
  };
96
104
 
97
105
  type ContractFactory<
@@ -289,6 +297,131 @@ function buildContractFromDsl<Definition extends ContractInput>(
289
297
  >(buildSqlContractFromDefinition(buildContractDefinition(definition), definition.codecLookup));
290
298
  }
291
299
 
300
+ // Input for buildBoundContract — all fields from ContractInput except family/target
301
+ // (those are injected by the builder, pre-bound at the call site).
302
+ type BoundDefinitionInput<
303
+ Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = Record<
304
+ never,
305
+ never
306
+ >,
307
+ Models extends Record<string, ModelLike> = Record<never, never>,
308
+ ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined = undefined,
309
+ Naming extends ContractInput['naming'] | undefined = undefined,
310
+ StorageHash extends string | undefined = undefined,
311
+ ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
312
+ Namespaces extends readonly string[] | undefined = undefined,
313
+ > = {
314
+ readonly extensionPacks?: ExtensionPacks;
315
+ readonly naming?: Naming;
316
+ readonly storageHash?: StorageHash;
317
+ readonly foreignKeyDefaults?: ForeignKeyDefaults;
318
+ readonly defaultControlPolicy?: ControlPolicy;
319
+ readonly namespaces?: Namespaces;
320
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
321
+ readonly types?: Types;
322
+ readonly models?: Models;
323
+ readonly codecLookup?: CodecLookup;
324
+ readonly enums?: Record<string, EnumTypeHandle>;
325
+ };
326
+
327
+ // Merges a bound input with the pre-bound family/target to produce a full ContractDefinition.
328
+ type WithFamilyTarget<
329
+ Input,
330
+ F extends FamilyPackRef<string>,
331
+ T extends TargetPackRef<'sql', string>,
332
+ > = Input & { readonly family: F; readonly target: T };
333
+
334
+ /**
335
+ * Shared builder that assembles a SqlContract with pre-bound family and target.
336
+ * Extension wrappers keep their own public overloads and delegate their impl body here;
337
+ * this is a plain overloaded function (not a factory returning an overloaded function)
338
+ * so no overloaded-function-return cast is needed.
339
+ *
340
+ * Overload 1: definition form (no factory).
341
+ */
342
+ export function buildBoundContract<
343
+ const F extends FamilyPackRef<string>,
344
+ const T extends TargetPackRef<'sql', string>,
345
+ const Definition extends BoundDefinitionInput<
346
+ Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
347
+ Record<string, ModelLike>,
348
+ Record<string, ExtensionPackRef<'sql', string>> | undefined,
349
+ ContractInput['naming'] | undefined,
350
+ string | undefined,
351
+ ForeignKeyDefaultsState | undefined,
352
+ readonly string[] | undefined
353
+ >,
354
+ >(
355
+ family: F,
356
+ target: T,
357
+ definition: Definition,
358
+ factory?: undefined,
359
+ ): SqlContractResult<WithFamilyTarget<Definition, F, T>>;
360
+ /**
361
+ * Overload 2: factory form.
362
+ */
363
+ export function buildBoundContract<
364
+ const F extends FamilyPackRef<string>,
365
+ const T extends TargetPackRef<'sql', string>,
366
+ const Definition extends BoundDefinitionInput<
367
+ Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
368
+ Record<string, ModelLike>,
369
+ Record<string, ExtensionPackRef<'sql', string>> | undefined,
370
+ ContractInput['naming'] | undefined,
371
+ string | undefined,
372
+ ForeignKeyDefaultsState | undefined,
373
+ readonly string[] | undefined
374
+ >,
375
+ const Built extends {
376
+ readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
377
+ readonly models?: Record<string, ModelLike>;
378
+ },
379
+ >(
380
+ family: F,
381
+ target: T,
382
+ definition: Definition,
383
+ factory: (
384
+ helpers: ComposedAuthoringHelpers<F, T, NonNullable<Definition['extensionPacks']>>,
385
+ ) => Built,
386
+ ): SqlContractResult<WithFamilyTarget<Definition & Built, F, T>>;
387
+ /** Implementation. */
388
+ export function buildBoundContract(
389
+ family: FamilyPackRef<string>,
390
+ target: TargetPackRef<'sql', string>,
391
+ definition: Omit<ContractInput, 'family' | 'target'>,
392
+ factory?:
393
+ | ((
394
+ helpers: ComposedAuthoringHelpers<
395
+ FamilyPackRef<string>,
396
+ TargetPackRef<'sql', string>,
397
+ Record<string, ExtensionPackRef<'sql', string>> | undefined
398
+ >,
399
+ ) => {
400
+ readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
401
+ readonly models?: Record<string, ModelLike>;
402
+ })
403
+ | undefined,
404
+ ) {
405
+ const full = { ...definition, family, target };
406
+
407
+ if (factory !== undefined) {
408
+ const built = factory(
409
+ createComposedAuthoringHelpers({
410
+ family,
411
+ target,
412
+ extensionPacks: definition.extensionPacks,
413
+ }),
414
+ );
415
+ return buildContractFromDsl({
416
+ ...full,
417
+ ...ifDefined('types', built.types),
418
+ ...ifDefined('models', built.models),
419
+ });
420
+ }
421
+
422
+ return buildContractFromDsl(full);
423
+ }
424
+
292
425
  export function defineContract<
293
426
  const Family extends FamilyPackRef<string>,
294
427
  const Target extends TargetPackRef<'sql', string>,
@@ -384,22 +517,10 @@ export function defineContract(
384
517
  );
385
518
  }
386
519
 
387
- if (!factory) {
388
- return buildContractFromDsl(definition);
520
+ if (factory !== undefined) {
521
+ return buildBoundContract(definition.family, definition.target, definition, factory);
389
522
  }
390
-
391
- const builtDefinition = {
392
- ...definition,
393
- ...factory(
394
- createComposedAuthoringHelpers({
395
- family: definition.family,
396
- target: definition.target,
397
- extensionPacks: definition.extensionPacks,
398
- }),
399
- ),
400
- };
401
-
402
- return buildContractFromDsl(builtDefinition);
523
+ return buildBoundContract(definition.family, definition.target, definition);
403
524
  }
404
525
 
405
526
  export type {
@@ -409,4 +530,4 @@ export type {
409
530
  ModelLike,
410
531
  ScalarFieldBuilder,
411
532
  };
412
- export { field, model, rel };
533
+ export { extensionModel, field, model, rel };
@@ -1,4 +1,8 @@
1
- import type { ColumnDefault, ExecutionMutationDefaultPhases } from '@prisma-next/contract/types';
1
+ import type {
2
+ ColumnDefault,
3
+ ControlPolicy,
4
+ ExecutionMutationDefaultPhases,
5
+ } from '@prisma-next/contract/types';
2
6
  import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
3
7
  import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
4
8
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
@@ -9,6 +13,7 @@ import type {
9
13
  SqlNamespaceTablesInput,
10
14
  StorageTypeInstance,
11
15
  } from '@prisma-next/sql-contract/types';
16
+ import type { EnumTypeHandle } from './enum-type';
12
17
 
13
18
  export type { ExecutionMutationDefaultPhases };
14
19
 
@@ -20,6 +25,8 @@ export interface FieldNode {
20
25
  readonly default?: ColumnDefault;
21
26
  readonly executionDefaults?: ExecutionMutationDefaultPhases;
22
27
  readonly many?: boolean;
28
+ /** Present when the field was authored with `field.namedType(enumHandle)`. */
29
+ readonly enumTypeHandle?: EnumTypeHandle;
23
30
  }
24
31
 
25
32
  export interface PrimaryKeyNode {
@@ -52,6 +59,12 @@ export interface ForeignKeyNode {
52
59
  * know the target namespace can stamp it explicitly.
53
60
  */
54
61
  readonly namespaceId?: string;
62
+ /**
63
+ * Contract-space identity of the referenced table. When present, the
64
+ * table lives in a different contract space (identified by this value)
65
+ * rather than the current contract. Absent for local FKs.
66
+ */
67
+ readonly spaceId?: string;
55
68
  };
56
69
  readonly name?: string;
57
70
  readonly onDelete?: ReferentialAction;
@@ -64,7 +77,26 @@ export interface RelationNode {
64
77
  readonly fieldName: string;
65
78
  readonly toModel: string;
66
79
  readonly toTable: string;
80
+ /**
81
+ * Namespace coordinate of the related model. When omitted the assembler
82
+ * resolves the coordinate from the referenced model node's own
83
+ * `namespaceId`; the field exists so authoring paths that already know the
84
+ * target namespace can stamp it explicitly — required to disambiguate a
85
+ * relation to a model whose bare name also exists in another namespace.
86
+ */
87
+ readonly toNamespaceId?: string;
67
88
  readonly cardinality: '1:1' | '1:N' | 'N:1' | 'N:M';
89
+ /**
90
+ * Contract-space identity of the related model. When present, the
91
+ * related model lives in a different contract space. Absent for local
92
+ * (same-space) relations.
93
+ */
94
+ readonly spaceId?: string;
95
+ /**
96
+ * Namespace coordinate of the related model in the foreign space.
97
+ * Only set when `spaceId` is present.
98
+ */
99
+ readonly namespaceId?: string;
68
100
  readonly on: {
69
101
  readonly parentTable: string;
70
102
  readonly parentColumns: readonly string[];
@@ -113,10 +145,20 @@ export interface ModelNode {
113
145
  readonly indexes?: readonly IndexNode[];
114
146
  readonly foreignKeys?: readonly ForeignKeyNode[];
115
147
  readonly relations?: readonly RelationNode[];
148
+ readonly control?: ControlPolicy;
149
+ /**
150
+ * Single-table-inheritance variants share their base model's storage table:
151
+ * the variant's columns are materialised onto the base `ModelNode`, and this
152
+ * model contributes a domain model but no storage table of its own. When set,
153
+ * the assembler builds the domain model but skips creating a (shadow) storage
154
+ * table and a root for this model — the base owns both.
155
+ */
156
+ readonly sharesBaseTable?: boolean;
116
157
  }
117
158
 
118
159
  export interface ContractDefinition {
119
160
  readonly target: TargetPackRef<'sql', string>;
161
+ readonly defaultControlPolicy?: ControlPolicy;
120
162
  readonly extensionPacks?: Record<string, ExtensionPackRef<'sql', string>>;
121
163
  readonly storageHash?: string;
122
164
  readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
@@ -124,7 +166,7 @@ export interface ContractDefinition {
124
166
  /**
125
167
  * Enum types declared inside a named `namespace { enum … }` block,
126
168
  * keyed first by namespace id then by type name. These are routed to
127
- * `storage.namespaces[nsId].enum` rather than the implicit fallback
169
+ * `storage.namespaces[nsId].entries.type` rather than the implicit fallback
128
170
  * namespace used for top-level `storageTypes` enums.
129
171
  */
130
172
  readonly namespaceTypes?: Readonly<
@@ -139,8 +181,20 @@ export interface ContractDefinition {
139
181
  * Target-supplied factory that materialises a `Namespace` concretion
140
182
  * for a declared namespace coordinate. Mirrors
141
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).
142
187
  */
143
- readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
188
+ readonly createNamespace?: (
189
+ input: SqlNamespaceTablesInput,
190
+ enumTypes?: Readonly<Record<string, PostgresEnumStorageEntry>>,
191
+ ) => Namespace;
144
192
  readonly models: readonly ModelNode[];
145
193
  readonly valueObjects?: readonly ValueObjectNode[];
194
+ /**
195
+ * Domain enum handles authored via `enumType()`. Each entry lowers to a
196
+ * domain `enum` entry and a storage `valueSet` entry in the contract's
197
+ * default namespace.
198
+ */
199
+ readonly enums?: Record<string, EnumTypeHandle>;
146
200
  }