@prisma-next/framework-components 0.13.0 → 0.14.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.
Files changed (64) hide show
  1. package/dist/authoring.d.mts +2 -2
  2. package/dist/authoring.mjs +1 -1
  3. package/dist/{codec-DCQAerzB.d.mts → codec-types-yY3eSmi0.d.mts} +90 -67
  4. package/dist/codec-types-yY3eSmi0.d.mts.map +1 -0
  5. package/dist/codec.d.mts +31 -2
  6. package/dist/codec.d.mts.map +1 -1
  7. package/dist/codec.mjs +2 -1
  8. package/dist/codec.mjs.map +1 -1
  9. package/dist/components.d.mts +1 -1
  10. package/dist/control.d.mts +13 -25
  11. package/dist/control.d.mts.map +1 -1
  12. package/dist/control.mjs +12 -2
  13. package/dist/control.mjs.map +1 -1
  14. package/dist/{emission-types-vfpSTe63.d.mts → emission-types-C561PwcN.d.mts} +4 -4
  15. package/dist/emission-types-C561PwcN.d.mts.map +1 -0
  16. package/dist/emission.d.mts +1 -1
  17. package/dist/execution.d.mts +1 -1
  18. package/dist/{framework-authoring-R0TYCkvG.d.mts → framework-authoring-CJC4jwGo.d.mts} +121 -9
  19. package/dist/framework-authoring-CJC4jwGo.d.mts.map +1 -0
  20. package/dist/{framework-authoring-CnwPJCO4.mjs → framework-authoring-Dv5F3EFC.mjs} +51 -24
  21. package/dist/framework-authoring-Dv5F3EFC.mjs.map +1 -0
  22. package/dist/{framework-components-DDQXmW0b.d.mts → framework-components-5hA9ij1v.d.mts} +3 -3
  23. package/dist/{framework-components-DDQXmW0b.d.mts.map → framework-components-5hA9ij1v.d.mts.map} +1 -1
  24. package/dist/ir.d.mts +34 -4
  25. package/dist/ir.d.mts.map +1 -1
  26. package/dist/ir.mjs +51 -6
  27. package/dist/ir.mjs.map +1 -1
  28. package/dist/{psl-ast-Cn50B-UG.d.mts → psl-ast-B-C_9dWr.d.mts} +11 -37
  29. package/dist/psl-ast-B-C_9dWr.d.mts.map +1 -0
  30. package/dist/psl-ast.d.mts +4 -4
  31. package/dist/psl-ast.mjs +18 -32
  32. package/dist/psl-ast.mjs.map +1 -1
  33. package/dist/resolve-codec-D8EPZosv.mjs +59 -0
  34. package/dist/resolve-codec-D8EPZosv.mjs.map +1 -0
  35. package/dist/runtime-error-B2gWOtgH.mjs +37 -0
  36. package/dist/runtime-error-B2gWOtgH.mjs.map +1 -0
  37. package/dist/runtime.d.mts +12 -10
  38. package/dist/runtime.d.mts.map +1 -1
  39. package/dist/runtime.mjs +1 -33
  40. package/dist/runtime.mjs.map +1 -1
  41. package/package.json +7 -7
  42. package/src/control/control-migration-types.ts +5 -22
  43. package/src/control/control-stack.ts +28 -3
  44. package/src/control/emission-types.ts +3 -3
  45. package/src/control/psl-ast.ts +10 -61
  46. package/src/control/psl-extension-block-validator.ts +11 -9
  47. package/src/execution/runtime-error.ts +5 -55
  48. package/src/exports/authoring.ts +1 -0
  49. package/src/exports/codec.ts +7 -0
  50. package/src/exports/control.ts +0 -1
  51. package/src/exports/ir.ts +3 -1
  52. package/src/ir/entity-kind.ts +54 -0
  53. package/src/ir/storage.ts +32 -6
  54. package/src/shared/codec-descriptor.ts +5 -0
  55. package/src/shared/codec-types.ts +21 -1
  56. package/src/shared/framework-authoring.ts +118 -35
  57. package/src/shared/psl-extension-block.ts +85 -5
  58. package/src/shared/resolve-codec.ts +86 -0
  59. package/src/shared/runtime-error.ts +50 -0
  60. package/dist/codec-DCQAerzB.d.mts.map +0 -1
  61. package/dist/emission-types-vfpSTe63.d.mts.map +0 -1
  62. package/dist/framework-authoring-CnwPJCO4.mjs.map +0 -1
  63. package/dist/framework-authoring-R0TYCkvG.d.mts.map +0 -1
  64. package/dist/psl-ast-Cn50B-UG.d.mts.map +0 -1
@@ -7,6 +7,9 @@ export type {
7
7
  PslBlockParamValue,
8
8
  PslDiagnosticCode,
9
9
  PslExtensionBlock,
10
+ PslExtensionBlockAttribute,
11
+ PslExtensionBlockAttributeArg,
12
+ PslExtensionBlockParamBare,
10
13
  PslExtensionBlockParamList,
11
14
  PslExtensionBlockParamOption,
12
15
  PslExtensionBlockParamRef,
@@ -130,30 +133,6 @@ export interface PslModel {
130
133
  readonly comment?: string;
131
134
  }
132
135
 
133
- export interface PslEnumValue {
134
- readonly kind: 'enumValue';
135
- readonly name: string;
136
- /**
137
- * Optional storage label for the enum member, captured from a trailing
138
- * `@map("...")` attribute on the member line. The parser populates this
139
- * when the source PSL carries an explicit `@map`. Producers (e.g.
140
- * `sqlSchemaIrToPslAst`) leave it unset; the printer emits `@map(...)`
141
- * automatically when normalisation would change the printed member name
142
- * (so an enum value `'in-progress'` becomes `inProgress @map("in-progress")`
143
- * in PSL, preserving the round-trip).
144
- */
145
- readonly mapName?: string;
146
- readonly span: PslSpan;
147
- }
148
-
149
- export interface PslEnum {
150
- readonly kind: 'enum';
151
- readonly name: string;
152
- readonly values: readonly PslEnumValue[];
153
- readonly attributes: readonly PslAttribute[];
154
- readonly span: PslSpan;
155
- }
156
-
157
136
  /**
158
137
  * A reusable group of fields embedded in a model (a `type Name { … }` block) —
159
138
  * e.g. a MongoDB embedded document or a Postgres composite type. Unlike
@@ -202,7 +181,7 @@ export interface PslTypesBlock {
202
181
  export const UNSPECIFIED_PSL_NAMESPACE_ID = '__unspecified__';
203
182
 
204
183
  /** A value in {@link PslNamespace.entries}: a built-in entity node or an extension-contributed {@link PslExtensionBlock}. */
205
- export type PslNamespaceEntry = PslModel | PslEnum | PslCompositeType | PslExtensionBlock;
184
+ export type PslNamespaceEntry = PslModel | PslCompositeType | PslExtensionBlock;
206
185
 
207
186
  /**
208
187
  * A namespace block, or the parser's synthesised `__unspecified__` bucket for
@@ -220,8 +199,6 @@ export interface PslNamespace {
220
199
  readonly entries: Readonly<Record<string, Readonly<Record<string, PslNamespaceEntry>>>>;
221
200
  /** Built-in models, from `entries['model']`. Extension kinds: {@link namespacePslExtensionBlocks}. */
222
201
  readonly models: readonly PslModel[];
223
- /** Built-in enums, from `entries['enum']`. */
224
- readonly enums: readonly PslEnum[];
225
202
  /** Built-in composite types, from `entries['compositeType']`. */
226
203
  readonly compositeTypes: readonly PslCompositeType[];
227
204
  readonly span: PslSpan;
@@ -255,12 +232,6 @@ class PslNamespaceNode implements PslNamespace {
255
232
  );
256
233
  }
257
234
 
258
- get enums(): readonly PslEnum[] {
259
- return blindCast<readonly PslEnum[], 'entries[enum] holds only PslEnum by construction'>(
260
- Object.values(this.entries['enum'] ?? {}),
261
- );
262
- }
263
-
264
235
  get compositeTypes(): readonly PslCompositeType[] {
265
236
  return blindCast<
266
237
  readonly PslCompositeType[],
@@ -286,7 +257,6 @@ export function makePslNamespace(init: {
286
257
  */
287
258
  export function makePslNamespaceEntries(
288
259
  models: readonly PslModel[],
289
- enums: readonly PslEnum[],
290
260
  compositeTypes: readonly PslCompositeType[],
291
261
  extensionBlocks: readonly PslExtensionBlock[],
292
262
  ): Readonly<Record<string, Readonly<Record<string, PslNamespaceEntry>>>> {
@@ -300,14 +270,6 @@ export function makePslNamespaceEntries(
300
270
  container['model'] = Object.freeze(map);
301
271
  }
302
272
 
303
- if (enums.length > 0) {
304
- const map: Record<string, PslEnum> = {};
305
- for (const e of enums) {
306
- map[e.name] = e;
307
- }
308
- container['enum'] = Object.freeze(map);
309
- }
310
-
311
273
  if (compositeTypes.length > 0) {
312
274
  const map: Record<string, PslCompositeType> = {};
313
275
  for (const ct of compositeTypes) {
@@ -350,17 +312,6 @@ export function flatPslModels(ast: PslDocumentAst): readonly PslModel[] {
350
312
  );
351
313
  }
352
314
 
353
- /**
354
- * Returns all enums from every namespace in document order.
355
- */
356
- export function flatPslEnums(ast: PslDocumentAst): readonly PslEnum[] {
357
- return ast.namespaces.flatMap((ns) =>
358
- blindCast<PslEnum[], 'enum kind map contains only PslEnum by construction'>(
359
- Object.values(ns.entries['enum'] ?? {}),
360
- ),
361
- );
362
- }
363
-
364
315
  /**
365
316
  * Returns all composite types from every namespace in document order.
366
317
  */
@@ -379,20 +330,18 @@ export function flatPslCompositeTypes(ast: PslDocumentAst): readonly PslComposit
379
330
  * that is **not** in this set was contributed by an extension-block descriptor.
380
331
  *
381
332
  * Built-in keys match the PSL keyword used on each block type:
382
- * `'model'`, `'enum'`, `'compositeType'`.
333
+ * `'model'`, `'compositeType'`. The `'enum'` keyword is claimed by the
334
+ * extension-block grammar via a registered descriptor, so `entries['enum']`
335
+ * holds `PslExtensionBlock` nodes and is returned by `namespacePslExtensionBlocks`.
383
336
  */
384
- export const BUILTIN_PSL_KIND_KEYS: ReadonlySet<string> = new Set([
385
- 'model',
386
- 'enum',
387
- 'compositeType',
388
- ]);
337
+ export const BUILTIN_PSL_KIND_KEYS: ReadonlySet<string> = new Set(['model', 'compositeType']);
389
338
 
390
339
  /**
391
340
  * Returns all extension-contributed blocks in the given namespace, in
392
341
  * insertion order (the order the parser encountered them in the source).
393
342
  *
394
- * Reads from `namespace.entries`, skipping the three built-in kind keys
395
- * (`'model'`, `'enum'`, `'compositeType'`). All remaining kind maps contain
343
+ * Reads from `namespace.entries`, skipping the built-in kind keys
344
+ * (`'model'`, `'compositeType'`). All remaining kind maps contain
396
345
  * only `PslExtensionBlock` nodes by construction (see `makePslNamespaceEntries`).
397
346
  */
398
347
  export function namespacePslExtensionBlocks(ns: PslNamespace): readonly PslExtensionBlock[] {
@@ -107,15 +107,17 @@ export function validateExtensionBlock(
107
107
  const nodeKeys = new Set(Object.keys(node.parameters));
108
108
 
109
109
  // 1. Unknown parameters — keys in the node not in the descriptor.
110
- for (const key of nodeKeys) {
111
- if (!descriptorKeys.has(key)) {
112
- const captured = node.parameters[key];
113
- diagnostics.push({
114
- code: 'PSL_EXTENSION_UNKNOWN_PARAMETER',
115
- message: `Unknown parameter "${key}" in "${descriptor.keyword}" block "${node.name}". The descriptor does not declare this parameter.`,
116
- sourceId,
117
- span: captured?.span ?? node.span,
118
- });
110
+ if (!descriptor.variadicParameters) {
111
+ for (const key of nodeKeys) {
112
+ if (!descriptorKeys.has(key)) {
113
+ const captured = node.parameters[key];
114
+ diagnostics.push({
115
+ code: 'PSL_EXTENSION_UNKNOWN_PARAMETER',
116
+ message: `Unknown parameter "${key}" in "${descriptor.keyword}" block "${node.name}". The descriptor does not declare this parameter.`,
117
+ sourceId,
118
+ span: captured?.span ?? node.span,
119
+ });
120
+ }
119
121
  }
120
122
  }
121
123
 
@@ -1,9 +1,8 @@
1
- export interface RuntimeErrorEnvelope extends Error {
2
- readonly code: string;
3
- readonly category: 'PLAN' | 'CONTRACT' | 'LINT' | 'BUDGET' | 'RUNTIME';
4
- readonly severity: 'error';
5
- readonly details?: Record<string, unknown>;
6
- }
1
+ import type { RuntimeErrorEnvelope } from '../shared/runtime-error';
2
+ import { runtimeError } from '../shared/runtime-error';
3
+
4
+ export type { RuntimeErrorEnvelope } from '../shared/runtime-error';
5
+ export { isRuntimeError, runtimeError } from '../shared/runtime-error';
7
6
 
8
7
  /**
9
8
  * Stable code emitted by the runtime when an in-flight `execute()`
@@ -30,55 +29,6 @@ export type RuntimeAbortedPhase =
30
29
  | 'afterExecute'
31
30
  | 'onRow';
32
31
 
33
- /**
34
- * Type guard for the runtime-error envelope produced by `runtimeError`.
35
- *
36
- * Prefer this over duck-typing on `error.code` directly so consumers stay
37
- * insulated from the envelope's internal shape.
38
- */
39
- export function isRuntimeError(error: unknown): error is RuntimeErrorEnvelope {
40
- return (
41
- error instanceof Error &&
42
- 'code' in error &&
43
- typeof (error as { code?: unknown }).code === 'string' &&
44
- 'category' in error &&
45
- 'severity' in error
46
- );
47
- }
48
-
49
- export function runtimeError(
50
- code: string,
51
- message: string,
52
- details?: Record<string, unknown>,
53
- ): RuntimeErrorEnvelope {
54
- const error = new Error(message) as RuntimeErrorEnvelope;
55
- Object.defineProperty(error, 'name', {
56
- value: 'RuntimeError',
57
- configurable: true,
58
- });
59
-
60
- return Object.assign(error, {
61
- code,
62
- category: resolveCategory(code),
63
- severity: 'error' as const,
64
- message,
65
- details,
66
- });
67
- }
68
-
69
- function resolveCategory(code: string): RuntimeErrorEnvelope['category'] {
70
- const prefix = code.split('.')[0] ?? 'RUNTIME';
71
- switch (prefix) {
72
- case 'PLAN':
73
- case 'CONTRACT':
74
- case 'LINT':
75
- case 'BUDGET':
76
- return prefix;
77
- default:
78
- return 'RUNTIME';
79
- }
80
- }
81
-
82
32
  /**
83
33
  * Construct a `RUNTIME.ABORTED` envelope. Phase distinguishes where the
84
34
  * abort was observed — codec call sites (`encode` / `decode` / `stream`)
@@ -3,6 +3,7 @@ export type {
3
3
  AuthoringArgumentDescriptor,
4
4
  AuthoringColumnDefaultTemplate,
5
5
  AuthoringContributions,
6
+ AuthoringDiagnosticSink,
6
7
  AuthoringEntityContext,
7
8
  AuthoringEntityTypeDescriptor,
8
9
  AuthoringEntityTypeFactoryOutput,
@@ -16,6 +16,7 @@ export type {
16
16
  CodecLookup,
17
17
  CodecMeta,
18
18
  CodecRef,
19
+ CodecRegistry,
19
20
  CodecTrait,
20
21
  } from '../shared/codec-types';
21
22
  export { emptyCodecLookup, voidParamsSchema } from '../shared/codec-types';
@@ -26,3 +27,9 @@ export type {
26
27
  ColumnTypeDescriptor,
27
28
  } from '../shared/column-spec';
28
29
  export { column } from '../shared/column-spec';
30
+ export {
31
+ CONTRACT_CODEC_DESCRIPTOR_MISSING,
32
+ materializeCodec,
33
+ resolveCodecDescriptorOrThrow,
34
+ validateCodecTypeParams,
35
+ } from '../shared/resolve-codec';
@@ -47,7 +47,6 @@ export type {
47
47
  MigrationRunnerSuccessValue,
48
48
  MigrationScaffoldContext,
49
49
  OpFactoryCall,
50
- SerializedQueryPlan,
51
50
  TargetMigrationsCapability,
52
51
  } from '../control/control-migration-types';
53
52
  export type {
package/src/exports/ir.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  export { domainElementCoordinates } from '../ir/domain';
2
+ export type { AnyEntityKindDescriptor, EntityKindDescriptor } from '../ir/entity-kind';
3
+ export { hydrateNamespaceEntities } from '../ir/entity-kind';
2
4
  export type { IRNode } from '../ir/ir-node';
3
5
  export { freezeNode, IRNodeBase } from '../ir/ir-node';
4
6
  export type { Namespace } from '../ir/namespace';
5
7
  export { NamespaceBase, UNBOUND_NAMESPACE_ID } from '../ir/namespace';
6
8
  export type { EntityCoordinate, Storage } from '../ir/storage';
7
- export { elementCoordinates } from '../ir/storage';
9
+ export { elementCoordinates, entityAt, isPlainRecord } from '../ir/storage';
8
10
  export type { StorageType } from '../ir/storage-type';
@@ -0,0 +1,54 @@
1
+ import { blindCast } from '@prisma-next/utils/casts';
2
+ import type { Type } from 'arktype';
3
+
4
+ export interface EntityKindDescriptor<Input, Node> {
5
+ readonly kind: string;
6
+ // Type<unknown>, not Type<Input>: AnyEntityKindDescriptor widens Input to never, which would force an unusable Type<never>; concrete descriptors still carry their real schema.
7
+ readonly schema: Type<unknown>;
8
+ readonly construct: (input: Input) => Node;
9
+ }
10
+
11
+ export type AnyEntityKindDescriptor = EntityKindDescriptor<never, unknown>;
12
+
13
+ /**
14
+ * Hydrates a namespace's entities from raw JSON maps into IR class instances.
15
+ *
16
+ * For each kind in `entries`: if the descriptor map has a descriptor,
17
+ * construct each inner-map value; otherwise freeze-and-carry (`'carry'`)
18
+ * or throw naming the kind and nsId (`'fail'`).
19
+ *
20
+ * The single boundary cast hands `value` to `descriptor.construct` as its
21
+ * `Input`. The value satisfies the kind's `Input` either by the
22
+ * entries-input contract at authoring time or by prior `validateStorage`
23
+ * validation at hydration time.
24
+ */
25
+ export function hydrateNamespaceEntities(
26
+ entries: Readonly<Record<string, Readonly<Record<string, unknown>>>>,
27
+ kinds: ReadonlyMap<string, AnyEntityKindDescriptor>,
28
+ onUnknown: 'carry' | 'fail',
29
+ nsId?: string,
30
+ ): Record<string, Readonly<Record<string, unknown>>> {
31
+ const result: Record<string, Readonly<Record<string, unknown>>> = {};
32
+ for (const [kind, rawMap] of Object.entries(entries)) {
33
+ const descriptor = kinds.get(kind);
34
+ if (descriptor !== undefined) {
35
+ const built: Record<string, unknown> = {};
36
+ for (const [name, value] of Object.entries(rawMap)) {
37
+ built[name] = descriptor.construct(
38
+ blindCast<
39
+ never,
40
+ "value is this kind's descriptor Input: when authoring, the typed entries-input contract produces it; when hydrating, it was validated against descriptor.schema before this loop. The never target is AnyEntityKindDescriptor's erased Input parameter."
41
+ >(value),
42
+ );
43
+ }
44
+ result[kind] = Object.freeze(built);
45
+ } else if (onUnknown === 'carry') {
46
+ result[kind] = Object.freeze(rawMap);
47
+ } else {
48
+ throw new Error(
49
+ `Unknown entries key "${kind}" in namespace "${nsId ?? '?'}"; no hydration factory registered for this entity kind`,
50
+ );
51
+ }
52
+ }
53
+ return result;
54
+ }
package/src/ir/storage.ts CHANGED
@@ -1,7 +1,11 @@
1
+ import { isPlainRecord } from '@prisma-next/contract/is-plain-record';
1
2
  import type { StorageBase } from '@prisma-next/contract/types';
3
+ import { blindCast } from '@prisma-next/utils/casts';
2
4
  import type { IRNode } from './ir-node';
3
5
  import type { Namespace } from './namespace';
4
6
 
7
+ export { isPlainRecord };
8
+
5
9
  /**
6
10
  * Canonical address for a named entity in Contract IR / Schema IR.
7
11
  *
@@ -30,11 +34,11 @@ export interface EntityCoordinate {
30
34
  * value, yielded as {@link EntityCoordinate} tuples with
31
35
  * `plane: 'storage'` (the parameter type binds the plane).
32
36
  *
33
- * Iterates each namespace's `entries` slot maps structurally. Skips
37
+ * Iterates each namespace's `entries` kind maps structurally. Skips
34
38
  * non-object `entries`; `id` and `kind` are not walked (`kind` is
35
39
  * non-enumerable on concretions). For every entity-kind key under
36
40
  * `entries` whose value is a non-null object, yields one coordinate per
37
- * entity name in that map. No family-specific slot vocabulary is required.
41
+ * entity name in that map. No family-specific kind vocabulary is required.
38
42
  */
39
43
  export function* elementCoordinates(
40
44
  storage: Pick<StorageBase, 'namespaces'>,
@@ -42,15 +46,37 @@ export function* elementCoordinates(
42
46
  for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
43
47
  const entries = ns.entries;
44
48
  if (entries === null || typeof entries !== 'object') continue;
45
- for (const [entityKind, slot] of Object.entries(entries)) {
46
- if (slot === null || typeof slot !== 'object') continue;
47
- for (const entityName of Object.keys(slot)) {
49
+ for (const [entityKind, kindMap] of Object.entries(entries)) {
50
+ if (kindMap === null || typeof kindMap !== 'object') continue;
51
+ for (const entityName of Object.keys(kindMap)) {
48
52
  yield { plane: 'storage', namespaceId, entityKind, entityName };
49
53
  }
50
54
  }
51
55
  }
52
56
  }
53
57
 
58
+ /**
59
+ * Looks up a single entity in a `Storage`-shaped value by its full coordinate.
60
+ * Returns `undefined` if the namespace, entity kind, or entity name is absent.
61
+ * The type parameter is a caller assertion — the walk itself is structural
62
+ * and cannot verify the entity's shape.
63
+ */
64
+ export function entityAt<T = unknown>(
65
+ storage: Pick<StorageBase, 'namespaces'>,
66
+ coord: Pick<EntityCoordinate, 'namespaceId' | 'entityKind' | 'entityName'>,
67
+ ): T | undefined {
68
+ const ns = storage.namespaces[coord.namespaceId];
69
+ if (ns === undefined) return undefined;
70
+ const entries = ns.entries;
71
+ if (!isPlainRecord(entries)) return undefined;
72
+ const kindMap = entries[coord.entityKind];
73
+ if (!isPlainRecord(kindMap)) return undefined;
74
+ if (!Object.hasOwn(kindMap, coord.entityName)) return undefined;
75
+ return blindCast<T | undefined, 'caller asserts the entity type at this coordinate'>(
76
+ kindMap[coord.entityName],
77
+ );
78
+ }
79
+
54
80
  /**
55
81
  * Framework-level promise that every Contract IR / Schema IR carries a
56
82
  * collection of namespaces keyed by namespace id. Family storage
@@ -66,7 +92,7 @@ export function* elementCoordinates(
66
92
  * is honest at every layer.
67
93
  *
68
94
  * Extends `IRNode` so the framework's IR-walking surfaces (verifiers,
69
- * serializers) can dispatch on `Storage`-typed slots through the same
95
+ * serializers) can dispatch on `Storage`-typed fields through the same
70
96
  * IR-node alphabet as every other node — the structural dual already
71
97
  * holds in code (every concrete storage class extends an IR-node base);
72
98
  * the interface promotion makes the typing honest.
@@ -43,6 +43,8 @@ export interface CodecDescriptor<P = void> {
43
43
  readonly isParameterized: boolean;
44
44
  /** Emit-path string renderer for `contract.d.ts`. Returns the TypeScript output type expression for given params (e.g. `Vector<1536>`). Optional; absent renderers cause the emitter to fall back to the codec's base output type. Non-parameterized codecs typically omit it. */
45
45
  readonly renderOutputType?: (params: P) => string | undefined;
46
+ /** Emit-path string renderer for the `contract.d.ts` *input* position (create/update values). Returns the TypeScript input type expression for given params. Optional; absent renderers fall back to the codec's base input type. A codec supplies this when its write type is narrower than the generic codec input — e.g. an enum whose input should be the literal member union, not `string`. */
47
+ readonly renderInputType?: (params: P) => string | undefined;
46
48
  /** The curried higher-order codec. For non-parameterized codecs, the factory is constant — every call returns the same shared codec instance. For parameterized codecs, the factory is called once per `storage.types` instance (or once per inline-`typeParams` column), with `ctx` carrying the column set the resulting codec serves. */
47
49
  readonly factory: (params: P) => (ctx: CodecInstanceContext) => Codec;
48
50
  }
@@ -78,6 +80,9 @@ export abstract class CodecDescriptorImpl<TParams = void> implements CodecDescri
78
80
  /** Optional emit-path string renderer for `contract.d.ts`. Returns the TypeScript output type expression for the given params (e.g. `Vector<1536>`). Non-parameterized codecs typically omit it. */
79
81
  renderOutputType?(params: TParams): string | undefined;
80
82
 
83
+ /** Optional emit-path string renderer for the `contract.d.ts` input position. Returns the TypeScript input type expression for the given params; supplied when the write type is narrower than the generic codec input (e.g. an enum's literal member union). */
84
+ renderInputType?(params: TParams): string | undefined;
85
+
81
86
  /**
82
87
  * Materialize a curried codec factory for the given params. Concrete subclasses override with a typed return type (e.g. `factory<N>(params: { length: N }): (ctx) => VectorCodec<N>`); per-codec helpers read the typed return at the *direct* call site, which is what preserves method-level generics. Type extraction (e.g. `ReturnType<D['factory']>`) widens method generics to their constraint — that's why the column-helper surface is per-codec, not polymorphic.
83
88
  */
@@ -36,7 +36,7 @@ export interface CodecCallContext {
36
36
  /**
37
37
  * Codec-id-keyed read surface threaded into emit and authoring paths.
38
38
  *
39
- * - `get(id)` returns the runtime {@link Codec} instance for the codec id (used by `family.deserializeContract` for `decodeJson` of literal column defaults).
39
+ * - `get(id)` returns a representative {@link Codec} instance for the codec id (used by `family.deserializeContract` for `decodeJson` of literal column defaults). For parameterized codecs whose factory requires concrete params, this may return `undefined` — use `CodecRegistry.forCodecRef` instead.
40
40
  * - `targetTypesFor(id)` exposes the codec-id-keyed `targetTypes` metadata the runtime instance no longer carries (TML-2357). Returns the same array `CodecDescriptor.targetTypes` would; for Mongo (whose registration doesn't yet resolve through the unified descriptor map — TML-2324) the family-side assembly populates this directly from the contributor's codec metadata.
41
41
  * - `metaFor(id)` exposes the codec-id-keyed `meta` (e.g. SQL-side `db.sql.postgres.nativeType`) the runtime instance no longer carries.
42
42
  * - `renderOutputTypeFor(id, params)` exposes the codec-id-keyed `renderOutputType` renderer the runtime instance no longer carries. Returns `undefined` when the codec doesn't render a custom type or when the codec id is unknown.
@@ -46,6 +46,26 @@ export interface CodecLookup {
46
46
  targetTypesFor(id: string): readonly string[] | undefined;
47
47
  metaFor(id: string): CodecMeta | undefined;
48
48
  renderOutputTypeFor(id: string, params: Record<string, unknown>): string | undefined;
49
+ /** Codec-id-keyed `renderInputType` renderer for the `contract.d.ts` input position. Optional so existing lookups need not provide it; returns `undefined` when the codec renders no custom input type or the id is unknown. */
50
+ renderInputTypeFor?(id: string, params: Record<string, unknown>): string | undefined;
51
+ }
52
+
53
+ /**
54
+ * Full codec registry — the read surface of {@link CodecLookup} plus codec resolution by ref or
55
+ * column coordinate. Built once by `extractCodecLookup` and passed by reference to adapters and
56
+ * other consumers that need to materialise codecs at runtime.
57
+ *
58
+ * - `forCodecRef(ref)` materialises a codec from a {@link CodecRef}. Throws
59
+ * `RUNTIME.CODEC_DESCRIPTOR_MISSING` for unknown ids and `RUNTIME.TYPE_PARAMS_INVALID` on param
60
+ * schema rejection.
61
+ * - `forColumn(namespaceId, table, column)` returns the codec for a specific column coordinate, or
62
+ * `undefined` when no column-to-codec mapping is present. This registry is contract-free so it
63
+ * always returns `undefined` — the method exists so the object structurally satisfies the SQL
64
+ * `ContractCodecRegistry` interface.
65
+ */
66
+ export interface CodecRegistry extends CodecLookup {
67
+ forCodecRef(ref: CodecRef): Codec;
68
+ forColumn(namespaceId: string, table: string, column: string): Codec | undefined;
49
69
  }
50
70
 
51
71
  export const emptyCodecLookup: CodecLookup = {