@prisma-next/framework-components 0.5.0-dev.6 → 0.5.0-dev.61

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.
Files changed (92) hide show
  1. package/README.md +63 -3
  2. package/dist/authoring.d.mts +2 -2
  3. package/dist/authoring.mjs +2 -121
  4. package/dist/codec-BUBJeOk4.d.mts +168 -0
  5. package/dist/codec-BUBJeOk4.d.mts.map +1 -0
  6. package/dist/codec.d.mts +49 -2
  7. package/dist/codec.d.mts.map +1 -0
  8. package/dist/codec.mjs +68 -3
  9. package/dist/codec.mjs.map +1 -1
  10. package/dist/components.d.mts +3 -1
  11. package/dist/components.mjs +1 -1
  12. package/dist/control.d.mts +110 -71
  13. package/dist/control.d.mts.map +1 -1
  14. package/dist/control.mjs +47 -45
  15. package/dist/control.mjs.map +1 -1
  16. package/dist/emission-types-DzNgwiFC.d.mts +39 -0
  17. package/dist/emission-types-DzNgwiFC.d.mts.map +1 -0
  18. package/dist/emission.d.mts +2 -2
  19. package/dist/execution.d.mts +7 -5
  20. package/dist/execution.d.mts.map +1 -1
  21. package/dist/execution.mjs +3 -3
  22. package/dist/execution.mjs.map +1 -1
  23. package/dist/{framework-authoring-D1-JZ37B.d.mts → framework-authoring-DJbiXhmf.d.mts} +41 -12
  24. package/dist/framework-authoring-DJbiXhmf.d.mts.map +1 -0
  25. package/dist/framework-authoring-gi_BJlNO.mjs +206 -0
  26. package/dist/framework-authoring-gi_BJlNO.mjs.map +1 -0
  27. package/dist/{framework-components-EJXe-pum.d.mts → framework-components-BVqm1I48.d.mts} +45 -55
  28. package/dist/framework-components-BVqm1I48.d.mts.map +1 -0
  29. package/dist/{framework-components-C8ZhSwXe.mjs → framework-components-BsWST1Rn.mjs} +2 -2
  30. package/dist/framework-components-BsWST1Rn.mjs.map +1 -0
  31. package/dist/psl-ast-BlDveJZ4.d.mts +159 -0
  32. package/dist/psl-ast-BlDveJZ4.d.mts.map +1 -0
  33. package/dist/psl-ast.d.mts +2 -0
  34. package/dist/psl-ast.mjs +1 -0
  35. package/dist/runtime.d.mts +346 -19
  36. package/dist/runtime.d.mts.map +1 -1
  37. package/dist/runtime.mjs +256 -4
  38. package/dist/runtime.mjs.map +1 -1
  39. package/dist/{types-import-spec-C4sc7wbb.d.mts → types-import-spec-CPhrNJIV.d.mts} +2 -2
  40. package/dist/types-import-spec-CPhrNJIV.d.mts.map +1 -0
  41. package/package.json +7 -4
  42. package/src/control/control-capabilities.ts +71 -0
  43. package/src/{control-descriptors.ts → control/control-descriptors.ts} +7 -7
  44. package/src/{control-instances.ts → control/control-instances.ts} +6 -6
  45. package/src/{control-migration-types.ts → control/control-migration-types.ts} +57 -60
  46. package/src/control/control-operation-preview.ts +23 -0
  47. package/src/{control-stack.ts → control/control-stack.ts} +77 -94
  48. package/src/control/emission-types.ts +49 -0
  49. package/src/control/psl-ast.ts +193 -0
  50. package/src/{execution-descriptors.ts → execution/execution-descriptors.ts} +7 -7
  51. package/src/{execution-instances.ts → execution/execution-instances.ts} +1 -1
  52. package/src/{execution-requirements.ts → execution/execution-requirements.ts} +1 -1
  53. package/src/execution/query-plan.ts +53 -0
  54. package/src/execution/race-against-abort.ts +85 -0
  55. package/src/execution/run-with-middleware.ts +132 -0
  56. package/src/execution/runtime-core.ts +133 -0
  57. package/src/execution/runtime-error.ts +83 -0
  58. package/src/execution/runtime-middleware.ts +182 -0
  59. package/src/exports/authoring.ts +5 -2
  60. package/src/exports/codec.ts +27 -2
  61. package/src/exports/components.ts +2 -2
  62. package/src/exports/control.ts +26 -13
  63. package/src/exports/emission.ts +2 -2
  64. package/src/exports/execution.ts +5 -5
  65. package/src/exports/psl-ast.ts +1 -0
  66. package/src/exports/runtime.ts +17 -5
  67. package/src/shared/codec-descriptor.ts +87 -0
  68. package/src/shared/codec-types.ts +79 -0
  69. package/src/shared/codec.ts +80 -0
  70. package/src/shared/column-spec.ts +83 -0
  71. package/src/{framework-authoring.ts → shared/framework-authoring.ts} +202 -23
  72. package/src/{framework-components.ts → shared/framework-components.ts} +22 -48
  73. package/src/{mutation-default-types.ts → shared/mutation-default-types.ts} +22 -2
  74. package/dist/authoring.mjs.map +0 -1
  75. package/dist/codec-types-B58nCJiu.d.mts +0 -40
  76. package/dist/codec-types-B58nCJiu.d.mts.map +0 -1
  77. package/dist/emission-types-BPAALJbF.d.mts +0 -24
  78. package/dist/emission-types-BPAALJbF.d.mts.map +0 -1
  79. package/dist/framework-authoring-D1-JZ37B.d.mts.map +0 -1
  80. package/dist/framework-components-C8ZhSwXe.mjs.map +0 -1
  81. package/dist/framework-components-EJXe-pum.d.mts.map +0 -1
  82. package/dist/types-import-spec-C4sc7wbb.d.mts.map +0 -1
  83. package/src/codec-types.ts +0 -46
  84. package/src/control-capabilities.ts +0 -34
  85. package/src/emission-types.ts +0 -28
  86. package/src/runtime-error.ts +0 -39
  87. package/src/runtime-middleware.ts +0 -83
  88. /package/src/{control-result-types.ts → control/control-result-types.ts} +0 -0
  89. /package/src/{control-schema-view.ts → control/control-schema-view.ts} +0 -0
  90. /package/src/{async-iterable-result.ts → execution/async-iterable-result.ts} +0 -0
  91. /package/src/{execution-stack.ts → execution/execution-stack.ts} +0 -0
  92. /package/src/{types-import-spec.ts → shared/types-import-spec.ts} +0 -0
@@ -11,8 +11,8 @@
11
11
 
12
12
  import type { Contract } from '@prisma-next/contract/types';
13
13
  import type { Result } from '@prisma-next/utils/result';
14
+ import type { TargetBoundComponentDescriptor } from '../shared/framework-components';
14
15
  import type { ControlDriverInstance, ControlFamilyInstance } from './control-instances';
15
- import type { TargetBoundComponentDescriptor } from './framework-components';
16
16
 
17
17
  // ============================================================================
18
18
  // Operation Classes and Policy
@@ -28,61 +28,23 @@ import type { TargetBoundComponentDescriptor } from './framework-components';
28
28
  export type MigrationOperationClass = 'additive' | 'widening' | 'destructive' | 'data';
29
29
 
30
30
  // ============================================================================
31
- // Data Transform Operation
31
+ // Serialized Query Plan
32
32
  // ============================================================================
33
33
 
34
34
  /**
35
35
  * A lowered query statement as stored in ops.json.
36
36
  * Contains the SQL string and parameter values — ready for execution.
37
37
  * Lowering from query builder AST to SQL happens at verify time.
38
+ *
39
+ * The Postgres `dataTransform` factory uses this shape internally to
40
+ * carry the user's lowered `check`/`run` plans before wrapping them
41
+ * into precheck/execute/postcheck steps on the unified migration op.
38
42
  */
39
43
  export interface SerializedQueryPlan {
40
44
  readonly sql: string;
41
45
  readonly params: readonly unknown[];
42
46
  }
43
47
 
44
- /**
45
- * A data transform operation within a migration edge.
46
- *
47
- * Data transforms are authored in TypeScript using the query builder,
48
- * serialized to JSON ASTs at verification time, and rendered to SQL
49
- * by the target adapter at apply time.
50
- *
51
- * The `name` serves as the invariant identity — it's recorded in the
52
- * ledger and used for invariant-aware routing via environment refs.
53
- *
54
- * In draft state (before verification), `check` and `run` are null.
55
- * After verification, they contain the serialized query ASTs.
56
- */
57
- export interface DataTransformOperation extends MigrationPlanOperation {
58
- readonly operationClass: 'data';
59
- /**
60
- * The invariant name for this data transform.
61
- * Recorded in the ledger on successful edge completion.
62
- * Used by environment refs to declare required invariants.
63
- */
64
- readonly name: string;
65
- /**
66
- * Path to the TypeScript source file that produced this operation.
67
- * Not part of edgeId computation — for traceability only.
68
- */
69
- readonly source: string;
70
- /**
71
- * Serialized check query plan, or a boolean literal.
72
- * - SerializedQueryPlan: describes violations; empty result = already applied.
73
- * - false: always run (no check).
74
- * - true: always skip.
75
- * - null: not yet serialized (draft state).
76
- */
77
- readonly check: SerializedQueryPlan | boolean | null;
78
- /**
79
- * Serialized run query plans.
80
- * - Array of serialized query plans to execute sequentially.
81
- * - null: not yet serialized (draft state).
82
- */
83
- readonly run: readonly SerializedQueryPlan[] | null;
84
- }
85
-
86
48
  /**
87
49
  * Policy defining which operation classes are allowed during a migration.
88
50
  */
@@ -105,6 +67,17 @@ export interface MigrationPlanOperation {
105
67
  readonly label: string;
106
68
  /** The class of operation (additive, widening, destructive). */
107
69
  readonly operationClass: MigrationOperationClass;
70
+ /**
71
+ * Optional opt-in routing identity for data-transform operations.
72
+ * Presence opts the transform into invariant-aware routing; absence
73
+ * means it is path-dependent and not referenceable from refs.
74
+ *
75
+ * Lives on the base op so the manifest emitter and
76
+ * `deriveProvidedInvariants` can read it without depending on a
77
+ * target-specific shape. Schema-DDL ops (additive / widening /
78
+ * destructive) leave it undefined.
79
+ */
80
+ readonly invariantId?: string;
108
81
  }
109
82
 
110
83
  // ============================================================================
@@ -151,6 +124,17 @@ export interface MigrationPlan {
151
124
  };
152
125
  /** Ordered list of operations to execute. */
153
126
  readonly operations: readonly MigrationPlanOperation[];
127
+ /**
128
+ * Sorted, deduplicated invariant ids declared by this plan's data-transform
129
+ * ops. Authored migrations carry the canonical value from
130
+ * `migration.json.providedInvariants`; planner-built plans (`db init`,
131
+ * `db update`) omit it (the runner treats it as `[]`). Runners read this
132
+ * field for marker writes and self-edge no-op detection rather than
133
+ * re-deriving from `operations`, since the manifest is the canonical
134
+ * source for the invariant set across all runners (postgres, sqlite,
135
+ * mongo).
136
+ */
137
+ readonly providedInvariants?: readonly string[];
154
138
  }
155
139
 
156
140
  /**
@@ -289,21 +273,23 @@ export interface MigrationPlanner<
289
273
  readonly contract: unknown;
290
274
  readonly schema: unknown;
291
275
  readonly policy: MigrationOperationPolicy;
292
- /**
293
- * Storage hash of the "from" contract (the state the planner assumes the
294
- * database starts at). Planners use this to populate `describe()` on the
295
- * produced plan so the rendered `migration.ts` has correct `from`/`to`
296
- * metadata.
297
- */
298
- readonly fromHash: string;
299
276
  /**
300
277
  * The "from" contract (the state the planner assumes the database starts
301
- * at). Planners pass this to data-safety strategies so they can compare
302
- * `from` and `to` column shapes (e.g. to detect unsafe type changes).
303
- * `db update` / `db init` reconcile against the live schema and have no
304
- * "from" contract; only `migration plan` provides one.
278
+ * at), or `null` for a baseline plan with no prior state.
279
+ *
280
+ * Planners derive any "from" identity they need to stamp onto the
281
+ * produced plan's `describe()` from `fromContract?.storage.storageHash
282
+ * ?? null`. They also pass this to data-safety strategies so they can
283
+ * compare `from` and `to` column shapes (e.g. to detect unsafe type
284
+ * changes).
285
+ *
286
+ * Required at every call site to make the structural fact "I have a
287
+ * prior contract / I don't" visible in the type. Reconciliation
288
+ * commands (`db init`, `db update`) introspect a live schema and pass
289
+ * `null`; authoring commands (`migration plan`) pass the previous
290
+ * bundle's `metadata.toContract`.
305
291
  */
306
- readonly fromContract?: unknown;
292
+ readonly fromContract: Contract | null;
307
293
  /**
308
294
  * Active framework components participating in this composition.
309
295
  * Families/targets can interpret this list to derive family-specific metadata.
@@ -335,6 +321,16 @@ export interface MigrationRunner<
335
321
  TFamilyId extends string = string,
336
322
  TTargetId extends string = string,
337
323
  > {
324
+ /**
325
+ * Execute a migration plan against the configured driver.
326
+ *
327
+ * The `plan` parameter is trusted input. Callers are responsible for
328
+ * upstream verification of the originating migration package — typically
329
+ * by obtaining the package via `readMigrationPackage` from
330
+ * `@prisma-next/migration-tools/io`, which performs hash-integrity checks
331
+ * at the load boundary. Runners do not re-verify the plan and assume the
332
+ * `(metadata, ops)` pair on disk has not been tampered with since emit.
333
+ */
338
334
  execute(options: {
339
335
  readonly plan: MigrationPlan;
340
336
  readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
@@ -414,11 +410,12 @@ export interface MigrationScaffoldContext {
414
410
  /** Absolute path to the contract.json file, if one exists. Used by targets that emit typed-contract imports. */
415
411
  readonly contractJsonPath?: string;
416
412
  /**
417
- * Storage hash of the "from" contract. Targets use this to populate
418
- * `describe()` on the rendered empty migration so that identity metadata
419
- * is correctly populated.
413
+ * Storage hash of the "from" contract, or `null` for a baseline scaffold
414
+ * with no prior state. Targets use this to populate `describe()` on the
415
+ * rendered empty migration so that identity metadata is correctly
416
+ * populated.
420
417
  */
421
- readonly fromHash: string;
418
+ readonly fromHash: string | null;
422
419
  /**
423
420
  * Storage hash of the "to" contract. Same purpose as `fromHash` — threaded
424
421
  * through so the rendered class's `describe()` declares the correct
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Family-agnostic textual preview of a migration plan, used by the CLI to
3
+ * render a "DDL preview" section for `db init` / `db update` / `migration plan`
4
+ * / `migration show`. Each statement carries a free-form `language` tag so
5
+ * formatters can suffix `;` for SQL but render Mongo shell lines verbatim.
6
+ *
7
+ * Producers are family-specific: SQL emits `language: 'sql'` (existing DDL
8
+ * extraction); Mongo emits `language: 'mongodb-shell'` via the
9
+ * `MongoDdlCommandFormatter` visitor.
10
+ *
11
+ * The capability `OperationPreviewCapable` (declared in
12
+ * `./control-capabilities`) is how a family announces it can produce these.
13
+ */
14
+
15
+ export interface OperationPreviewStatement {
16
+ readonly text: string;
17
+ /** Dialect identifier, e.g. `'sql'`, `'mongodb-shell'`. Free-form by design (OQ-3). */
18
+ readonly language: string;
19
+ }
20
+
21
+ export interface OperationPreview {
22
+ readonly statements: readonly OperationPreviewStatement[];
23
+ }
@@ -1,25 +1,30 @@
1
- import type { CodecLookup } from './codec-types';
2
- import type {
3
- ControlAdapterDescriptor,
4
- ControlDriverDescriptor,
5
- ControlExtensionDescriptor,
6
- ControlFamilyDescriptor,
7
- ControlTargetDescriptor,
8
- } from './control-descriptors';
1
+ import type { Codec } from '../shared/codec';
2
+ import type { CodecLookup, CodecMeta } from '../shared/codec-types';
9
3
  import type {
10
4
  AuthoringContributions,
11
5
  AuthoringFieldNamespace,
12
- AuthoringFieldPresetDescriptor,
13
- AuthoringTypeConstructorDescriptor,
14
6
  AuthoringTypeNamespace,
15
- } from './framework-authoring';
16
- import type { ComponentMetadata } from './framework-components';
7
+ } from '../shared/framework-authoring';
8
+ import {
9
+ assertNoCrossRegistryCollisions,
10
+ isAuthoringFieldPresetDescriptor,
11
+ isAuthoringTypeConstructorDescriptor,
12
+ mergeAuthoringNamespaces,
13
+ } from '../shared/framework-authoring';
14
+ import type { ComponentMetadata } from '../shared/framework-components';
17
15
  import type {
18
16
  ControlMutationDefaultEntry,
19
17
  ControlMutationDefaults,
20
18
  MutationDefaultGeneratorDescriptor,
21
- } from './mutation-default-types';
22
- import type { TypesImportSpec } from './types-import-spec';
19
+ } from '../shared/mutation-default-types';
20
+ import type { TypesImportSpec } from '../shared/types-import-spec';
21
+ import type {
22
+ ControlAdapterDescriptor,
23
+ ControlDriverDescriptor,
24
+ ControlExtensionDescriptor,
25
+ ControlFamilyDescriptor,
26
+ ControlTargetDescriptor,
27
+ } from './control-descriptors';
23
28
 
24
29
  export interface AssembledAuthoringContributions {
25
30
  readonly field: AuthoringFieldNamespace;
@@ -153,70 +158,6 @@ export function extractComponentIds(
153
158
  return ids;
154
159
  }
155
160
 
156
- function isTypeConstructorDescriptor(value: unknown): value is AuthoringTypeConstructorDescriptor {
157
- return (
158
- typeof value === 'object' &&
159
- value !== null &&
160
- (value as { kind?: unknown }).kind === 'typeConstructor'
161
- );
162
- }
163
-
164
- function isFieldPresetDescriptor(value: unknown): value is AuthoringFieldPresetDescriptor {
165
- return (
166
- typeof value === 'object' &&
167
- value !== null &&
168
- (value as { kind?: unknown }).kind === 'fieldPreset'
169
- );
170
- }
171
-
172
- function mergeAuthoringNamespaces(
173
- target: Record<string, unknown>,
174
- source: Record<string, unknown>,
175
- path: readonly string[],
176
- leafGuard: (value: unknown) => boolean,
177
- label: string,
178
- ): void {
179
- const assertSafePath = (currentPath: readonly string[]) => {
180
- const blockedSegment = currentPath.find(
181
- (segment) => segment === '__proto__' || segment === 'constructor' || segment === 'prototype',
182
- );
183
- if (blockedSegment) {
184
- throw new Error(
185
- `Invalid authoring ${label} helper "${currentPath.join('.')}". Helper path segments must not use "${blockedSegment}".`,
186
- );
187
- }
188
- };
189
-
190
- for (const [key, sourceValue] of Object.entries(source)) {
191
- const currentPath = [...path, key];
192
- assertSafePath(currentPath);
193
- const hasExistingValue = Object.hasOwn(target, key);
194
- const existingValue = hasExistingValue ? target[key] : undefined;
195
-
196
- if (!hasExistingValue) {
197
- target[key] = sourceValue;
198
- continue;
199
- }
200
-
201
- const existingIsLeaf = leafGuard(existingValue);
202
- const sourceIsLeaf = leafGuard(sourceValue);
203
-
204
- if (existingIsLeaf || sourceIsLeaf) {
205
- throw new Error(
206
- `Duplicate authoring ${label} helper "${currentPath.join('.')}". Descriptor contributions must be unique across composed components.`,
207
- );
208
- }
209
-
210
- mergeAuthoringNamespaces(
211
- existingValue as Record<string, unknown>,
212
- sourceValue as Record<string, unknown>,
213
- currentPath,
214
- leafGuard,
215
- label,
216
- );
217
- }
218
- }
219
-
220
161
  export function assembleAuthoringContributions(
221
162
  descriptors: ReadonlyArray<{ readonly authoring?: AuthoringContributions }>,
222
163
  ): AssembledAuthoringContributions {
@@ -229,7 +170,7 @@ export function assembleAuthoringContributions(
229
170
  field,
230
171
  descriptor.authoring.field,
231
172
  [],
232
- isFieldPresetDescriptor,
173
+ isAuthoringFieldPresetDescriptor,
233
174
  'field',
234
175
  );
235
176
  }
@@ -240,14 +181,18 @@ export function assembleAuthoringContributions(
240
181
  type,
241
182
  descriptor.authoring.type,
242
183
  [],
243
- isTypeConstructorDescriptor,
184
+ isAuthoringTypeConstructorDescriptor,
244
185
  'type',
245
186
  );
246
187
  }
247
188
 
189
+ const fieldNamespace = field as AuthoringFieldNamespace;
190
+ const typeNamespace = type as AuthoringTypeNamespace;
191
+ assertNoCrossRegistryCollisions(typeNamespace, fieldNamespace);
192
+
248
193
  return {
249
- field: field as AuthoringFieldNamespace,
250
- type: type as AuthoringTypeNamespace,
194
+ field: fieldNamespace,
195
+ type: typeNamespace,
251
196
  };
252
197
  }
253
198
 
@@ -326,27 +271,65 @@ export function assembleControlMutationDefaults(
326
271
  }
327
272
 
328
273
  export function extractCodecLookup(
329
- descriptors: ReadonlyArray<Pick<ComponentMetadata & { id?: string }, 'types' | 'id'>>,
274
+ descriptors: ReadonlyArray<Pick<ComponentMetadata & { id: string }, 'types' | 'id'>>,
330
275
  ): CodecLookup {
331
- const byId = new Map<string, import('./codec-types').Codec>();
276
+ const byId = new Map<string, Codec>();
277
+ const targetTypesById = new Map<string, readonly string[]>();
278
+ const metaById = new Map<string, CodecMeta>();
279
+ const renderersById = new Map<string, (params: Record<string, unknown>) => string | undefined>();
332
280
  const owners = new Map<string, string>();
333
281
  for (const descriptor of descriptors) {
334
- const codecInstances = descriptor.types?.codecTypes?.codecInstances;
335
- if (!codecInstances) continue;
336
- const descriptorId = descriptor.id ?? '<unknown>';
337
- for (const codec of codecInstances) {
282
+ const codecTypes = descriptor.types?.codecTypes;
283
+ const descriptorId = descriptor.id;
284
+ // Descriptor-side metadata is the single source of truth for `targetTypes` / `meta` / `renderOutputType`. Every contributor ships a `codecDescriptors` list on `types.codecTypes`.
285
+ for (const codecDescriptor of codecTypes?.codecDescriptors ?? []) {
338
286
  assertUniqueCodecOwner({
339
- codecId: codec.id,
287
+ codecId: codecDescriptor.codecId,
340
288
  owners,
341
289
  descriptorId,
342
- entityLabel: 'codec instance',
343
- entityOwnershipLabel: 'codec instance provider',
290
+ entityLabel: 'codec descriptor',
291
+ entityOwnershipLabel: 'codec descriptor provider',
344
292
  });
345
- owners.set(codec.id, descriptorId);
346
- byId.set(codec.id, codec);
293
+ owners.set(codecDescriptor.codecId, descriptorId);
294
+ if (Array.isArray(codecDescriptor.targetTypes)) {
295
+ targetTypesById.set(codecDescriptor.codecId, codecDescriptor.targetTypes);
296
+ }
297
+ if (codecDescriptor.meta !== undefined) {
298
+ metaById.set(codecDescriptor.codecId, codecDescriptor.meta);
299
+ }
300
+ if (typeof codecDescriptor.renderOutputType === 'function') {
301
+ renderersById.set(codecDescriptor.codecId, codecDescriptor.renderOutputType);
302
+ }
303
+ // Materialize a representative `Codec` instance for `byId.get()` so consumers reading the lookup's instance side (e.g. SQL renderer's cast-policy lookup, or the contract emitter's literal-default `encodeJson` resolver) keep finding the codec.
304
+ //
305
+ // Two cohorts:
306
+ // - Non-parameterized descriptors: factory must succeed; any throw is a real bug and we let it propagate (no silent try/catch).
307
+ // - Parameterized descriptors: try with empty params. Many parameterized codecs treat params as advisory (e.g. `pg/timestamptz@1` whose precision is rendered into the `nativeType` only and never read by the runtime codec), so an empty-params construction yields a usable representative for id-keyed lookups (e.g. emit-time literal-default encoding). Codecs whose factory genuinely requires params (e.g. `pg/vector@1` threading `length` into the runtime codec) will throw; for those, per-column instances are materialized at runtime by `buildContractCodecRegistry` and the id-keyed lookup miss is correct (the column-aware path resolves them).
308
+ if (!byId.has(codecDescriptor.codecId)) {
309
+ if (codecDescriptor.isParameterized) {
310
+ try {
311
+ const representative = codecDescriptor.factory({} as never)({
312
+ name: `<lookup:${codecDescriptor.codecId}>`,
313
+ } as Parameters<ReturnType<typeof codecDescriptor.factory>>[0]);
314
+ byId.set(codecDescriptor.codecId, representative);
315
+ } catch {
316
+ // Factory requires concrete params; skip representative materialization. Per-column instances are built at runtime; id-keyed lookup miss is the correct outcome here.
317
+ }
318
+ } else {
319
+ const representative = codecDescriptor.factory(undefined as never)({
320
+ name: `<lookup:${codecDescriptor.codecId}>`,
321
+ } as Parameters<ReturnType<typeof codecDescriptor.factory>>[0]);
322
+ byId.set(codecDescriptor.codecId, representative);
323
+ }
324
+ }
347
325
  }
348
326
  }
349
- return { get: (id) => byId.get(id) };
327
+ return {
328
+ get: (id) => byId.get(id),
329
+ targetTypesFor: (id) => targetTypesById.get(id),
330
+ metaFor: (id) => metaById.get(id),
331
+ renderOutputTypeFor: (id, params) => renderersById.get(id)?.(params),
332
+ };
350
333
  }
351
334
 
352
335
  export function validateScalarTypeCodecIds(
@@ -0,0 +1,49 @@
1
+ import type { Contract, ContractModel } from '@prisma-next/contract/types';
2
+ import type { TypesImportSpec } from '../shared/types-import-spec';
3
+
4
+ export interface GenerateContractTypesOptions {
5
+ readonly queryOperationTypeImports?: ReadonlyArray<TypesImportSpec>;
6
+ }
7
+
8
+ export interface ValidationContext {
9
+ readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
10
+ readonly operationTypeImports?: ReadonlyArray<TypesImportSpec>;
11
+ readonly extensionIds?: ReadonlyArray<string>;
12
+ }
13
+
14
+ export interface EmissionSpi {
15
+ readonly id: string;
16
+
17
+ generateStorageType(contract: Contract, storageHashTypeName: string): string;
18
+
19
+ generateModelStorageType(modelName: string, model: ContractModel): string;
20
+
21
+ getFamilyImports(): string[];
22
+
23
+ getFamilyTypeAliases(options?: GenerateContractTypesOptions): string;
24
+
25
+ getTypeMapsExpression(): string;
26
+
27
+ getContractWrapper(contractBaseName: string, typeMapsName: string): string;
28
+
29
+ /**
30
+ * Per-family resolver for typeParams that don't live inline on the
31
+ * framework-domain `ContractField`. Some families (notably SQL) let columns
32
+ * reference a named entry in `storage.types` via `typeRef`; the typeParams
33
+ * live on that named entry rather than on the domain field. The framework
34
+ * emit path consults this hook so the codec's `renderOutputType` can run
35
+ * for typeRef-shaped columns.
36
+ *
37
+ * Inline `field.type.typeParams` always takes precedence; the hook is only
38
+ * consulted when the domain field has no typeParams. Families without
39
+ * named storage types (e.g. mongo) don't implement this hook.
40
+ *
41
+ * Returns `undefined` when the field has no resolvable typeParams.
42
+ */
43
+ resolveFieldTypeParams?(
44
+ modelName: string,
45
+ fieldName: string,
46
+ model: ContractModel,
47
+ contract: Contract,
48
+ ): Record<string, unknown> | undefined;
49
+ }
@@ -0,0 +1,193 @@
1
+ export interface PslPosition {
2
+ readonly offset: number;
3
+ readonly line: number;
4
+ readonly column: number;
5
+ }
6
+
7
+ export interface PslSpan {
8
+ readonly start: PslPosition;
9
+ readonly end: PslPosition;
10
+ }
11
+
12
+ export type PslDiagnosticCode =
13
+ | 'PSL_UNTERMINATED_BLOCK'
14
+ | 'PSL_UNSUPPORTED_TOP_LEVEL_BLOCK'
15
+ | 'PSL_INVALID_ATTRIBUTE_SYNTAX'
16
+ | 'PSL_INVALID_MODEL_MEMBER'
17
+ | 'PSL_UNSUPPORTED_MODEL_ATTRIBUTE'
18
+ | 'PSL_UNSUPPORTED_FIELD_ATTRIBUTE'
19
+ | 'PSL_INVALID_RELATION_ATTRIBUTE'
20
+ | 'PSL_INVALID_REFERENTIAL_ACTION'
21
+ | 'PSL_INVALID_DEFAULT_VALUE'
22
+ | 'PSL_INVALID_ENUM_MEMBER'
23
+ | 'PSL_INVALID_TYPES_MEMBER';
24
+
25
+ export interface PslDiagnostic {
26
+ readonly code: PslDiagnosticCode;
27
+ readonly message: string;
28
+ readonly sourceId: string;
29
+ readonly span: PslSpan;
30
+ }
31
+
32
+ export interface PslDefaultFunctionValue {
33
+ readonly kind: 'function';
34
+ readonly name: 'autoincrement' | 'now';
35
+ }
36
+
37
+ export interface PslDefaultLiteralValue {
38
+ readonly kind: 'literal';
39
+ readonly value: string | number | boolean;
40
+ }
41
+
42
+ export type PslDefaultValue = PslDefaultFunctionValue | PslDefaultLiteralValue;
43
+
44
+ export type PslAttributeTarget = 'field' | 'model' | 'enum' | 'namedType';
45
+
46
+ export interface PslAttributePositionalArgument {
47
+ readonly kind: 'positional';
48
+ readonly value: string;
49
+ readonly span: PslSpan;
50
+ }
51
+
52
+ export interface PslAttributeNamedArgument {
53
+ readonly kind: 'named';
54
+ readonly name: string;
55
+ readonly value: string;
56
+ readonly span: PslSpan;
57
+ }
58
+
59
+ export type PslAttributeArgument = PslAttributePositionalArgument | PslAttributeNamedArgument;
60
+
61
+ export interface PslTypeConstructorCall {
62
+ readonly kind: 'typeConstructor';
63
+ readonly path: readonly string[];
64
+ readonly args: readonly PslAttributeArgument[];
65
+ readonly span: PslSpan;
66
+ }
67
+
68
+ export interface PslAttribute {
69
+ readonly kind: 'attribute';
70
+ readonly target: PslAttributeTarget;
71
+ readonly name: string;
72
+ readonly args: readonly PslAttributeArgument[];
73
+ readonly span: PslSpan;
74
+ }
75
+
76
+ export type PslReferentialAction = string;
77
+
78
+ export type PslFieldAttribute = PslAttribute;
79
+
80
+ export interface PslField {
81
+ readonly kind: 'field';
82
+ readonly name: string;
83
+ readonly typeName: string;
84
+ readonly typeConstructor?: PslTypeConstructorCall;
85
+ readonly optional: boolean;
86
+ readonly list: boolean;
87
+ readonly typeRef?: string;
88
+ readonly attributes: readonly PslFieldAttribute[];
89
+ readonly span: PslSpan;
90
+ }
91
+
92
+ export interface PslUniqueConstraint {
93
+ readonly kind: 'unique';
94
+ readonly fields: readonly string[];
95
+ readonly span: PslSpan;
96
+ }
97
+
98
+ export interface PslIndexConstraint {
99
+ readonly kind: 'index';
100
+ readonly fields: readonly string[];
101
+ readonly span: PslSpan;
102
+ }
103
+
104
+ export type PslModelAttribute = PslAttribute;
105
+
106
+ export interface PslModel {
107
+ readonly kind: 'model';
108
+ readonly name: string;
109
+ readonly fields: readonly PslField[];
110
+ readonly attributes: readonly PslModelAttribute[];
111
+ readonly span: PslSpan;
112
+ /**
113
+ * Optional leading comment line emitted above the `model` keyword by the
114
+ * printer. Producers (e.g. `sqlSchemaIrToPslAst`) attach introspection
115
+ * advisories such as "// WARNING: This table has no primary key in the
116
+ * database" here. The parser leaves this field unset; round-tripping a
117
+ * parsed schema does not re-attach comments.
118
+ */
119
+ readonly comment?: string;
120
+ }
121
+
122
+ export interface PslEnumValue {
123
+ readonly kind: 'enumValue';
124
+ readonly name: string;
125
+ /**
126
+ * Optional storage label for the enum member, captured from a trailing
127
+ * `@map("...")` attribute on the member line. The parser populates this
128
+ * when the source PSL carries an explicit `@map`. Producers (e.g.
129
+ * `sqlSchemaIrToPslAst`) leave it unset; the printer emits `@map(...)`
130
+ * automatically when normalisation would change the printed member name
131
+ * (so an enum value `'in-progress'` becomes `inProgress @map("in-progress")`
132
+ * in PSL, preserving the round-trip).
133
+ */
134
+ readonly mapName?: string;
135
+ readonly span: PslSpan;
136
+ }
137
+
138
+ export interface PslEnum {
139
+ readonly kind: 'enum';
140
+ readonly name: string;
141
+ readonly values: readonly PslEnumValue[];
142
+ readonly attributes: readonly PslAttribute[];
143
+ readonly span: PslSpan;
144
+ }
145
+
146
+ export interface PslCompositeType {
147
+ readonly kind: 'compositeType';
148
+ readonly name: string;
149
+ readonly fields: readonly PslField[];
150
+ readonly attributes: readonly PslAttribute[];
151
+ readonly span: PslSpan;
152
+ }
153
+
154
+ export interface PslNamedTypeDeclaration {
155
+ readonly kind: 'namedType';
156
+ readonly name: string;
157
+ /**
158
+ * Parser invariant: exactly one of `baseType` and `typeConstructor` is set.
159
+ * Expressing this as a discriminated union trips TypeScript narrowing when
160
+ * the declaration flows through helpers that accept the full union.
161
+ */
162
+ readonly baseType?: string;
163
+ readonly typeConstructor?: PslTypeConstructorCall;
164
+ readonly attributes: readonly PslAttribute[];
165
+ readonly span: PslSpan;
166
+ }
167
+
168
+ export interface PslTypesBlock {
169
+ readonly kind: 'types';
170
+ readonly declarations: readonly PslNamedTypeDeclaration[];
171
+ readonly span: PslSpan;
172
+ }
173
+
174
+ export interface PslDocumentAst {
175
+ readonly kind: 'document';
176
+ readonly sourceId: string;
177
+ readonly models: readonly PslModel[];
178
+ readonly enums: readonly PslEnum[];
179
+ readonly compositeTypes: readonly PslCompositeType[];
180
+ readonly types?: PslTypesBlock;
181
+ readonly span: PslSpan;
182
+ }
183
+
184
+ export interface ParsePslDocumentInput {
185
+ readonly schema: string;
186
+ readonly sourceId: string;
187
+ }
188
+
189
+ export interface ParsePslDocumentResult {
190
+ readonly ast: PslDocumentAst;
191
+ readonly diagnostics: readonly PslDiagnostic[];
192
+ readonly ok: boolean;
193
+ }