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

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
 
@@ -58,6 +62,7 @@ type ContractDefinition<
58
62
  StorageHash extends string | undefined,
59
63
  ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined,
60
64
  Namespaces extends readonly string[] | undefined = undefined,
65
+ Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
61
66
  > = {
62
67
  readonly family: Family;
63
68
  readonly target: Target;
@@ -65,11 +70,13 @@ type ContractDefinition<
65
70
  readonly naming?: Naming;
66
71
  readonly storageHash?: StorageHash;
67
72
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
73
+ readonly defaultControlPolicy?: ControlPolicy;
68
74
  readonly namespaces?: Namespaces;
69
75
  readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
70
76
  readonly types?: Types;
71
77
  readonly models?: Models;
72
78
  readonly codecLookup?: CodecLookup;
79
+ readonly enums?: Enums;
73
80
  };
74
81
 
75
82
  type ContractScaffold<
@@ -80,6 +87,7 @@ type ContractScaffold<
80
87
  StorageHash extends string | undefined,
81
88
  ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined,
82
89
  Namespaces extends readonly string[] | undefined = undefined,
90
+ Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
83
91
  > = {
84
92
  readonly family: Family;
85
93
  readonly target: Target;
@@ -87,11 +95,13 @@ type ContractScaffold<
87
95
  readonly naming?: Naming;
88
96
  readonly storageHash?: StorageHash;
89
97
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
98
+ readonly defaultControlPolicy?: ControlPolicy;
90
99
  readonly namespaces?: Namespaces;
91
100
  readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
92
101
  readonly types?: never;
93
102
  readonly models?: never;
94
103
  readonly codecLookup?: CodecLookup;
104
+ readonly enums?: Enums;
95
105
  };
96
106
 
97
107
  type ContractFactory<
@@ -100,9 +110,11 @@ type ContractFactory<
100
110
  Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
101
111
  Models extends Record<string, ModelLike>,
102
112
  ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined,
113
+ Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
103
114
  > = (helpers: ComposedAuthoringHelpers<Family, Target, ExtensionPacks>) => {
104
115
  readonly types?: Types;
105
116
  readonly models?: Models;
117
+ readonly enums?: Enums;
106
118
  };
107
119
 
108
120
  function validateTargetPackRef(
@@ -289,6 +301,149 @@ function buildContractFromDsl<Definition extends ContractInput>(
289
301
  >(buildSqlContractFromDefinition(buildContractDefinition(definition), definition.codecLookup));
290
302
  }
291
303
 
304
+ // Input for buildBoundContract — all fields from ContractInput except family/target
305
+ // (those are injected by the builder, pre-bound at the call site).
306
+ type BoundDefinitionInput<
307
+ Types extends Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = Record<
308
+ never,
309
+ never
310
+ >,
311
+ Models extends Record<string, ModelLike> = Record<never, never>,
312
+ ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined = undefined,
313
+ Naming extends ContractInput['naming'] | undefined = undefined,
314
+ StorageHash extends string | undefined = undefined,
315
+ ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
316
+ Namespaces extends readonly string[] | undefined = undefined,
317
+ > = {
318
+ readonly extensionPacks?: ExtensionPacks;
319
+ readonly naming?: Naming;
320
+ readonly storageHash?: StorageHash;
321
+ readonly foreignKeyDefaults?: ForeignKeyDefaults;
322
+ readonly defaultControlPolicy?: ControlPolicy;
323
+ readonly namespaces?: Namespaces;
324
+ readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
325
+ readonly types?: Types;
326
+ readonly models?: Models;
327
+ readonly codecLookup?: CodecLookup;
328
+ readonly enums?: Record<string, EnumTypeHandle>;
329
+ };
330
+
331
+ // A bare `Record<string, EnumTypeHandle>` (no literal keys) is the widened
332
+ // default for a side that declared no enums; drop it so the merge keeps only
333
+ // literally-authored enum handles.
334
+ type LiteralEnums<E extends Record<string, EnumTypeHandle>> = string extends keyof E
335
+ ? Record<never, never>
336
+ : E;
337
+
338
+ // Merges enum handles authored on the scaffold definition with those returned
339
+ // from the factory callback. Either side may be the widened default (empty).
340
+ export type MergeEnums<
341
+ ScaffoldEnums extends Record<string, EnumTypeHandle>,
342
+ FactoryEnums extends Record<string, EnumTypeHandle>,
343
+ > = LiteralEnums<ScaffoldEnums> & LiteralEnums<FactoryEnums>;
344
+
345
+ // Merges a bound input with the pre-bound family/target to produce a full ContractDefinition.
346
+ type WithFamilyTarget<
347
+ Input,
348
+ F extends FamilyPackRef<string>,
349
+ T extends TargetPackRef<'sql', string>,
350
+ > = Input & { readonly family: F; readonly target: T };
351
+
352
+ /**
353
+ * Shared builder that assembles a SqlContract with pre-bound family and target.
354
+ * Extension wrappers keep their own public overloads and delegate their impl body here;
355
+ * this is a plain overloaded function (not a factory returning an overloaded function)
356
+ * so no overloaded-function-return cast is needed.
357
+ *
358
+ * Overload 1: definition form (no factory).
359
+ */
360
+ export function buildBoundContract<
361
+ const F extends FamilyPackRef<string>,
362
+ const T extends TargetPackRef<'sql', string>,
363
+ const Definition extends BoundDefinitionInput<
364
+ Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
365
+ Record<string, ModelLike>,
366
+ Record<string, ExtensionPackRef<'sql', string>> | undefined,
367
+ ContractInput['naming'] | undefined,
368
+ string | undefined,
369
+ ForeignKeyDefaultsState | undefined,
370
+ readonly string[] | undefined
371
+ >,
372
+ >(
373
+ family: F,
374
+ target: T,
375
+ definition: Definition,
376
+ factory?: undefined,
377
+ ): SqlContractResult<WithFamilyTarget<Definition, F, T>>;
378
+ /**
379
+ * Overload 2: factory form.
380
+ */
381
+ export function buildBoundContract<
382
+ const F extends FamilyPackRef<string>,
383
+ const T extends TargetPackRef<'sql', string>,
384
+ const Definition extends BoundDefinitionInput<
385
+ Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
386
+ Record<string, ModelLike>,
387
+ Record<string, ExtensionPackRef<'sql', string>> | undefined,
388
+ ContractInput['naming'] | undefined,
389
+ string | undefined,
390
+ ForeignKeyDefaultsState | undefined,
391
+ readonly string[] | undefined
392
+ >,
393
+ const Built extends {
394
+ readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
395
+ readonly models?: Record<string, ModelLike>;
396
+ readonly enums?: Record<string, EnumTypeHandle>;
397
+ },
398
+ >(
399
+ family: F,
400
+ target: T,
401
+ definition: Definition,
402
+ factory: (
403
+ helpers: ComposedAuthoringHelpers<F, T, NonNullable<Definition['extensionPacks']>>,
404
+ ) => Built,
405
+ ): SqlContractResult<WithFamilyTarget<Definition & Built, F, T>>;
406
+ /** Implementation. */
407
+ export function buildBoundContract(
408
+ family: FamilyPackRef<string>,
409
+ target: TargetPackRef<'sql', string>,
410
+ definition: Omit<ContractInput, 'family' | 'target'>,
411
+ factory?:
412
+ | ((
413
+ helpers: ComposedAuthoringHelpers<
414
+ FamilyPackRef<string>,
415
+ TargetPackRef<'sql', string>,
416
+ Record<string, ExtensionPackRef<'sql', string>> | undefined
417
+ >,
418
+ ) => {
419
+ readonly types?: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
420
+ readonly models?: Record<string, ModelLike>;
421
+ readonly enums?: Record<string, EnumTypeHandle>;
422
+ })
423
+ | undefined,
424
+ ) {
425
+ const full = { ...definition, family, target };
426
+
427
+ if (factory !== undefined) {
428
+ const built = factory(
429
+ createComposedAuthoringHelpers({
430
+ family,
431
+ target,
432
+ extensionPacks: definition.extensionPacks,
433
+ }),
434
+ );
435
+ const mergedEnums = { ...(definition.enums ?? {}), ...built.enums };
436
+ return buildContractFromDsl({
437
+ ...full,
438
+ ...ifDefined('types', built.types),
439
+ ...ifDefined('models', built.models),
440
+ ...ifDefined('enums', Object.keys(mergedEnums).length > 0 ? mergedEnums : undefined),
441
+ });
442
+ }
443
+
444
+ return buildContractFromDsl(full);
445
+ }
446
+
292
447
  export function defineContract<
293
448
  const Family extends FamilyPackRef<string>,
294
449
  const Target extends TargetPackRef<'sql', string>,
@@ -304,6 +459,7 @@ export function defineContract<
304
459
  const StorageHash extends string | undefined = undefined,
305
460
  const ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
306
461
  const Namespaces extends readonly string[] | undefined = undefined,
462
+ const Enums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
307
463
  >(
308
464
  definition: ContractDefinition<
309
465
  Family,
@@ -314,7 +470,8 @@ export function defineContract<
314
470
  Naming,
315
471
  StorageHash,
316
472
  ForeignKeyDefaults,
317
- Namespaces
473
+ Namespaces,
474
+ Enums
318
475
  >,
319
476
  ): SqlContractResult<
320
477
  ContractDefinition<
@@ -326,7 +483,8 @@ export function defineContract<
326
483
  Naming,
327
484
  StorageHash,
328
485
  ForeignKeyDefaults,
329
- Namespaces
486
+ Namespaces,
487
+ Enums
330
488
  >
331
489
  >;
332
490
  export function defineContract<
@@ -344,6 +502,8 @@ export function defineContract<
344
502
  const StorageHash extends string | undefined = undefined,
345
503
  const ForeignKeyDefaults extends ForeignKeyDefaultsState | undefined = undefined,
346
504
  const Namespaces extends readonly string[] | undefined = undefined,
505
+ const ScaffoldEnums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
506
+ const FactoryEnums extends Record<string, EnumTypeHandle> = Record<string, EnumTypeHandle>,
347
507
  >(
348
508
  definition: ContractScaffold<
349
509
  Family,
@@ -352,9 +512,10 @@ export function defineContract<
352
512
  Naming,
353
513
  StorageHash,
354
514
  ForeignKeyDefaults,
355
- Namespaces
515
+ Namespaces,
516
+ ScaffoldEnums
356
517
  >,
357
- factory: ContractFactory<Family, Target, Types, Models, ExtensionPacks>,
518
+ factory: ContractFactory<Family, Target, Types, Models, ExtensionPacks, FactoryEnums>,
358
519
  ): SqlContractResult<
359
520
  ContractDefinition<
360
521
  Family,
@@ -365,7 +526,8 @@ export function defineContract<
365
526
  Naming,
366
527
  StorageHash,
367
528
  ForeignKeyDefaults,
368
- Namespaces
529
+ Namespaces,
530
+ MergeEnums<ScaffoldEnums, FactoryEnums>
369
531
  >
370
532
  >;
371
533
  export function defineContract(
@@ -384,22 +546,10 @@ export function defineContract(
384
546
  );
385
547
  }
386
548
 
387
- if (!factory) {
388
- return buildContractFromDsl(definition);
549
+ if (factory !== undefined) {
550
+ return buildBoundContract(definition.family, definition.target, definition, factory);
389
551
  }
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);
552
+ return buildBoundContract(definition.family, definition.target, definition);
403
553
  }
404
554
 
405
555
  export type {
@@ -409,4 +559,4 @@ export type {
409
559
  ModelLike,
410
560
  ScalarFieldBuilder,
411
561
  };
412
- export { field, model, rel };
562
+ 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
  }