@prisma-next/sql-contract-ts 0.14.0-dev.7 → 0.14.0-dev.71

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.
@@ -13,9 +13,6 @@ import type {
13
13
  } from '@prisma-next/framework-components/authoring';
14
14
  import {
15
15
  assertNoCrossRegistryCollisions,
16
- isAuthoringEntityTypeDescriptor,
17
- isAuthoringFieldPresetDescriptor,
18
- isAuthoringTypeConstructorDescriptor,
19
16
  mergeAuthoringNamespaces,
20
17
  } from '@prisma-next/framework-components/authoring';
21
18
  import type {
@@ -183,7 +180,7 @@ function composeTypeNamespace(components: readonly AuthoringComponent[]): Author
183
180
  for (const component of components) {
184
181
  const ns = extractTypeNamespace(component);
185
182
  if (Object.keys(ns).length > 0) {
186
- mergeAuthoringNamespaces(merged, ns, [], isAuthoringTypeConstructorDescriptor, 'type');
183
+ mergeAuthoringNamespaces(merged, ns, [], 'typeConstructor', 'type');
187
184
  }
188
185
  }
189
186
  return merged as AuthoringTypeNamespace;
@@ -194,7 +191,7 @@ function composeFieldNamespace(components: readonly AuthoringComponent[]): Autho
194
191
  for (const component of components) {
195
192
  const ns = extractFieldNamespace(component);
196
193
  if (Object.keys(ns).length > 0) {
197
- mergeAuthoringNamespaces(merged, ns, [], isAuthoringFieldPresetDescriptor, 'field');
194
+ mergeAuthoringNamespaces(merged, ns, [], 'fieldPreset', 'field');
198
195
  }
199
196
  }
200
197
  return merged as AuthoringFieldNamespace;
@@ -207,7 +204,7 @@ function composeEntityNamespace(
207
204
  for (const component of components) {
208
205
  const ns = extractEntitiesNamespace(component);
209
206
  if (Object.keys(ns).length > 0) {
210
- mergeAuthoringNamespaces(merged, ns, [], isAuthoringEntityTypeDescriptor, 'entity');
207
+ mergeAuthoringNamespaces(merged, ns, [], 'entity', 'entity');
211
208
  }
212
209
  }
213
210
  return merged as AuthoringEntityTypeNamespace;
@@ -3,6 +3,7 @@ import type { ContractConfig } from '@prisma-next/config/config-types';
3
3
  import { applySpecifierDefaultControlPolicy } from '@prisma-next/contract/apply-specifier-default-control-policy';
4
4
  import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
5
5
  import type { TargetPackRef } from '@prisma-next/framework-components/components';
6
+ import type { SqlNamespaceBase, SqlNamespaceInput } from '@prisma-next/sql-contract/types';
6
7
  import { ifDefined } from '@prisma-next/utils/defined';
7
8
  import { ok } from '@prisma-next/utils/result';
8
9
  import { extname } from 'pathe';
@@ -27,13 +28,18 @@ export interface TypeScriptContractSpecifierOptions {
27
28
  export function emptyContract(options: {
28
29
  readonly output?: string;
29
30
  readonly target: TargetPackRef<'sql', string>;
31
+ readonly createNamespace: (input: SqlNamespaceInput) => SqlNamespaceBase;
30
32
  readonly defaultControlPolicy?: ControlPolicy;
31
33
  }): ContractConfig {
32
34
  return {
33
35
  source: {
34
36
  sourceFormat: 'typescript',
35
37
  load: async () => {
36
- const built = buildSqlContractFromDefinition({ target: options.target, models: [] });
38
+ const built = buildSqlContractFromDefinition({
39
+ target: options.target,
40
+ createNamespace: options.createNamespace,
41
+ models: [],
42
+ });
37
43
  return ok(applySpecifierDefaultControlPolicy(built, options.defaultControlPolicy));
38
44
  },
39
45
  },
@@ -6,8 +6,11 @@ import type {
6
6
  FamilyPackRef,
7
7
  TargetPackRef,
8
8
  } from '@prisma-next/framework-components/components';
9
- import type { Namespace } from '@prisma-next/framework-components/ir';
10
- import type { SqlNamespaceTablesInput, StorageTypeInstance } from '@prisma-next/sql-contract/types';
9
+ import type {
10
+ SqlNamespaceBase,
11
+ SqlNamespaceInput,
12
+ StorageTypeInstance,
13
+ } from '@prisma-next/sql-contract/types';
11
14
  import { blindCast } from '@prisma-next/utils/casts';
12
15
  import { ifDefined } from '@prisma-next/utils/defined';
13
16
  import { buildSqlContractFromDefinition } from './build-contract';
@@ -15,6 +18,7 @@ import {
15
18
  type ComposedAuthoringHelpers,
16
19
  createComposedAuthoringHelpers,
17
20
  } from './composed-authoring-helpers';
21
+ import type { PackEntitiesInput } from './contract-definition';
18
22
  import {
19
23
  type ContractInput,
20
24
  type ContractModelBuilder,
@@ -68,11 +72,12 @@ type ContractDefinition<
68
72
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
69
73
  readonly defaultControlPolicy?: ControlPolicy;
70
74
  readonly namespaces?: Namespaces;
71
- readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
75
+ readonly createNamespace: (input: SqlNamespaceInput) => SqlNamespaceBase;
72
76
  readonly types?: Types;
73
77
  readonly models?: Models;
74
78
  readonly codecLookup?: CodecLookup;
75
79
  readonly enums?: Enums;
80
+ readonly packEntities?: PackEntitiesInput;
76
81
  };
77
82
 
78
83
  type ContractScaffold<
@@ -93,11 +98,12 @@ type ContractScaffold<
93
98
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
94
99
  readonly defaultControlPolicy?: ControlPolicy;
95
100
  readonly namespaces?: Namespaces;
96
- readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
101
+ readonly createNamespace: (input: SqlNamespaceInput) => SqlNamespaceBase;
97
102
  readonly types?: never;
98
103
  readonly models?: never;
99
104
  readonly codecLookup?: CodecLookup;
100
105
  readonly enums?: Enums;
106
+ readonly packEntities?: PackEntitiesInput;
101
107
  };
102
108
 
103
109
  type ContractFactory<
@@ -111,6 +117,7 @@ type ContractFactory<
111
117
  readonly types?: Types;
112
118
  readonly models?: Models;
113
119
  readonly enums?: Enums;
120
+ readonly packEntities?: PackEntitiesInput;
114
121
  };
115
122
 
116
123
  function validateTargetPackRef(
@@ -314,11 +321,12 @@ type BoundDefinitionInput<
314
321
  readonly foreignKeyDefaults?: ForeignKeyDefaults;
315
322
  readonly defaultControlPolicy?: ControlPolicy;
316
323
  readonly namespaces?: Namespaces;
317
- readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
324
+ readonly createNamespace: (input: SqlNamespaceInput) => SqlNamespaceBase;
318
325
  readonly types?: Types;
319
326
  readonly models?: Models;
320
327
  readonly codecLookup?: CodecLookup;
321
328
  readonly enums?: Record<string, EnumTypeHandle>;
329
+ readonly packEntities?: PackEntitiesInput;
322
330
  };
323
331
 
324
332
  // A bare `Record<string, EnumTypeHandle>` (no literal keys) is the widened
@@ -342,6 +350,60 @@ type WithFamilyTarget<
342
350
  T extends TargetPackRef<'sql', string>,
343
351
  > = Input & { readonly family: F; readonly target: T };
344
352
 
353
+ // Deep-merges packEntities authored on the scaffold definition with those
354
+ // returned from the factory callback — three levels deep (namespace, kind,
355
+ // name), unlike `enums`' flat shallow merge, since a factory-declared
356
+ // namespace/kind must not silently drop a scaffold-declared sibling entry
357
+ // under the same namespace or kind. A same-name key present on both sides is
358
+ // only merged when it is the identical entity instance; two *different*
359
+ // entities of the same namespace/kind/name is a collision (the emitted
360
+ // entry could reflect only one), rejected here the same way
361
+ // `mergeCollectedPackEntities` in `build-contract.ts` rejects it.
362
+ function mergePackEntityNames(
363
+ namespaceId: string,
364
+ kind: string,
365
+ a: Readonly<Record<string, unknown>> | undefined,
366
+ b: Readonly<Record<string, unknown>> | undefined,
367
+ ): Readonly<Record<string, unknown>> {
368
+ if (a !== undefined && b !== undefined) {
369
+ for (const [name, entity] of Object.entries(b)) {
370
+ const existing = a[name];
371
+ if (existing !== undefined && existing !== entity) {
372
+ throw new Error(
373
+ `defineContract: two different "${kind}" entities named "${name}" in namespace "${namespaceId}" — a factory-returned pack entity conflicts with a scaffold-declared one; pack-entity names must be unique per namespace.`,
374
+ );
375
+ }
376
+ }
377
+ }
378
+ return { ...(a ?? {}), ...(b ?? {}) };
379
+ }
380
+
381
+ function mergePackEntityKinds(
382
+ namespaceId: string,
383
+ a: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
384
+ b: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined,
385
+ ): Readonly<Record<string, Readonly<Record<string, unknown>>>> {
386
+ const kinds = new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);
387
+ const result: Record<string, Readonly<Record<string, unknown>>> = {};
388
+ for (const kind of kinds) {
389
+ result[kind] = mergePackEntityNames(namespaceId, kind, a?.[kind], b?.[kind]);
390
+ }
391
+ return result;
392
+ }
393
+
394
+ function mergePackEntities(
395
+ a: PackEntitiesInput | undefined,
396
+ b: PackEntitiesInput | undefined,
397
+ ): PackEntitiesInput | undefined {
398
+ if (a === undefined && b === undefined) return undefined;
399
+ const namespaces = new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);
400
+ const result: Record<string, Readonly<Record<string, Readonly<Record<string, unknown>>>>> = {};
401
+ for (const namespaceId of namespaces) {
402
+ result[namespaceId] = mergePackEntityKinds(namespaceId, a?.[namespaceId], b?.[namespaceId]);
403
+ }
404
+ return result;
405
+ }
406
+
345
407
  /**
346
408
  * Shared builder that assembles a SqlContract with pre-bound family and target.
347
409
  * Extension wrappers keep their own public overloads and delegate their impl body here;
@@ -387,6 +449,7 @@ export function buildBoundContract<
387
449
  readonly types?: Record<string, StorageTypeInstance>;
388
450
  readonly models?: Record<string, ModelLike>;
389
451
  readonly enums?: Record<string, EnumTypeHandle>;
452
+ readonly packEntities?: PackEntitiesInput;
390
453
  },
391
454
  >(
392
455
  family: F,
@@ -412,6 +475,7 @@ export function buildBoundContract(
412
475
  readonly types?: Record<string, StorageTypeInstance>;
413
476
  readonly models?: Record<string, ModelLike>;
414
477
  readonly enums?: Record<string, EnumTypeHandle>;
478
+ readonly packEntities?: PackEntitiesInput;
415
479
  })
416
480
  | undefined,
417
481
  ) {
@@ -426,11 +490,13 @@ export function buildBoundContract(
426
490
  }),
427
491
  );
428
492
  const mergedEnums = { ...(definition.enums ?? {}), ...built.enums };
493
+ const mergedPackEntities = mergePackEntities(definition.packEntities, built.packEntities);
429
494
  return buildContractFromDsl({
430
495
  ...full,
431
496
  ...ifDefined('types', built.types),
432
497
  ...ifDefined('models', built.models),
433
498
  ...ifDefined('enums', Object.keys(mergedEnums).length > 0 ? mergedEnums : undefined),
499
+ ...ifDefined('packEntities', mergedPackEntities),
434
500
  });
435
501
  }
436
502
 
@@ -6,16 +6,30 @@ import type {
6
6
  import type { ForeignKeyDefaultsState } from '@prisma-next/contract-authoring';
7
7
  import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
8
8
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
9
- import type { Namespace } from '@prisma-next/framework-components/ir';
10
9
  import type {
11
10
  ReferentialAction,
12
- SqlNamespaceTablesInput,
11
+ SqlNamespaceBase,
12
+ SqlNamespaceInput,
13
13
  StorageTypeInstance,
14
14
  } from '@prisma-next/sql-contract/types';
15
15
  import type { EnumTypeHandle } from './enum-type';
16
16
 
17
17
  export type { ExecutionMutationDefaultPhases };
18
18
 
19
+ /**
20
+ * An author-declared, namespace-scoped pack entity attachment: namespace id →
21
+ * entity kind (the discriminator the target/extension pack registered its
22
+ * `AuthoringContributions.entityTypes` descriptor under, e.g. `native_enum`)
23
+ * → entity name → the lowered entity instance. Generic on purpose — neither
24
+ * the framework nor `contract-ts` names a specific entity kind here; the
25
+ * shape mirrors `SqlNamespaceInput.entries` (`entries.<kind>[name]`), just
26
+ * namespace-nested so an author can target any declared namespace (default
27
+ * or named), not only the contract's default namespace.
28
+ */
29
+ export type PackEntitiesInput = Readonly<
30
+ Record<string, Readonly<Record<string, Readonly<Record<string, unknown>>>>>
31
+ >;
32
+
19
33
  export interface FieldNode {
20
34
  readonly fieldName: string;
21
35
  readonly columnName: string;
@@ -175,8 +189,8 @@ export interface ContractDefinition {
175
189
  * `SqlStorage.namespaces` together with `createNamespace`.
176
190
  */
177
191
  readonly namespaces?: readonly string[];
178
- /** Target-supplied factory that materialises a `Namespace` concretion for a declared namespace coordinate. */
179
- readonly createNamespace?: (input: SqlNamespaceTablesInput) => Namespace;
192
+ /** Target-supplied factory that materialises a `SqlNamespaceBase` concretion for a declared namespace coordinate. */
193
+ readonly createNamespace: (input: SqlNamespaceInput) => SqlNamespaceBase;
180
194
  readonly models: readonly ModelNode[];
181
195
  readonly valueObjects?: readonly ValueObjectNode[];
182
196
  /**
@@ -185,4 +199,13 @@ export interface ContractDefinition {
185
199
  * default namespace.
186
200
  */
187
201
  readonly enums?: Record<string, EnumTypeHandle>;
202
+ /**
203
+ * Author-declared pack entities, keyed by namespace then entity kind then
204
+ * name. Each entity lowers into `storage.namespaces[ns].entries.<kind>`;
205
+ * when the registered entity-type descriptor's factory output implements
206
+ * the `SqlValueSetDerivingEntityTypeOutput.deriveValueSet` hook, the
207
+ * derived value-set also folds into `entries.valueSet`, mirroring how
208
+ * `enums` flows there.
209
+ */
210
+ readonly packEntities?: PackEntitiesInput;
188
211
  }