@prisma-next/sql-relational-core 0.5.0-dev.60 → 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 (58) hide show
  1. package/README.md +45 -47
  2. package/dist/{errors-p3Ou_n9J.d.mts → errors-Chs-ph28.d.mts} +2 -2
  3. package/dist/errors-Chs-ph28.d.mts.map +1 -0
  4. package/dist/{errors-D6kqqjHM.mjs → errors-kgKOaDM1.mjs} +1 -1
  5. package/dist/{errors-D6kqqjHM.mjs.map → errors-kgKOaDM1.mjs.map} +1 -1
  6. package/dist/exports/ast.d.mts +141 -87
  7. package/dist/exports/ast.d.mts.map +1 -1
  8. package/dist/exports/ast.mjs +242 -272
  9. package/dist/exports/ast.mjs.map +1 -1
  10. package/dist/exports/codec-descriptor-registry.d.mts +18 -0
  11. package/dist/exports/codec-descriptor-registry.d.mts.map +1 -0
  12. package/dist/exports/codec-descriptor-registry.mjs +40 -0
  13. package/dist/exports/codec-descriptor-registry.mjs.map +1 -0
  14. package/dist/exports/errors.d.mts +4 -4
  15. package/dist/exports/errors.mjs +1 -1
  16. package/dist/exports/expression.d.mts +29 -24
  17. package/dist/exports/expression.d.mts.map +1 -1
  18. package/dist/exports/expression.mjs +33 -11
  19. package/dist/exports/expression.mjs.map +1 -1
  20. package/dist/exports/plan.d.mts +2 -2
  21. package/dist/exports/query-lane-context.d.mts +2 -3
  22. package/dist/exports/types.d.mts +5 -4
  23. package/dist/index.d.mts +10 -11
  24. package/dist/index.mjs +5 -5
  25. package/dist/{plan-C7SiEWkN.d.mts → plan-nwFE15re.d.mts} +2 -2
  26. package/dist/plan-nwFE15re.d.mts.map +1 -0
  27. package/dist/query-lane-context-DlWgKvvt.d.mts +175 -0
  28. package/dist/query-lane-context-DlWgKvvt.d.mts.map +1 -0
  29. package/dist/{sql-execution-plan-Dgx7BGin.d.mts → sql-execution-plan-DTfj23Tj.d.mts} +2 -2
  30. package/dist/{sql-execution-plan-Dgx7BGin.d.mts.map → sql-execution-plan-DTfj23Tj.d.mts.map} +1 -1
  31. package/dist/{types-DUL-3vy6.mjs → types-CO7zrXfK.mjs} +15 -7
  32. package/dist/types-CO7zrXfK.mjs.map +1 -0
  33. package/dist/{types-DviRR7AL.d.mts → types-G3hdNPZZ.d.mts} +4 -4
  34. package/dist/{types-DviRR7AL.d.mts.map → types-G3hdNPZZ.d.mts.map} +1 -1
  35. package/dist/{types-B4dL4lc3.d.mts → types-U74HFwNI.d.mts} +22 -4
  36. package/dist/types-U74HFwNI.d.mts.map +1 -0
  37. package/dist/{types-BUlUvdIU.d.mts → types-dPxXIUPS.d.mts} +3 -3
  38. package/dist/{types-BUlUvdIU.d.mts.map → types-dPxXIUPS.d.mts.map} +1 -1
  39. package/package.json +10 -8
  40. package/src/ast/adapter-types.ts +3 -19
  41. package/src/ast/codec-types.ts +53 -541
  42. package/src/ast/sql-codec-helpers.ts +79 -0
  43. package/src/ast/sql-codecs.ts +280 -137
  44. package/src/ast/types.ts +33 -8
  45. package/src/ast/validate-param-refs.ts +39 -0
  46. package/src/codec-descriptor-registry.ts +52 -0
  47. package/src/exports/ast.ts +2 -0
  48. package/src/exports/codec-descriptor-registry.ts +1 -0
  49. package/src/expression.ts +40 -23
  50. package/src/query-lane-context.ts +14 -96
  51. package/dist/codec-types-DJEaWT36.d.mts +0 -313
  52. package/dist/codec-types-DJEaWT36.d.mts.map +0 -1
  53. package/dist/errors-p3Ou_n9J.d.mts.map +0 -1
  54. package/dist/plan-C7SiEWkN.d.mts.map +0 -1
  55. package/dist/query-lane-context-Bwca4sc8.d.mts +0 -150
  56. package/dist/query-lane-context-Bwca4sc8.d.mts.map +0 -1
  57. package/dist/types-B4dL4lc3.d.mts.map +0 -1
  58. package/dist/types-DUL-3vy6.mjs.map +0 -1
@@ -1,28 +1,21 @@
1
- import type { JsonValue } from '@prisma-next/contract/types';
2
1
  import type {
3
2
  Codec as BaseCodec,
4
3
  CodecCallContext,
4
+ CodecDescriptor,
5
5
  CodecInstanceContext,
6
6
  CodecTrait,
7
7
  } from '@prisma-next/framework-components/codec';
8
- import { ifDefined } from '@prisma-next/utils/defined';
9
- import type { Type } from 'arktype';
10
- import type { O } from 'ts-toolbelt';
11
8
 
12
- export type { CodecCallContext, CodecTrait } from '@prisma-next/framework-components/codec';
9
+ export type {
10
+ CodecCallContext,
11
+ CodecDescriptor,
12
+ CodecTrait,
13
+ } from '@prisma-next/framework-components/codec';
13
14
 
14
15
  /**
15
- * SQL-family addressing of a single column. The decode site populates a
16
- * `SqlColumnRef` whenever it can resolve the cell to a single underlying
17
- * `(table, column)` (the typical case for projected columns from a
18
- * single-table source); cells the runtime cannot resolve (aggregate
19
- * aliases, include aggregate fields, computed projections without a
20
- * simple ref) get `column = undefined`.
16
+ * SQL-family addressing of a single column. The decode site populates a `SqlColumnRef` whenever it can resolve the cell to a single underlying `(table, column)` (the typical case for projected columns from a single-table source); cells the runtime cannot resolve (aggregate aliases, include aggregate fields, computed projections without a simple ref) get `column = undefined`.
21
17
  *
22
- * The shape is a structural projection of the runtime's `ColumnRef` so
23
- * the SQL decode site can reuse the resolution it already performs for
24
- * `RUNTIME.DECODE_FAILED` envelope construction without allocating
25
- * twice per cell.
18
+ * The shape is a structural projection of the runtime's `ColumnRef` so the SQL decode site can reuse the resolution it already performs for `RUNTIME.DECODE_FAILED` envelope construction without allocating twice per cell.
26
19
  */
27
20
  export interface SqlColumnRef {
28
21
  readonly table: string;
@@ -30,92 +23,29 @@ export interface SqlColumnRef {
30
23
  }
31
24
 
32
25
  /**
33
- * SQL-family per-call context. Extends the framework {@link CodecCallContext}
34
- * (which carries `signal` only) with `column?: SqlColumnRef`, populated
35
- * on **decode** call sites that can resolve a single underlying column
36
- * ref. Encode call sites currently leave `column` undefined (encode-time
37
- * column context is the middleware's domain).
26
+ * SQL-family per-call context. Extends the framework {@link CodecCallContext} (which carries `signal` only) with `column?: SqlColumnRef`, populated on **decode** call sites that can resolve a single underlying column ref. Encode call sites currently leave `column` undefined (encode-time column context is the middleware's domain).
38
27
  *
39
- * SQL codec authors writing a `(value, ctx)` author function for the SQL
40
- * `codec()` factory observe this type. The framework codec dispatch
41
- * surface (and Mongo) sees only the base `CodecCallContext`.
28
+ * SQL codec authors writing codec methods observe this type via {@link SqlCodec}. The framework codec dispatch surface (and Mongo) sees only the base `CodecCallContext`.
42
29
  */
43
30
  export interface SqlCodecCallContext extends CodecCallContext {
44
31
  readonly column?: SqlColumnRef;
45
32
  }
46
33
 
47
34
  /**
48
- * SQL-family per-instance context. Extends the framework
49
- * {@link CodecInstanceContext} (`name` only) with `usedAt`, the set of
50
- * `(table, column)` pairs the resolved codec serves.
35
+ * SQL-family per-instance context. Extends the framework {@link CodecInstanceContext} (`name` only) with `usedAt`, the set of `(table, column)` pairs the resolved codec serves.
51
36
  *
52
- * - For `typeRef` columns sharing one named `storage.types` instance, the
53
- * array lists every referencing columna column-scoped stateful codec
54
- * (e.g. encryption) can derive aggregated per-instance state across all
55
- * the columns sharing the named instance.
56
- * - For inline-`typeParams` columns, the array has exactly one entry —
57
- * the column that owns the inline params.
58
- * - For shared non-parameterized codecs, the array carries one
59
- * representative entry (the column that triggered materialization);
60
- * the codec is shared across every column with that codec id, so the
61
- * `usedAt` is informational only.
37
+ * - For `typeRef` columns sharing one named `storage.types` instance, the array lists every referencing column — a column-scoped stateful codec (e.g. encryption) can derive aggregated per-instance state across all the columns sharing the named instance.
38
+ * - For inline-`typeParams` columns, the array has exactly one entrythe column that owns the inline params.
39
+ * - For shared non-parameterized codecs, the array carries one representative entry (the column that triggered materialization); the codec is shared across every column with that codec id, so the `usedAt` is informational only.
62
40
  *
63
- * SQL extensions consuming `usedAt` (e.g. column-scoped state derivation)
64
- * type their factory parameter as `SqlCodecInstanceContext`. Extensions
65
- * that don't read `usedAt` type their factory parameter as the
66
- * family-agnostic {@link CodecInstanceContext} — a `SqlCodecInstanceContext`
67
- * is structurally assignable to the base.
41
+ * SQL extensions consuming `usedAt` (e.g. column-scoped state derivation) type their factory parameter as `SqlCodecInstanceContext`. Extensions that don't read `usedAt` type their factory parameter as the family-agnostic {@link CodecInstanceContext} — a `SqlCodecInstanceContext` is structurally assignable to the base.
68
42
  */
69
43
  export interface SqlCodecInstanceContext extends CodecInstanceContext {
70
44
  readonly usedAt: ReadonlyArray<{ readonly table: string; readonly column: string }>;
71
45
  }
72
46
 
73
47
  /**
74
- * Legacy adapter-level descriptor for parameterized codecs that require
75
- * type-parameter validation at compile time. The runtime descriptor
76
- * (`RuntimeParameterizedCodecDescriptor` in `@prisma-next/sql-runtime`)
77
- * has migrated to the unified `CodecDescriptor<P>` shape with
78
- * `factory: (P) => (CodecInstanceContext) => Codec`; this descriptor stays only because
79
- * the SQL `Adapter.parameterizedCodecs()` surface still returns
80
- * `CodecParamsDescriptor[]` (compile-time typeParams validation only,
81
- * not runtime materialization).
82
- *
83
- * Retirement is tracked under TML-2357 T3.5.4 (single registration slot)
84
- * — the adapter-level `parameterizedCodecs()` collapses into the unified
85
- * runtime descriptor map once contributors migrate fully.
86
- *
87
- * @template TParams - The shape of the type parameters (e.g., `{ length: number }`)
88
- * @template THelper - The type returned by the optional `init` hook
89
- */
90
- export interface CodecParamsDescriptor<TParams = Record<string, unknown>, THelper = unknown> {
91
- /** The codec ID this descriptor applies to (e.g., 'pg/vector@1') */
92
- readonly codecId: string;
93
-
94
- /**
95
- * Arktype schema for validating typeParams.
96
- * Used to validate both storage.types entries and inline column typeParams.
97
- */
98
- readonly paramsSchema: Type<TParams>;
99
-
100
- /**
101
- * Optional init hook called during runtime context creation.
102
- * Receives validated params and returns a helper object to be stored in context.types.
103
- * If not provided, the validated params are stored directly.
104
- *
105
- * Predecessor pattern. The runtime descriptor's curried
106
- * `factory: (P) => (CodecInstanceContext) => Codec` subsumes this hook — per-instance
107
- * state lives on the resolved codec rather than in a parallel
108
- * `TypeHelperRegistry` entry. Retirement tracked under TML-2357 T3.5.2
109
- * (narrow runtime `Codec` interface) and T3.5.4 (single registration
110
- * slot). Adapter-level callers reading codec-self-carried `init` should
111
- * migrate to the runtime descriptor map's factory instead.
112
- */
113
- readonly init?: (params: TParams) => THelper;
114
- }
115
-
116
- /**
117
- * Codec metadata for database-specific type information.
118
- * Used for schema introspection and verification.
48
+ * Codec metadata for database-specific type information. Used for schema introspection and verification.
119
49
  */
120
50
  export interface CodecMeta {
121
51
  readonly db?: {
@@ -128,503 +58,85 @@ export interface CodecMeta {
128
58
  }
129
59
 
130
60
  /**
131
- * SQL codec — extends the framework codec base with SQL-specific metadata:
132
- * driver-native type info (`meta.db.sql.<dialect>.nativeType`) and an
133
- * optional parameterized-codec descriptor (`paramsSchema` + `init`) for
134
- * codecs that require type-parameter validation (e.g. `pg/vector@1`).
61
+ * SQL codec — extends the framework codec base by narrowing the per-call context to the SQL-family {@link SqlCodecCallContext} (adds `column?: SqlColumnRef`). TypeScript treats method-syntax declarations bivariantly, so the SQL narrowing is structurally compatible with the framework {@link BaseCodec} super-interface.
135
62
  *
136
- * `encode` and `decode` are redeclared here to narrow the per-call
137
- * context to the SQL-family {@link SqlCodecCallContext} (adds
138
- * `column?: SqlColumnRef`). TypeScript treats method-syntax declarations
139
- * bivariantly, so the SQL narrowing is structurally compatible with the
140
- * framework {@link BaseCodec} super-interface.
63
+ * Codec-id-keyed static metadata (`traits`, `targetTypes`, `meta`, `paramsSchema`, `renderOutputType`) lives on the unified {@link import('@prisma-next/framework-components/codec').CodecDescriptor} — the codec instance itself only carries `id` plus the four conversion methods.
141
64
  *
142
- * Note: `paramsSchema` and `init` here are the legacy adapter-level slots
143
- * mirrored from {@link CodecParamsDescriptor}. The runtime materialization
144
- * path uses `RuntimeParameterizedCodecDescriptor` (in
145
- * `@prisma-next/sql-runtime`) via the unified `CodecDescriptor<P>` shape;
146
- * codec-self-carried `paramsSchema`/`init` retire under TML-2357 (T3.5.2
147
- * narrows the runtime `Codec` interface; T3.5.4 collapses the parallel
148
- * registration slots).
149
- *
150
- * See `Codec` in `@prisma-next/framework-components/codec` for the codec
151
- * contract that this interface extends.
65
+ * See `Codec` in `@prisma-next/framework-components/codec` for the codec contract that this interface extends.
152
66
  */
153
67
  export interface Codec<
154
68
  Id extends string = string,
155
69
  TTraits extends readonly CodecTrait[] = readonly CodecTrait[],
156
70
  TWire = unknown,
157
71
  TInput = unknown,
158
- TParams = Record<string, unknown>,
159
- THelper = unknown,
160
72
  > extends BaseCodec<Id, TTraits, TWire, TInput> {
161
73
  encode(value: TInput, ctx: SqlCodecCallContext): Promise<TWire>;
162
74
  decode(wire: TWire, ctx: SqlCodecCallContext): Promise<TInput>;
163
- readonly meta?: CodecMeta;
164
- readonly paramsSchema?: Type<TParams>;
165
- /**
166
- * Predecessor init hook. Retirement tracked under TML-2357 (T3.5.2 /
167
- * T3.5.4); the unified runtime descriptor's
168
- * `factory: (P) => (CodecInstanceContext) => Codec` is the replacement.
169
- */
170
- readonly init?: (params: TParams) => THelper;
171
75
  }
172
76
 
173
77
  /**
174
78
  * Contract-bound codec registry.
175
79
  *
176
- * The dispatch interface for encode/decode at runtime: built once at
177
- * `ExecutionContext` construction time by walking the contract's
178
- * `storage.tables[].columns[]` and resolving each column to either a per-
179
- * instance parameterized codec (via `descriptor.factory(typeParams)(ctx)`)
180
- * or the shared codec instance from the legacy `CodecRegistry` (for non-
181
- * parameterized codecs). The dispatch path calls
182
- * `forColumn(table, column).encode/decode(...)` and doesn't know whether
183
- * the codec is parameterized.
184
- *
185
- * `forCodecId(codecId)` is a fallback for sites that don't carry the
186
- * `(table, column)` ref through to the encode/decode call site —
187
- * primarily the param-encoding path, where `ParamRef.refs` is not
188
- * populated by the SQL builder today (every `ParamRef` carries `codecId`
189
- * but not the column it relates to). For the parameterized codecs shipped
190
- * at Phase B, encode is per-instance-stateless (pgvector formats
191
- * `[v1,v2,v3]` regardless of length; JSON's `encode` is `JSON.stringify`
192
- * regardless of schema), so a codec-id-keyed lookup yields a structurally
193
- * equivalent encoder; the fallback is the bridge that lets the legacy
194
- * `codecs:` registration retire from the dispatch path while staying as
195
- * the codec-id-only source for now.
80
+ * The dispatch interface for encode/decode at runtime: built once at `ExecutionContext` construction time by walking the contract's `storage.tables[].columns[]` and resolving each column through its descriptor's factory (per-instance for parameterized columns; the cached shared codec for non-parameterized columns). The dispatch path calls `forColumn(table, column).encode/decode(...)` and doesn't know whether the codec
81
+ * is parameterized.
196
82
  *
197
- * The encode-side fallback is the AC-5-deferred carve-out documented in
198
- * the codec-registry-unification spec § Non-functional constraints.
199
- * TML-2357 retires the fallback by threading `ParamRef.refs` through
200
- * column-bound construction sites.
83
+ * `forCodecId(codecId)` is the refs-less fallback. Every column-bound `ParamRef` carries `refs: { table; column }` and the builder-pipeline `validateParamRefRefs` pass enforces refs on every parameterized `ParamRef` before encode runs, so this fallback is only reached for non-parameterized codec ids.
201
84
  */
202
85
  export interface ContractCodecRegistry {
203
86
  /**
204
- * Resolve the codec for `(table, column)`. Returns the per-instance
205
- * parameterized codec for parameterized columns, the shared codec for
206
- * non-parameterized columns, or `undefined` if the column is unknown
207
- * or the codec isn't registered.
87
+ * Resolve the codec for `(table, column)`. Returns the per-instance parameterized codec for parameterized columns, the shared codec for non-parameterized columns, or `undefined` if the column is unknown or the codec isn't registered.
208
88
  */
209
89
  forColumn(table: string, column: string): Codec | undefined;
210
90
 
211
91
  /**
212
- * Resolve a codec by id. Returns the same codec instance the legacy
213
- * `CodecRegistry.get(codecId)` would return for non-parameterized
214
- * codecs that's the shared instance; for parameterized codecs that's
215
- * a representative resolved instance. Used by sites that don't carry
216
- * `(table, column)` through to the encode/decode call site (the AC-5
217
- * carve-out path).
92
+ * Resolve a codec by id. For non-parameterized codecs this returns the canonical shared instance materialized once at context construction; for parameterized codecs it returns the column-resolved instance when a single column declares the codec id, or the `factory(undefined)` representative when the descriptor's factory is parameter-tolerant. Used by refs-less call sites; the validator pass guarantees the call site's
93
+ * `codecId` is non-parameterized (or parameter-tolerant) at this boundary.
218
94
  */
219
95
  forCodecId(codecId: string): Codec | undefined;
220
96
  }
221
97
 
222
98
  /**
223
- * Registry interface for codecs organized by ID and by contract scalar type.
224
- *
225
- * The registry allows looking up codecs by their namespaced ID or by the
226
- * contract scalar types they handle. Multiple codecs may handle the same
227
- * scalar type; ordering in byScalar reflects preference (adapter first,
228
- * then packs, then app overrides).
99
+ * Variance-erased descriptor type used for heterogeneous storage in collection containers and on the unified contributor `codecs:` slot. The descriptor's `factory` and `renderOutputType` are contravariant in `P`, so descriptors with different params shapes are not in a subtype relationship; collecting them into one container needs an explicit variance erasure rather than `CodecDescriptor<unknown>` (which is the
100
+ * narrowest, not the widest, of the family).
229
101
  */
230
- export interface CodecRegistry {
231
- get(id: string): Codec<string> | undefined;
232
- has(id: string): boolean;
233
- getByScalar(scalar: string): readonly Codec<string>[];
234
- getDefaultCodec(scalar: string): Codec<string> | undefined;
235
- register(codec: Codec<string>): void;
236
- /** Returns true if the codec with this ID has the given trait. */
237
- hasTrait(codecId: string, trait: CodecTrait): boolean;
238
- /** Returns all traits for a codec, or an empty array if not found. */
239
- traitsOf(codecId: string): readonly CodecTrait[];
240
- [Symbol.iterator](): Iterator<Codec<string>>;
241
- values(): IterableIterator<Codec<string>>;
242
- }
243
-
244
- /**
245
- * Implementation of CodecRegistry.
246
- */
247
- class CodecRegistryImpl implements CodecRegistry {
248
- private readonly _byId = new Map<string, Codec<string>>();
249
- private readonly _byScalar = new Map<string, Codec<string>[]>();
250
-
251
- /**
252
- * Map-like interface for codec lookup by ID.
253
- * Example: registry.get('pg/text@1')
254
- */
255
- get(id: string): Codec<string> | undefined {
256
- return this._byId.get(id);
257
- }
258
-
259
- /**
260
- * Check if a codec with the given ID is registered.
261
- */
262
- has(id: string): boolean {
263
- return this._byId.has(id);
264
- }
265
-
266
- /**
267
- * Get all codecs that handle a given scalar type.
268
- * Returns an empty frozen array if no codecs are found.
269
- * Example: registry.getByScalar('text') → [codec1, codec2, ...]
270
- */
271
- getByScalar(scalar: string): readonly Codec<string>[] {
272
- return this._byScalar.get(scalar) ?? Object.freeze([]);
273
- }
274
-
275
- /**
276
- * Get the default codec for a scalar type (first registered codec).
277
- * Returns undefined if no codec handles this scalar type.
278
- */
279
- getDefaultCodec(scalar: string): Codec<string> | undefined {
280
- const _codecs = this._byScalar.get(scalar);
281
- return _codecs?.[0];
282
- }
283
-
284
- /**
285
- * Register a codec in the registry.
286
- * Throws an error if a codec with the same ID is already registered.
287
- *
288
- * @param codec - The codec to register
289
- * @throws Error if a codec with the same ID already exists
290
- */
291
- register(codec: Codec<string>): void {
292
- if (this._byId.has(codec.id)) {
293
- throw new Error(`Codec with ID '${codec.id}' is already registered`);
294
- }
102
+ // biome-ignore lint/suspicious/noExplicitAny: descriptor variance erasure — `P` is contravariant on the factory and renderOutputType slots, so heterogeneous descriptor storage cannot use `unknown`.
103
+ export type AnyCodecDescriptor = CodecDescriptor<any>;
295
104
 
296
- this._byId.set(codec.id, codec);
105
+ type DescriptorResolvedCodec<D> =
106
+ D extends CodecDescriptor<infer _P> ? ReturnType<ReturnType<D['factory']>> : never;
297
107
 
298
- // Update byScalar mapping
299
- for (const scalarType of codec.targetTypes) {
300
- const existing = this._byScalar.get(scalarType);
301
- if (existing) {
302
- existing.push(codec);
303
- } else {
304
- this._byScalar.set(scalarType, [codec]);
305
- }
306
- }
307
- }
108
+ export type DescriptorCodecId<D> = D extends AnyCodecDescriptor ? D['codecId'] : never;
308
109
 
309
- hasTrait(codecId: string, trait: CodecTrait): boolean {
310
- const codec = this._byId.get(codecId);
311
- return codec?.traits?.includes(trait) ?? false;
312
- }
313
-
314
- traitsOf(codecId: string): readonly CodecTrait[] {
315
- return this._byId.get(codecId)?.traits ?? [];
316
- }
317
-
318
- /**
319
- * Returns an iterator over all registered codecs.
320
- * Useful for iterating through codecs from another registry.
321
- */
322
- *[Symbol.iterator](): Iterator<Codec<string>> {
323
- for (const codec of this._byId.values()) {
324
- yield codec;
325
- }
326
- }
327
-
328
- /**
329
- * Returns an iterable of all registered codecs.
330
- */
331
- values(): IterableIterator<Codec<string>> {
332
- return this._byId.values();
333
- }
334
- }
335
-
336
- /**
337
- * Conditional bundle for `encodeJson`/`decodeJson`: when `TInput` is
338
- * structurally assignable to `JsonValue` the identity defaults are
339
- * sound and both fields are optional; otherwise both fields are
340
- * required so an author cannot silently produce a non-JSON-safe
341
- * contract artifact.
342
- */
343
- type JsonRoundTripConfig<TInput> = [TInput] extends [JsonValue]
344
- ? {
345
- encodeJson?: (value: TInput) => JsonValue;
346
- decodeJson?: (json: JsonValue) => TInput;
347
- }
348
- : {
349
- encodeJson: (value: TInput) => JsonValue;
350
- decodeJson: (json: JsonValue) => TInput;
351
- };
110
+ export type DescriptorCodecInput<D> =
111
+ DescriptorResolvedCodec<D> extends BaseCodec<string, readonly CodecTrait[], unknown, infer In>
112
+ ? In
113
+ : never;
352
114
 
353
115
  /**
354
- * Construct a SQL codec from author functions and optional metadata.
355
- *
356
- * Author `encode` and `decode` as sync or async functions; the factory
357
- * produces a {@link Codec} whose query-time methods follow the boundary
358
- * contract documented on `Codec`. Authors receive a second `ctx` options
359
- * argument carrying the SQL-family per-call context; ignore it if you
360
- * don't need it.
116
+ * Resolve the trait union for a descriptor `D`.
361
117
  *
362
- * Both `encode` and `decode` are required so `TInput` and `TWire` are
363
- * always covered by an explicit author function the factory installs
364
- * no identity fallback. `encodeJson` and `decodeJson` default to identity
365
- * **only when `TInput` is assignable to `JsonValue`**; otherwise both are
366
- * required so the contract artifact stays JSON-safe.
118
+ * Reads `traits` directly off the descriptor — concrete descriptor classes declare `override readonly traits = [...] as const`, which preserves the literal trait tuple at the descriptor type. Reading from the resolved codec instance (`CodecImpl<…, TTraits, …>`) would lose the literal because `Codec` carries `TTraits` only on its optional phantom slot (`readonly __codecTraits?: TTraits`); codecs extending `CodecImpl`
119
+ * have no required structural site that pins `TTraits`, so a descriptor-keyed extractor reading from the codec instance would widen to the broad `CodecTrait` union.
367
120
  */
368
- export function codec<
369
- Id extends string,
370
- const TTraits extends readonly CodecTrait[] = readonly [],
371
- TWire = unknown,
372
- TInput = unknown,
373
- TParams = Record<string, unknown>,
374
- THelper = unknown,
375
- >(
376
- config: {
377
- typeId: Id;
378
- targetTypes: readonly string[];
379
- encode: (value: TInput, ctx: SqlCodecCallContext) => TWire | Promise<TWire>;
380
- decode: (wire: TWire, ctx: SqlCodecCallContext) => TInput | Promise<TInput>;
381
- meta?: CodecMeta;
382
- paramsSchema?: Type<TParams>;
383
- init?: (params: TParams) => THelper;
384
- traits?: TTraits;
385
- renderOutputType?: (typeParams: Record<string, unknown>) => string | undefined;
386
- } & JsonRoundTripConfig<TInput>,
387
- ): Codec<Id, TTraits, TWire, TInput, TParams, THelper> {
388
- const identity = (v: unknown) => v;
389
- // The runtime allocates one `SqlCodecCallContext` per `runtime.execute()`
390
- // call (no caller-supplied `signal` produces `{}` instead of `undefined`)
391
- // and threads it as a non-optional reference to every codec call. The
392
- // author surface keeps the second parameter optional so single-arg
393
- // `(value) => …` authors continue to satisfy the signature via
394
- // TypeScript's bivariance for trailing parameters.
395
- const userEncode = config.encode;
396
- const userDecode = config.decode;
397
- // The conditional JsonRoundTripConfig narrows TInput|JsonValue at the
398
- // boundary; widen back to the generic shape inside the factory body.
399
- const widenedConfig = config as {
400
- encodeJson?: (value: TInput) => JsonValue;
401
- decodeJson?: (json: JsonValue) => TInput;
402
- };
403
- return {
404
- id: config.typeId,
405
- targetTypes: config.targetTypes,
406
- ...ifDefined('meta', config.meta),
407
- ...ifDefined('paramsSchema', config.paramsSchema),
408
- ...ifDefined('init', config.init),
409
- ...ifDefined(
410
- 'traits',
411
- config.traits ? (Object.freeze([...config.traits]) as TTraits) : undefined,
412
- ),
413
- ...ifDefined('renderOutputType', config.renderOutputType),
414
- encode: (value, ctx) => {
415
- try {
416
- return Promise.resolve(userEncode(value, ctx));
417
- } catch (error) {
418
- return Promise.reject(error);
419
- }
420
- },
421
- decode: (wire, ctx) => {
422
- try {
423
- return Promise.resolve(userDecode(wire, ctx));
424
- } catch (error) {
425
- return Promise.reject(error);
426
- }
427
- },
428
- encodeJson: (widenedConfig.encodeJson ?? identity) as (value: TInput) => JsonValue,
429
- decodeJson: (widenedConfig.decodeJson ?? identity) as (json: JsonValue) => TInput,
430
- };
121
+ export type DescriptorCodecTraits<D> = D extends {
122
+ readonly traits: infer TTraits extends readonly CodecTrait[];
431
123
  }
124
+ ? TTraits[number] & CodecTrait
125
+ : never;
432
126
 
433
127
  /**
434
- * Type helpers to extract codec types.
435
- */
436
- export type CodecId<T> =
437
- T extends Codec<infer Id> ? Id : T extends { readonly id: infer Id } ? Id : never;
438
- export type CodecInput<T> =
439
- T extends Codec<string, readonly CodecTrait[], unknown, infer In> ? In : never;
440
- export type CodecTraits<T> =
441
- T extends Codec<string, infer TTraits> ? TTraits[number] & CodecTrait : never;
442
-
443
- /**
444
- * Type helper to extract codec types from builder instance.
128
+ * Project a record of {@link AnyCodecDescriptor}s keyed by scalar name onto the codec-id-keyed `CodecTypes` shape consumed by emit and no-emit type pipelines (`{ readonly [codecId]: { input; output; traits } }`).
129
+ *
130
+ * Canonical extractor for the descriptor-keyed type pipeline; the legacy instance-keyed extractor and its `mkCodec`-bound builder retired alongside the carrier deletion.
445
131
  */
446
132
  export type ExtractCodecTypes<
447
- ScalarNames extends { readonly [K in keyof ScalarNames]: Codec<string> } = Record<never, never>,
133
+ ScalarNames extends {
134
+ readonly [K in keyof ScalarNames]: AnyCodecDescriptor;
135
+ } = Record<never, never>,
448
136
  > = {
449
- readonly [K in keyof ScalarNames as ScalarNames[K] extends Codec<infer Id> ? Id : never]: {
450
- readonly input: CodecInput<ScalarNames[K]>;
451
- readonly output: CodecInput<ScalarNames[K]>;
452
- readonly traits: CodecTraits<ScalarNames[K]>;
137
+ readonly [K in keyof ScalarNames as DescriptorCodecId<ScalarNames[K]>]: {
138
+ readonly input: DescriptorCodecInput<ScalarNames[K]>;
139
+ readonly output: DescriptorCodecInput<ScalarNames[K]>;
140
+ readonly traits: DescriptorCodecTraits<ScalarNames[K]>;
453
141
  };
454
142
  };
455
-
456
- /**
457
- * Type helper to extract data type IDs from builder instance.
458
- * Uses ExtractCodecTypes which preserves literal types as keys.
459
- * Since ExtractCodecTypes<Record<K, ScalarNames[K]>> has exactly one key (the Id),
460
- * we extract it by creating a mapped type that uses the Id as both key and value,
461
- * then extract the value type. This preserves literal types.
462
- */
463
- export type ExtractDataTypes<
464
- ScalarNames extends { readonly [K in keyof ScalarNames]: Codec<string> },
465
- > = {
466
- readonly [K in keyof ScalarNames]: {
467
- readonly [Id in keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>]: Id;
468
- }[keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>];
469
- };
470
-
471
- /**
472
- * Builder interface for declaring codecs.
473
- */
474
- export interface CodecDefBuilder<
475
- ScalarNames extends { readonly [K in keyof ScalarNames]: Codec<string> } = Record<never, never>,
476
- > {
477
- readonly CodecTypes: ExtractCodecTypes<ScalarNames>;
478
-
479
- add<ScalarName extends string, CodecImpl extends Codec<string>>(
480
- scalarName: ScalarName,
481
- codecImpl: CodecImpl,
482
- ): CodecDefBuilder<
483
- O.Overwrite<ScalarNames, Record<ScalarName, CodecImpl>> & Record<ScalarName, CodecImpl>
484
- >;
485
-
486
- readonly codecDefinitions: {
487
- readonly [K in keyof ScalarNames]: {
488
- readonly typeId: ScalarNames[K] extends Codec<infer Id extends string> ? Id : never;
489
- readonly scalar: K;
490
- readonly codec: ScalarNames[K];
491
- readonly input: CodecInput<ScalarNames[K]>;
492
- readonly output: CodecInput<ScalarNames[K]>;
493
- readonly jsType: CodecInput<ScalarNames[K]>;
494
- };
495
- };
496
-
497
- readonly dataTypes: {
498
- readonly [K in keyof ScalarNames]: {
499
- readonly [Id in keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>]: Id;
500
- }[keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>];
501
- };
502
- }
503
-
504
- /**
505
- * Implementation of CodecDefBuilder.
506
- */
507
- class CodecDefBuilderImpl<
508
- ScalarNames extends { readonly [K in keyof ScalarNames]: Codec<string> } = Record<never, never>,
509
- > implements CodecDefBuilder<ScalarNames>
510
- {
511
- private readonly _codecs: ScalarNames;
512
-
513
- public readonly CodecTypes: ExtractCodecTypes<ScalarNames>;
514
- public readonly dataTypes: {
515
- readonly [K in keyof ScalarNames]: {
516
- readonly [Id in keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>]: Id;
517
- }[keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>];
518
- };
519
-
520
- constructor(codecs: ScalarNames) {
521
- this._codecs = codecs;
522
-
523
- // Populate CodecTypes from codecs
524
- const codecTypes: Record<
525
- string,
526
- { readonly input: unknown; readonly output: unknown; readonly traits: unknown }
527
- > = {};
528
- for (const [, codecImpl] of Object.entries(this._codecs)) {
529
- const codecImplTyped = codecImpl as Codec<string>;
530
- codecTypes[codecImplTyped.id] = {
531
- input: undefined as unknown as CodecInput<typeof codecImplTyped>,
532
- output: undefined as unknown as CodecInput<typeof codecImplTyped>,
533
- traits: undefined as unknown as CodecTraits<typeof codecImplTyped>,
534
- };
535
- }
536
- this.CodecTypes = codecTypes as ExtractCodecTypes<ScalarNames>;
537
-
538
- // Populate dataTypes from codecs - extract id property from each codec
539
- // Build object preserving keys from ScalarNames
540
- // Type assertion is safe because we know ScalarNames structure matches the return type
541
- // biome-ignore lint/suspicious/noExplicitAny: dynamic codec mapping requires any
542
- const dataTypes = {} as any;
543
- for (const key in this._codecs) {
544
- if (Object.hasOwn(this._codecs, key)) {
545
- const codec = this._codecs[key] as Codec<string>;
546
- dataTypes[key] = codec.id;
547
- }
548
- }
549
- this.dataTypes = dataTypes as {
550
- readonly [K in keyof ScalarNames]: {
551
- readonly [Id in keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>]: Id;
552
- }[keyof ExtractCodecTypes<Record<K, ScalarNames[K]>>];
553
- };
554
- }
555
-
556
- add<ScalarName extends string, CodecImpl extends Codec<string>>(
557
- scalarName: ScalarName,
558
- codecImpl: CodecImpl,
559
- ): CodecDefBuilder<
560
- O.Overwrite<ScalarNames, Record<ScalarName, CodecImpl>> & Record<ScalarName, CodecImpl>
561
- > {
562
- return new CodecDefBuilderImpl({
563
- ...this._codecs,
564
- [scalarName]: codecImpl,
565
- } as O.Overwrite<ScalarNames, Record<ScalarName, CodecImpl>> & Record<ScalarName, CodecImpl>);
566
- }
567
-
568
- /**
569
- * Derive codecDefinitions structure.
570
- */
571
- get codecDefinitions(): {
572
- readonly [K in keyof ScalarNames]: {
573
- readonly typeId: ScalarNames[K] extends Codec<infer Id> ? Id : never;
574
- readonly scalar: K;
575
- readonly codec: ScalarNames[K];
576
- readonly input: CodecInput<ScalarNames[K]>;
577
- readonly output: CodecInput<ScalarNames[K]>;
578
- readonly jsType: CodecInput<ScalarNames[K]>;
579
- };
580
- } {
581
- const result: Record<
582
- string,
583
- {
584
- typeId: string;
585
- scalar: string;
586
- codec: Codec;
587
- input: unknown;
588
- output: unknown;
589
- jsType: unknown;
590
- }
591
- > = {};
592
-
593
- for (const [scalarName, codecImpl] of Object.entries(this._codecs)) {
594
- const codec = codecImpl as Codec<string>;
595
- result[scalarName] = {
596
- typeId: codec.id,
597
- scalar: scalarName,
598
- codec: codec,
599
- input: undefined as unknown as CodecInput<typeof codec>,
600
- output: undefined as unknown as CodecInput<typeof codec>,
601
- jsType: undefined as unknown as CodecInput<typeof codec>,
602
- };
603
- }
604
-
605
- return result as {
606
- readonly [K in keyof ScalarNames]: {
607
- readonly typeId: ScalarNames[K] extends Codec<infer Id extends string> ? Id : never;
608
- readonly scalar: K;
609
- readonly codec: ScalarNames[K];
610
- readonly input: CodecInput<ScalarNames[K]>;
611
- readonly output: CodecInput<ScalarNames[K]>;
612
- readonly jsType: CodecInput<ScalarNames[K]>;
613
- };
614
- };
615
- }
616
- }
617
-
618
- /**
619
- * Create a new codec registry.
620
- */
621
- export function createCodecRegistry(): CodecRegistry {
622
- return new CodecRegistryImpl();
623
- }
624
-
625
- /**
626
- * Create a new codec definition builder.
627
- */
628
- export function defineCodecs(): CodecDefBuilder<Record<never, never>> {
629
- return new CodecDefBuilderImpl({});
630
- }