@prisma-next/sql-relational-core 0.4.1 → 0.4.3

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 (62) hide show
  1. package/README.md +67 -1
  2. package/dist/codec-types-DJEaWT36.d.mts +313 -0
  3. package/dist/codec-types-DJEaWT36.d.mts.map +1 -0
  4. package/dist/{errors-ChY_dHam.d.mts → errors-BRt5yHo9.d.mts} +2 -2
  5. package/dist/errors-BRt5yHo9.d.mts.map +1 -0
  6. package/dist/{errors-D3xmG4h-.mjs → errors-D6kqqjHM.mjs} +1 -1
  7. package/dist/{errors-D3xmG4h-.mjs.map → errors-D6kqqjHM.mjs.map} +1 -1
  8. package/dist/exports/ast.d.mts +37 -20
  9. package/dist/exports/ast.d.mts.map +1 -1
  10. package/dist/exports/ast.mjs +63 -1089
  11. package/dist/exports/ast.mjs.map +1 -1
  12. package/dist/exports/errors.d.mts +4 -4
  13. package/dist/exports/errors.mjs +1 -1
  14. package/dist/exports/expression.d.mts +79 -0
  15. package/dist/exports/expression.d.mts.map +1 -0
  16. package/dist/exports/expression.mjs +41 -0
  17. package/dist/exports/expression.mjs.map +1 -0
  18. package/dist/exports/plan.d.mts +3 -2
  19. package/dist/exports/plan.mjs +1 -17
  20. package/dist/exports/query-lane-context.d.mts +3 -3
  21. package/dist/exports/types.d.mts +5 -4
  22. package/dist/index.d.mts +11 -9
  23. package/dist/index.mjs +6 -4
  24. package/dist/plan-C7SiEWkN.d.mts +25 -0
  25. package/dist/plan-C7SiEWkN.d.mts.map +1 -0
  26. package/dist/{query-lane-context-UlR8vOkd.d.mts → query-lane-context-BF-wuc0r.d.mts} +53 -3
  27. package/dist/query-lane-context-BF-wuc0r.d.mts.map +1 -0
  28. package/dist/sql-execution-plan-Dgx7BGin.d.mts +33 -0
  29. package/dist/sql-execution-plan-Dgx7BGin.d.mts.map +1 -0
  30. package/dist/{types-C3Hg-CVz.d.mts → types-B4dL4lc3.d.mts} +17 -22
  31. package/dist/types-B4dL4lc3.d.mts.map +1 -0
  32. package/dist/types-BUlUvdIU.d.mts +24 -0
  33. package/dist/types-BUlUvdIU.d.mts.map +1 -0
  34. package/dist/{types-k9pir8XY.d.mts → types-BWOCTYd8.d.mts} +12 -19
  35. package/dist/types-BWOCTYd8.d.mts.map +1 -0
  36. package/dist/types-DUL-3vy6.mjs +1064 -0
  37. package/dist/types-DUL-3vy6.mjs.map +1 -0
  38. package/package.json +9 -8
  39. package/src/ast/adapter-types.ts +20 -10
  40. package/src/ast/codec-types.ts +251 -45
  41. package/src/ast/sql-codecs.ts +20 -3
  42. package/src/ast/types.ts +142 -172
  43. package/src/ast/util.ts +23 -0
  44. package/src/exports/expression.ts +1 -0
  45. package/src/exports/plan.ts +1 -0
  46. package/src/exports/types.ts +1 -0
  47. package/src/expression.ts +117 -0
  48. package/src/index.ts +1 -0
  49. package/src/plan.ts +11 -30
  50. package/src/query-lane-context.ts +52 -1
  51. package/src/runtime-scope.ts +20 -0
  52. package/src/sql-execution-plan.ts +28 -0
  53. package/src/types.ts +9 -22
  54. package/dist/codec-types-DcEITed4.d.mts +0 -144
  55. package/dist/codec-types-DcEITed4.d.mts.map +0 -1
  56. package/dist/errors-ChY_dHam.d.mts.map +0 -1
  57. package/dist/exports/plan.mjs.map +0 -1
  58. package/dist/plan-Cs65hb-E.d.mts +0 -28
  59. package/dist/plan-Cs65hb-E.d.mts.map +0 -1
  60. package/dist/query-lane-context-UlR8vOkd.d.mts.map +0 -1
  61. package/dist/types-C3Hg-CVz.d.mts.map +0 -1
  62. package/dist/types-k9pir8XY.d.mts.map +0 -1
@@ -1,14 +1,88 @@
1
1
  import type { JsonValue } from '@prisma-next/contract/types';
2
- import type { Codec as BaseCodec, CodecTrait } from '@prisma-next/framework-components/codec';
2
+ import type {
3
+ Codec as BaseCodec,
4
+ CodecCallContext,
5
+ CodecInstanceContext,
6
+ CodecTrait,
7
+ } from '@prisma-next/framework-components/codec';
3
8
  import { ifDefined } from '@prisma-next/utils/defined';
4
9
  import type { Type } from 'arktype';
5
10
  import type { O } from 'ts-toolbelt';
6
11
 
7
- export type { CodecTrait } from '@prisma-next/framework-components/codec';
12
+ export type { CodecCallContext, CodecTrait } from '@prisma-next/framework-components/codec';
8
13
 
9
14
  /**
10
- * Descriptor for parameterized codecs that require type parameter validation.
11
- * Shared between adapter (compile-time) and runtime layers to avoid duplication.
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`.
21
+ *
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.
26
+ */
27
+ export interface SqlColumnRef {
28
+ readonly table: string;
29
+ readonly name: string;
30
+ }
31
+
32
+ /**
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).
38
+ *
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`.
42
+ */
43
+ export interface SqlCodecCallContext extends CodecCallContext {
44
+ readonly column?: SqlColumnRef;
45
+ }
46
+
47
+ /**
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.
51
+ *
52
+ * - For `typeRef` columns sharing one named `storage.types` instance, the
53
+ * array lists every referencing column — a 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.
62
+ *
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.
68
+ */
69
+ export interface SqlCodecInstanceContext extends CodecInstanceContext {
70
+ readonly usedAt: ReadonlyArray<{ readonly table: string; readonly column: string }>;
71
+ }
72
+
73
+ /**
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.
12
86
  *
13
87
  * @template TParams - The shape of the type parameters (e.g., `{ length: number }`)
14
88
  * @template THelper - The type returned by the optional `init` hook
@@ -27,6 +101,14 @@ export interface CodecParamsDescriptor<TParams = Record<string, unknown>, THelpe
27
101
  * Optional init hook called during runtime context creation.
28
102
  * Receives validated params and returns a helper object to be stored in context.types.
29
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.
30
112
  */
31
113
  readonly init?: (params: TParams) => THelper;
32
114
  }
@@ -46,25 +128,97 @@ export interface CodecMeta {
46
128
  }
47
129
 
48
130
  /**
49
- * SQL codec interface — extends the framework base with SQL-specific fields.
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`).
50
135
  *
51
- * Codecs are pure, synchronous functions with no side effects or IO.
52
- * They provide deterministic conversion between database wire types and JS values,
53
- * and between JS values and contract JSON.
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.
141
+ *
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.
54
152
  */
55
153
  export interface Codec<
56
154
  Id extends string = string,
57
155
  TTraits extends readonly CodecTrait[] = readonly CodecTrait[],
58
156
  TWire = unknown,
59
- TJs = unknown,
157
+ TInput = unknown,
60
158
  TParams = Record<string, unknown>,
61
159
  THelper = unknown,
62
- > extends BaseCodec<Id, TTraits, TWire, TJs> {
160
+ > extends BaseCodec<Id, TTraits, TWire, TInput> {
161
+ encode(value: TInput, ctx: SqlCodecCallContext): Promise<TWire>;
162
+ decode(wire: TWire, ctx: SqlCodecCallContext): Promise<TInput>;
63
163
  readonly meta?: CodecMeta;
64
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
+ */
65
170
  readonly init?: (params: TParams) => THelper;
66
171
  }
67
172
 
173
+ /**
174
+ * Contract-bound codec registry.
175
+ *
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.
196
+ *
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.
201
+ */
202
+ export interface ContractCodecRegistry {
203
+ /**
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.
208
+ */
209
+ forColumn(table: string, column: string): Codec | undefined;
210
+
211
+ /**
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).
218
+ */
219
+ forCodecId(codecId: string): Codec | undefined;
220
+ }
221
+
68
222
  /**
69
223
  * Registry interface for codecs organized by ID and by contract scalar type.
70
224
  *
@@ -180,30 +334,72 @@ class CodecRegistryImpl implements CodecRegistry {
180
334
  }
181
335
 
182
336
  /**
183
- * Codec factory - creates a codec with typeId and encode/decode functions.
184
- * Provides identity defaults for encodeJson/decodeJson when not supplied.
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
+ };
352
+
353
+ /**
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.
361
+ *
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.
185
367
  */
186
368
  export function codec<
187
369
  Id extends string,
188
- const TTraits extends readonly CodecTrait[],
189
- TWire,
190
- TJs,
370
+ const TTraits extends readonly CodecTrait[] = readonly [],
371
+ TWire = unknown,
372
+ TInput = unknown,
191
373
  TParams = Record<string, unknown>,
192
374
  THelper = unknown,
193
- >(config: {
194
- typeId: Id;
195
- targetTypes: readonly string[];
196
- encode: (value: TJs) => TWire;
197
- decode: (wire: TWire) => TJs;
198
- encodeJson?: (value: TJs) => JsonValue;
199
- decodeJson?: (json: JsonValue) => TJs;
200
- meta?: CodecMeta;
201
- paramsSchema?: Type<TParams>;
202
- init?: (params: TParams) => THelper;
203
- traits?: TTraits;
204
- renderOutputType?: (typeParams: Record<string, unknown>) => string | undefined;
205
- }): Codec<Id, TTraits, TWire, TJs, TParams, THelper> {
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> {
206
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
+ };
207
403
  return {
208
404
  id: config.typeId,
209
405
  targetTypes: config.targetTypes,
@@ -215,10 +411,22 @@ export function codec<
215
411
  config.traits ? (Object.freeze([...config.traits]) as TTraits) : undefined,
216
412
  ),
217
413
  ...ifDefined('renderOutputType', config.renderOutputType),
218
- encode: config.encode,
219
- decode: config.decode,
220
- encodeJson: (config.encodeJson ?? identity) as (value: TJs) => JsonValue,
221
- decodeJson: (config.decodeJson ?? identity) as (json: JsonValue) => TJs,
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,
222
430
  };
223
431
  }
224
432
 
@@ -228,9 +436,7 @@ export function codec<
228
436
  export type CodecId<T> =
229
437
  T extends Codec<infer Id> ? Id : T extends { readonly id: infer Id } ? Id : never;
230
438
  export type CodecInput<T> =
231
- T extends Codec<string, readonly CodecTrait[], unknown, infer JsT> ? JsT : never;
232
- export type CodecOutput<T> =
233
- T extends Codec<string, readonly CodecTrait[], unknown, infer JsT> ? JsT : never;
439
+ T extends Codec<string, readonly CodecTrait[], unknown, infer In> ? In : never;
234
440
  export type CodecTraits<T> =
235
441
  T extends Codec<string, infer TTraits> ? TTraits[number] & CodecTrait : never;
236
442
 
@@ -242,7 +448,7 @@ export type ExtractCodecTypes<
242
448
  > = {
243
449
  readonly [K in keyof ScalarNames as ScalarNames[K] extends Codec<infer Id> ? Id : never]: {
244
450
  readonly input: CodecInput<ScalarNames[K]>;
245
- readonly output: CodecOutput<ScalarNames[K]>;
451
+ readonly output: CodecInput<ScalarNames[K]>;
246
452
  readonly traits: CodecTraits<ScalarNames[K]>;
247
453
  };
248
454
  };
@@ -283,8 +489,8 @@ export interface CodecDefBuilder<
283
489
  readonly scalar: K;
284
490
  readonly codec: ScalarNames[K];
285
491
  readonly input: CodecInput<ScalarNames[K]>;
286
- readonly output: CodecOutput<ScalarNames[K]>;
287
- readonly jsType: CodecOutput<ScalarNames[K]>;
492
+ readonly output: CodecInput<ScalarNames[K]>;
493
+ readonly jsType: CodecInput<ScalarNames[K]>;
288
494
  };
289
495
  };
290
496
 
@@ -323,7 +529,7 @@ class CodecDefBuilderImpl<
323
529
  const codecImplTyped = codecImpl as Codec<string>;
324
530
  codecTypes[codecImplTyped.id] = {
325
531
  input: undefined as unknown as CodecInput<typeof codecImplTyped>,
326
- output: undefined as unknown as CodecOutput<typeof codecImplTyped>,
532
+ output: undefined as unknown as CodecInput<typeof codecImplTyped>,
327
533
  traits: undefined as unknown as CodecTraits<typeof codecImplTyped>,
328
534
  };
329
535
  }
@@ -368,8 +574,8 @@ class CodecDefBuilderImpl<
368
574
  readonly scalar: K;
369
575
  readonly codec: ScalarNames[K];
370
576
  readonly input: CodecInput<ScalarNames[K]>;
371
- readonly output: CodecOutput<ScalarNames[K]>;
372
- readonly jsType: CodecOutput<ScalarNames[K]>;
577
+ readonly output: CodecInput<ScalarNames[K]>;
578
+ readonly jsType: CodecInput<ScalarNames[K]>;
373
579
  };
374
580
  } {
375
581
  const result: Record<
@@ -391,8 +597,8 @@ class CodecDefBuilderImpl<
391
597
  scalar: scalarName,
392
598
  codec: codec,
393
599
  input: undefined as unknown as CodecInput<typeof codec>,
394
- output: undefined as unknown as CodecOutput<typeof codec>,
395
- jsType: undefined as unknown as CodecOutput<typeof codec>,
600
+ output: undefined as unknown as CodecInput<typeof codec>,
601
+ jsType: undefined as unknown as CodecInput<typeof codec>,
396
602
  };
397
603
  }
398
604
 
@@ -402,8 +608,8 @@ class CodecDefBuilderImpl<
402
608
  readonly scalar: K;
403
609
  readonly codec: ScalarNames[K];
404
610
  readonly input: CodecInput<ScalarNames[K]>;
405
- readonly output: CodecOutput<ScalarNames[K]>;
406
- readonly jsType: CodecOutput<ScalarNames[K]>;
611
+ readonly output: CodecInput<ScalarNames[K]>;
612
+ readonly jsType: CodecInput<ScalarNames[K]>;
407
613
  };
408
614
  };
409
615
  }
@@ -1,3 +1,4 @@
1
+ import type { JsonValue } from '@prisma-next/contract/types';
1
2
  import { type as arktype } from 'arktype';
2
3
  import { codec, defineCodecs } from './codec-types';
3
4
 
@@ -104,12 +105,28 @@ const sqlTextCodec = codec({
104
105
  decode: (wire: string): string => wire,
105
106
  });
106
107
 
107
- const sqlTimestampCodec = codec({
108
+ const sqlTimestampCodec = codec<
109
+ typeof SQL_TIMESTAMP_CODEC_ID,
110
+ readonly ['equality', 'order'],
111
+ Date,
112
+ Date
113
+ >({
108
114
  typeId: SQL_TIMESTAMP_CODEC_ID,
109
115
  targetTypes: ['timestamp'],
110
116
  traits: ['equality', 'order'],
111
- encode: (value: string | Date): string => (value instanceof Date ? value.toISOString() : value),
112
- decode: (wire: string | Date): string => (wire instanceof Date ? wire.toISOString() : wire),
117
+ encode: (value: Date): Date => value,
118
+ decode: (wire: Date): Date => wire,
119
+ encodeJson: (value: Date): JsonValue => value.toISOString(),
120
+ decodeJson: (json: JsonValue): Date => {
121
+ if (typeof json !== 'string') {
122
+ throw new Error(`Expected ISO date string for sql/timestamp@1, got ${typeof json}`);
123
+ }
124
+ const date = new Date(json);
125
+ if (Number.isNaN(date.getTime())) {
126
+ throw new Error(`Invalid ISO date string for sql/timestamp@1: ${json}`);
127
+ }
128
+ return date;
129
+ },
113
130
  paramsSchema: precisionParamsSchema,
114
131
  renderOutputType: (typeParams) => {
115
132
  const precision = typeParams['precision'];