@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
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Shared encode/decode/render constants and codec id literals for the six SQL base codecs (`sql/char@1`, `sql/varchar@1`, `sql/int@1`, `sql/float@1`, `sql/text@1`, `sql/timestamp@1`).
3
+ *
4
+ * The codec implementations live in `sql-codecs.ts` (TML-2357). This module retains only the conversion helpers + emit-path renderers the codec methods compose with — keeping a single source of truth for non-trivial conversions while the codec methods provide the framework-required `Promise<…>` boundary.
5
+ */
6
+
7
+ import type { JsonValue } from '@prisma-next/contract/types';
8
+
9
+ export const SQL_CHAR_CODEC_ID = 'sql/char@1' as const;
10
+ export const SQL_VARCHAR_CODEC_ID = 'sql/varchar@1' as const;
11
+ export const SQL_INT_CODEC_ID = 'sql/int@1' as const;
12
+ export const SQL_FLOAT_CODEC_ID = 'sql/float@1' as const;
13
+ export const SQL_TEXT_CODEC_ID = 'sql/text@1' as const;
14
+ export const SQL_TIMESTAMP_CODEC_ID = 'sql/timestamp@1' as const;
15
+
16
+ export const sqlCharEncode = (value: string): string => value;
17
+ export const sqlCharDecode = (wire: string): string => wire.trimEnd();
18
+ export const sqlCharRenderOutputType = (typeParams: { readonly length?: number }) => {
19
+ const length = typeParams.length;
20
+ if (length === undefined) return undefined;
21
+ if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
22
+ throw new Error(
23
+ `renderOutputType: expected integer "length" in typeParams for Char, got ${String(length)}`,
24
+ );
25
+ }
26
+ return `Char<${length}>`;
27
+ };
28
+
29
+ export const sqlVarcharEncode = (value: string): string => value;
30
+ export const sqlVarcharDecode = (wire: string): string => wire;
31
+ export const sqlVarcharRenderOutputType = (typeParams: { readonly length?: number }) => {
32
+ const length = typeParams.length;
33
+ if (length === undefined) return undefined;
34
+ if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
35
+ throw new Error(
36
+ `renderOutputType: expected integer "length" in typeParams for Varchar, got ${String(length)}`,
37
+ );
38
+ }
39
+ return `Varchar<${length}>`;
40
+ };
41
+
42
+ export const sqlIntEncode = (value: number): number => value;
43
+ export const sqlIntDecode = (wire: number): number => wire;
44
+
45
+ export const sqlFloatEncode = (value: number): number => value;
46
+ export const sqlFloatDecode = (wire: number): number => wire;
47
+
48
+ export const sqlTextEncode = (value: string): string => value;
49
+ export const sqlTextDecode = (wire: string): string => wire;
50
+
51
+ export const sqlTimestampEncode = (value: Date): Date => value;
52
+ export const sqlTimestampDecode = (wire: Date): Date => wire;
53
+ export const sqlTimestampEncodeJson = (value: Date): JsonValue => value.toISOString();
54
+ export const sqlTimestampDecodeJson = (json: JsonValue): Date => {
55
+ if (typeof json !== 'string') {
56
+ throw new Error(`Expected ISO date string for sql/timestamp@1, got ${typeof json}`);
57
+ }
58
+ const date = new Date(json);
59
+ if (Number.isNaN(date.getTime())) {
60
+ throw new Error(`Invalid ISO date string for sql/timestamp@1: ${json}`);
61
+ }
62
+ return date;
63
+ };
64
+ export const sqlTimestampRenderOutputType = (typeParams: { readonly precision?: number }) => {
65
+ const precision = typeParams.precision;
66
+ if (precision === undefined) {
67
+ return 'Timestamp';
68
+ }
69
+ if (
70
+ typeof precision !== 'number' ||
71
+ !Number.isFinite(precision) ||
72
+ !Number.isInteger(precision)
73
+ ) {
74
+ throw new Error(
75
+ `renderOutputType: expected integer "precision" in typeParams for Timestamp, got ${String(precision)}`,
76
+ );
77
+ }
78
+ return `Timestamp<${precision}>`;
79
+ };
@@ -1,159 +1,302 @@
1
+ /**
2
+ * The six SQL base codecs (TML-2357).
3
+ *
4
+ * Each codec ships as three artifacts:
5
+ *
6
+ * 1. A `SqlXCodec` class extending {@link CodecImpl} that wraps the module-level encode/decode constants exported from `sql-codec-helpers.ts` (the single source of truth for runtime behaviour). 2. A `SqlXDescriptor` class extending {@link CodecDescriptorImpl} declaring the codec id, traits, target types, params schema, and (where applicable) the emit-path `renderOutputType`. 3. A per-codec column helper (`sqlXColumn`)
7
+ * that calls `descriptor.factory(...)` directly and packages the result into a {@link ColumnSpec} via the framework {@link column} packager. The helper is tied to its descriptor with `satisfies ColumnHelperFor`.
8
+ *
9
+ * After TML-2357 this file is the canonical source of SQL base codec metadata and runtime behaviour — the legacy `mkCodec` / `defineCodec` carriers retired with the deletion sweep.
10
+ */
11
+
1
12
  import type { JsonValue } from '@prisma-next/contract/types';
13
+ import {
14
+ type CodecCallContext,
15
+ CodecDescriptorImpl,
16
+ CodecImpl,
17
+ type CodecInstanceContext,
18
+ type ColumnHelperFor,
19
+ type ColumnHelperForStrict,
20
+ column,
21
+ voidParamsSchema,
22
+ } from '@prisma-next/framework-components/codec';
23
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
24
  import { type as arktype } from 'arktype';
3
- import { codec, defineCodecs } from './codec-types';
25
+ import {
26
+ SQL_CHAR_CODEC_ID,
27
+ SQL_FLOAT_CODEC_ID,
28
+ SQL_INT_CODEC_ID,
29
+ SQL_TEXT_CODEC_ID,
30
+ SQL_TIMESTAMP_CODEC_ID,
31
+ SQL_VARCHAR_CODEC_ID,
32
+ sqlCharDecode,
33
+ sqlCharEncode,
34
+ sqlCharRenderOutputType,
35
+ sqlFloatDecode,
36
+ sqlFloatEncode,
37
+ sqlIntDecode,
38
+ sqlIntEncode,
39
+ sqlTextDecode,
40
+ sqlTextEncode,
41
+ sqlTimestampDecode,
42
+ sqlTimestampDecodeJson,
43
+ sqlTimestampEncode,
44
+ sqlTimestampEncodeJson,
45
+ sqlTimestampRenderOutputType,
46
+ sqlVarcharDecode,
47
+ sqlVarcharEncode,
48
+ sqlVarcharRenderOutputType,
49
+ } from './sql-codec-helpers';
4
50
 
5
- export const SQL_CHAR_CODEC_ID = 'sql/char@1' as const;
6
- export const SQL_VARCHAR_CODEC_ID = 'sql/varchar@1' as const;
7
- export const SQL_INT_CODEC_ID = 'sql/int@1' as const;
8
- export const SQL_FLOAT_CODEC_ID = 'sql/float@1' as const;
9
- export const SQL_TEXT_CODEC_ID = 'sql/text@1' as const;
10
- export const SQL_TIMESTAMP_CODEC_ID = 'sql/timestamp@1' as const;
51
+ type LengthParams = { readonly length?: number };
52
+ type PrecisionParams = { readonly precision?: number };
11
53
 
12
54
  const lengthParamsSchema = arktype({
13
- length: 'number.integer > 0',
14
- });
55
+ 'length?': 'number.integer > 0',
56
+ }) satisfies StandardSchemaV1<LengthParams>;
15
57
 
16
58
  const precisionParamsSchema = arktype({
17
59
  'precision?': 'number.integer >= 0 & number.integer <= 6',
18
- });
19
-
20
- type LengthTypeHelper = {
21
- readonly kind: 'fixed' | 'variable';
22
- readonly maxLength: number;
23
- };
24
-
25
- function createLengthTypeHelper(
26
- kind: LengthTypeHelper['kind'],
27
- ): (params: Record<string, unknown>) => LengthTypeHelper {
28
- return (params) => ({
29
- kind,
30
- maxLength: params['length'] as number,
31
- });
60
+ }) satisfies StandardSchemaV1<PrecisionParams>;
61
+
62
+ export class SqlTextCodec extends CodecImpl<
63
+ typeof SQL_TEXT_CODEC_ID,
64
+ readonly ['equality', 'order', 'textual'],
65
+ string,
66
+ string
67
+ > {
68
+ async encode(value: string, _ctx: CodecCallContext): Promise<string> {
69
+ return sqlTextEncode(value);
70
+ }
71
+ async decode(wire: string, _ctx: CodecCallContext): Promise<string> {
72
+ return sqlTextDecode(wire);
73
+ }
74
+ encodeJson(value: string): JsonValue {
75
+ return value;
76
+ }
77
+ decodeJson(json: JsonValue): string {
78
+ return json as string;
79
+ }
80
+ }
81
+
82
+ export class SqlTextDescriptor extends CodecDescriptorImpl<void> {
83
+ override readonly codecId = SQL_TEXT_CODEC_ID;
84
+ override readonly traits = ['equality', 'order', 'textual'] as const;
85
+ override readonly targetTypes = ['text'] as const;
86
+ override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
87
+ override factory(): (ctx: CodecInstanceContext) => SqlTextCodec {
88
+ return () => new SqlTextCodec(this);
89
+ }
90
+ }
91
+
92
+ export const sqlTextDescriptor = new SqlTextDescriptor();
93
+
94
+ export const sqlTextColumn = () =>
95
+ column(sqlTextDescriptor.factory(), sqlTextDescriptor.codecId, undefined, 'text');
96
+
97
+ sqlTextColumn satisfies ColumnHelperFor<SqlTextDescriptor>;
98
+ sqlTextColumn satisfies ColumnHelperForStrict<SqlTextDescriptor>;
99
+
100
+ export class SqlIntCodec extends CodecImpl<
101
+ typeof SQL_INT_CODEC_ID,
102
+ readonly ['equality', 'order', 'numeric'],
103
+ number,
104
+ number
105
+ > {
106
+ async encode(value: number, _ctx: CodecCallContext): Promise<number> {
107
+ return sqlIntEncode(value);
108
+ }
109
+ async decode(wire: number, _ctx: CodecCallContext): Promise<number> {
110
+ return sqlIntDecode(wire);
111
+ }
112
+ encodeJson(value: number): JsonValue {
113
+ return value;
114
+ }
115
+ decodeJson(json: JsonValue): number {
116
+ return json as number;
117
+ }
118
+ }
119
+
120
+ export class SqlIntDescriptor extends CodecDescriptorImpl<void> {
121
+ override readonly codecId = SQL_INT_CODEC_ID;
122
+ override readonly traits = ['equality', 'order', 'numeric'] as const;
123
+ override readonly targetTypes = ['int'] as const;
124
+ override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
125
+ override factory(): (ctx: CodecInstanceContext) => SqlIntCodec {
126
+ return () => new SqlIntCodec(this);
127
+ }
32
128
  }
33
129
 
34
- const sqlCharCodec = codec<
130
+ export const sqlIntDescriptor = new SqlIntDescriptor();
131
+
132
+ export const sqlIntColumn = () =>
133
+ column(sqlIntDescriptor.factory(), sqlIntDescriptor.codecId, undefined, 'int');
134
+
135
+ sqlIntColumn satisfies ColumnHelperFor<SqlIntDescriptor>;
136
+ sqlIntColumn satisfies ColumnHelperForStrict<SqlIntDescriptor>;
137
+
138
+ export class SqlFloatCodec extends CodecImpl<
139
+ typeof SQL_FLOAT_CODEC_ID,
140
+ readonly ['equality', 'order', 'numeric'],
141
+ number,
142
+ number
143
+ > {
144
+ async encode(value: number, _ctx: CodecCallContext): Promise<number> {
145
+ return sqlFloatEncode(value);
146
+ }
147
+ async decode(wire: number, _ctx: CodecCallContext): Promise<number> {
148
+ return sqlFloatDecode(wire);
149
+ }
150
+ encodeJson(value: number): JsonValue {
151
+ return value;
152
+ }
153
+ decodeJson(json: JsonValue): number {
154
+ return json as number;
155
+ }
156
+ }
157
+
158
+ export class SqlFloatDescriptor extends CodecDescriptorImpl<void> {
159
+ override readonly codecId = SQL_FLOAT_CODEC_ID;
160
+ override readonly traits = ['equality', 'order', 'numeric'] as const;
161
+ override readonly targetTypes = ['float'] as const;
162
+ override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
163
+ override factory(): (ctx: CodecInstanceContext) => SqlFloatCodec {
164
+ return () => new SqlFloatCodec(this);
165
+ }
166
+ }
167
+
168
+ export const sqlFloatDescriptor = new SqlFloatDescriptor();
169
+
170
+ export const sqlFloatColumn = () =>
171
+ column(sqlFloatDescriptor.factory(), sqlFloatDescriptor.codecId, undefined, 'float');
172
+
173
+ sqlFloatColumn satisfies ColumnHelperFor<SqlFloatDescriptor>;
174
+ sqlFloatColumn satisfies ColumnHelperForStrict<SqlFloatDescriptor>;
175
+
176
+ export class SqlCharCodec extends CodecImpl<
35
177
  typeof SQL_CHAR_CODEC_ID,
36
178
  readonly ['equality', 'order', 'textual'],
37
179
  string,
38
180
  string
39
- >({
40
- typeId: SQL_CHAR_CODEC_ID,
41
- targetTypes: ['char'],
42
- traits: ['equality', 'order', 'textual'],
43
- encode: (value: string): string => value,
44
- decode: (wire: string): string => wire.trimEnd(),
45
- paramsSchema: lengthParamsSchema,
46
- init: createLengthTypeHelper('fixed'),
47
- renderOutputType: (typeParams) => {
48
- const length = typeParams['length'];
49
- if (length === undefined) return undefined;
50
- if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
51
- throw new Error(
52
- `renderOutputType: expected integer "length" in typeParams for Char, got ${String(length)}`,
53
- );
54
- }
55
- return `Char<${length}>`;
56
- },
57
- });
58
-
59
- const sqlVarcharCodec = codec<
181
+ > {
182
+ async encode(value: string, _ctx: CodecCallContext): Promise<string> {
183
+ return sqlCharEncode(value);
184
+ }
185
+ async decode(wire: string, _ctx: CodecCallContext): Promise<string> {
186
+ return sqlCharDecode(wire);
187
+ }
188
+ encodeJson(value: string): JsonValue {
189
+ return value;
190
+ }
191
+ decodeJson(json: JsonValue): string {
192
+ return json as string;
193
+ }
194
+ }
195
+
196
+ export class SqlCharDescriptor extends CodecDescriptorImpl<LengthParams> {
197
+ override readonly codecId = SQL_CHAR_CODEC_ID;
198
+ override readonly traits = ['equality', 'order', 'textual'] as const;
199
+ override readonly targetTypes = ['char'] as const;
200
+ override readonly paramsSchema: StandardSchemaV1<LengthParams> = lengthParamsSchema;
201
+ override renderOutputType(params: LengthParams): string | undefined {
202
+ return sqlCharRenderOutputType(params);
203
+ }
204
+ override factory(_params: LengthParams): (ctx: CodecInstanceContext) => SqlCharCodec {
205
+ return () => new SqlCharCodec(this);
206
+ }
207
+ }
208
+
209
+ export const sqlCharDescriptor = new SqlCharDescriptor();
210
+
211
+ export const sqlCharColumn = (params: LengthParams = {}) =>
212
+ column(sqlCharDescriptor.factory(params), sqlCharDescriptor.codecId, params, 'char');
213
+
214
+ sqlCharColumn satisfies ColumnHelperFor<SqlCharDescriptor>;
215
+ sqlCharColumn satisfies ColumnHelperForStrict<SqlCharDescriptor>;
216
+
217
+ export class SqlVarcharCodec extends CodecImpl<
60
218
  typeof SQL_VARCHAR_CODEC_ID,
61
219
  readonly ['equality', 'order', 'textual'],
62
220
  string,
63
221
  string
64
- >({
65
- typeId: SQL_VARCHAR_CODEC_ID,
66
- targetTypes: ['varchar'],
67
- traits: ['equality', 'order', 'textual'],
68
- encode: (value: string): string => value,
69
- decode: (wire: string): string => wire,
70
- paramsSchema: lengthParamsSchema,
71
- init: createLengthTypeHelper('variable'),
72
- renderOutputType: (typeParams) => {
73
- const length = typeParams['length'];
74
- if (length === undefined) return undefined;
75
- if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
76
- throw new Error(
77
- `renderOutputType: expected integer "length" in typeParams for Varchar, got ${String(length)}`,
78
- );
79
- }
80
- return `Varchar<${length}>`;
81
- },
82
- });
83
-
84
- const sqlIntCodec = codec({
85
- typeId: SQL_INT_CODEC_ID,
86
- targetTypes: ['int'],
87
- traits: ['equality', 'order', 'numeric'],
88
- encode: (value: number): number => value,
89
- decode: (wire: number): number => wire,
90
- });
91
-
92
- const sqlFloatCodec = codec({
93
- typeId: SQL_FLOAT_CODEC_ID,
94
- targetTypes: ['float'],
95
- traits: ['equality', 'order', 'numeric'],
96
- encode: (value: number): number => value,
97
- decode: (wire: number): number => wire,
98
- });
99
-
100
- const sqlTextCodec = codec({
101
- typeId: SQL_TEXT_CODEC_ID,
102
- targetTypes: ['text'],
103
- traits: ['equality', 'order', 'textual'],
104
- encode: (value: string): string => value,
105
- decode: (wire: string): string => wire,
106
- });
107
-
108
- const sqlTimestampCodec = codec<
222
+ > {
223
+ async encode(value: string, _ctx: CodecCallContext): Promise<string> {
224
+ return sqlVarcharEncode(value);
225
+ }
226
+ async decode(wire: string, _ctx: CodecCallContext): Promise<string> {
227
+ return sqlVarcharDecode(wire);
228
+ }
229
+ encodeJson(value: string): JsonValue {
230
+ return value;
231
+ }
232
+ decodeJson(json: JsonValue): string {
233
+ return json as string;
234
+ }
235
+ }
236
+
237
+ export class SqlVarcharDescriptor extends CodecDescriptorImpl<LengthParams> {
238
+ override readonly codecId = SQL_VARCHAR_CODEC_ID;
239
+ override readonly traits = ['equality', 'order', 'textual'] as const;
240
+ override readonly targetTypes = ['varchar'] as const;
241
+ override readonly paramsSchema: StandardSchemaV1<LengthParams> = lengthParamsSchema;
242
+ override renderOutputType(params: LengthParams): string | undefined {
243
+ return sqlVarcharRenderOutputType(params);
244
+ }
245
+ override factory(_params: LengthParams): (ctx: CodecInstanceContext) => SqlVarcharCodec {
246
+ return () => new SqlVarcharCodec(this);
247
+ }
248
+ }
249
+
250
+ export const sqlVarcharDescriptor = new SqlVarcharDescriptor();
251
+
252
+ export const sqlVarcharColumn = (params: LengthParams = {}) =>
253
+ column(sqlVarcharDescriptor.factory(params), sqlVarcharDescriptor.codecId, params, 'varchar');
254
+
255
+ sqlVarcharColumn satisfies ColumnHelperFor<SqlVarcharDescriptor>;
256
+ sqlVarcharColumn satisfies ColumnHelperForStrict<SqlVarcharDescriptor>;
257
+
258
+ export class SqlTimestampCodec extends CodecImpl<
109
259
  typeof SQL_TIMESTAMP_CODEC_ID,
110
260
  readonly ['equality', 'order'],
111
261
  Date,
112
262
  Date
113
- >({
114
- typeId: SQL_TIMESTAMP_CODEC_ID,
115
- targetTypes: ['timestamp'],
116
- traits: ['equality', 'order'],
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
- },
130
- paramsSchema: precisionParamsSchema,
131
- renderOutputType: (typeParams) => {
132
- const precision = typeParams['precision'];
133
- if (precision === undefined) {
134
- return 'Timestamp';
135
- }
136
- if (
137
- typeof precision !== 'number' ||
138
- !Number.isFinite(precision) ||
139
- !Number.isInteger(precision)
140
- ) {
141
- throw new Error(
142
- `renderOutputType: expected integer "precision" in typeParams for Timestamp, got ${String(precision)}`,
143
- );
144
- }
145
- return `Timestamp<${precision}>`;
146
- },
147
- });
148
-
149
- const codecs = defineCodecs()
150
- .add('char', sqlCharCodec)
151
- .add('varchar', sqlVarcharCodec)
152
- .add('int', sqlIntCodec)
153
- .add('float', sqlFloatCodec)
154
- .add('text', sqlTextCodec)
155
- .add('timestamp', sqlTimestampCodec);
156
-
157
- export const sqlCodecDefinitions = codecs.codecDefinitions;
158
- export const sqlDataTypes = codecs.dataTypes;
159
- export type SqlCodecTypes = typeof codecs.CodecTypes;
263
+ > {
264
+ async encode(value: Date, _ctx: CodecCallContext): Promise<Date> {
265
+ return sqlTimestampEncode(value);
266
+ }
267
+ async decode(wire: Date, _ctx: CodecCallContext): Promise<Date> {
268
+ return sqlTimestampDecode(wire);
269
+ }
270
+ encodeJson(value: Date): JsonValue {
271
+ return sqlTimestampEncodeJson(value);
272
+ }
273
+ decodeJson(json: JsonValue): Date {
274
+ return sqlTimestampDecodeJson(json);
275
+ }
276
+ }
277
+
278
+ export class SqlTimestampDescriptor extends CodecDescriptorImpl<PrecisionParams> {
279
+ override readonly codecId = SQL_TIMESTAMP_CODEC_ID;
280
+ override readonly traits = ['equality', 'order'] as const;
281
+ override readonly targetTypes = ['timestamp'] as const;
282
+ override readonly paramsSchema: StandardSchemaV1<PrecisionParams> = precisionParamsSchema;
283
+ override renderOutputType(params: PrecisionParams): string | undefined {
284
+ return sqlTimestampRenderOutputType(params);
285
+ }
286
+ override factory(_params: PrecisionParams): (ctx: CodecInstanceContext) => SqlTimestampCodec {
287
+ return () => new SqlTimestampCodec(this);
288
+ }
289
+ }
290
+
291
+ export const sqlTimestampDescriptor = new SqlTimestampDescriptor();
292
+
293
+ export const sqlTimestampColumn = (params: PrecisionParams = {}) =>
294
+ column(
295
+ sqlTimestampDescriptor.factory(params),
296
+ sqlTimestampDescriptor.codecId,
297
+ params,
298
+ 'timestamp',
299
+ );
300
+
301
+ sqlTimestampColumn satisfies ColumnHelperFor<SqlTimestampDescriptor>;
302
+ sqlTimestampColumn satisfies ColumnHelperForStrict<SqlTimestampDescriptor>;
package/src/ast/types.ts CHANGED
@@ -156,7 +156,7 @@ function rewriteProjectionItem(item: ProjectionItem, rewriter: AstRewriter): Pro
156
156
  ? rewriter.literal(item.expr)
157
157
  : item.expr
158
158
  : item.expr.rewrite(rewriter);
159
- return new ProjectionItem(item.alias, rewrittenExpr, item.codecId);
159
+ return new ProjectionItem(item.alias, rewrittenExpr, item.codecId, item.refs);
160
160
  }
161
161
 
162
162
  function rewriteInsertValue(value: InsertValue, rewriter: AstRewriter): InsertValue {
@@ -320,9 +320,7 @@ export class DerivedTableSource extends FromSource {
320
320
  return new DerivedTableSource(alias, query);
321
321
  }
322
322
 
323
- // Intentionally does not call rewriter.tableSource — derived tables are rewritten
324
- // via their inner query, not intercepted at the FromSource level. A future
325
- // fromSource?(source: AnyFromSource) callback would be needed for that.
323
+ // Intentionally does not call rewriter.tableSource — derived tables are rewritten via their inner query, not intercepted at the FromSource level. A future fromSource?(source: AnyFromSource) callback would be needed for that.
326
324
  override rewrite(rewriter: AstRewriter): AnyFromSource {
327
325
  return new DerivedTableSource(this.alias, this.query.rewrite(rewriter));
328
326
  }
@@ -392,23 +390,37 @@ export class IdentifierRef extends Expression {
392
390
  }
393
391
  }
394
392
 
393
+ /**
394
+ * Column ref carried by a {@link ParamRef} that was constructed at a column-bound site (e.g. an INSERT/UPDATE column assignment, a `WHERE column = $param` comparison). The encode-side dispatch path uses `refs` to call `contractCodecs.forColumn(refs.table, refs.column)`, which selects the per-instance parameterized codec for the column — required when a parameterized codec id is shared by multiple columns with distinct
395
+ * typeParams (e.g. `vector(1024)` vs. `vector(1536)`).
396
+ *
397
+ * `refs` may legitimately be `undefined` for `ParamRef`s constructed without a column context — the validator pass (`validateParamRefRefs`) treats refs-less ParamRefs as a hard error only when their codec id is parameterized.
398
+ */
399
+ export interface ParamRefBindingRefs {
400
+ readonly table: string;
401
+ readonly column: string;
402
+ }
403
+
395
404
  export class ParamRef extends Expression {
396
405
  readonly kind = 'param-ref' as const;
397
406
  readonly value: unknown;
398
407
  readonly name: string | undefined;
399
408
  readonly codecId: string | undefined;
409
+ readonly refs: ParamRefBindingRefs | undefined;
400
410
 
401
411
  constructor(
402
412
  value: unknown,
403
413
  options?: {
404
414
  name?: string;
405
415
  codecId?: string;
416
+ refs?: ParamRefBindingRefs;
406
417
  },
407
418
  ) {
408
419
  super();
409
420
  this.value = value;
410
421
  this.name = options?.name;
411
422
  this.codecId = options?.codecId;
423
+ this.refs = options?.refs ? Object.freeze({ ...options.refs }) : undefined;
412
424
  this.freeze();
413
425
  }
414
426
 
@@ -417,6 +429,7 @@ export class ParamRef extends Expression {
417
429
  options?: {
418
430
  name?: string;
419
431
  codecId?: string;
432
+ refs?: ParamRefBindingRefs;
420
433
  },
421
434
  ): ParamRef {
422
435
  return new ParamRef(value, options);
@@ -1069,21 +1082,32 @@ export class ProjectionItem extends AstNode {
1069
1082
  readonly alias: string;
1070
1083
  readonly expr: ProjectionExpr;
1071
1084
  readonly codecId: string | undefined;
1085
+ /**
1086
+ * Column-bound metadata for the projection. Populated by builder paths that promote a top-level field shortcut (`select('email', ...)`) into a bare `IdentifierRef` AST while still knowing the originating `(table, column)`. Decode-side dispatch consults `refs` to call `forColumn(table, column)` so parameterized codec ids resolve to the per-instance codec — required when multiple columns share a codec id with distinct
1087
+ * typeParams (e.g. `varchar(36)` vs. `varchar(255)`). Stays `undefined` for `column-ref` projections (the AST already carries the binding) and for non-column-bound projections (computed expressions, subqueries, raw aliases).
1088
+ */
1089
+ readonly refs: ParamRefBindingRefs | undefined;
1072
1090
 
1073
- constructor(alias: string, expr: ProjectionExpr, codecId?: string) {
1091
+ constructor(alias: string, expr: ProjectionExpr, codecId?: string, refs?: ParamRefBindingRefs) {
1074
1092
  super();
1075
1093
  this.alias = alias;
1076
1094
  this.expr = expr;
1077
1095
  this.codecId = codecId;
1096
+ this.refs = refs ? Object.freeze({ ...refs }) : undefined;
1078
1097
  this.freeze();
1079
1098
  }
1080
1099
 
1081
- static of(alias: string, expr: ProjectionExpr, codecId?: string): ProjectionItem {
1082
- return new ProjectionItem(alias, expr, codecId);
1100
+ static of(
1101
+ alias: string,
1102
+ expr: ProjectionExpr,
1103
+ codecId?: string,
1104
+ refs?: ParamRefBindingRefs,
1105
+ ): ProjectionItem {
1106
+ return new ProjectionItem(alias, expr, codecId, refs);
1083
1107
  }
1084
1108
 
1085
1109
  withCodecId(codecId: string | undefined): ProjectionItem {
1086
- return new ProjectionItem(this.alias, this.expr, codecId);
1110
+ return new ProjectionItem(this.alias, this.expr, codecId, this.refs);
1087
1111
  }
1088
1112
  }
1089
1113
 
@@ -1241,6 +1265,7 @@ export class SelectAst extends QueryAst {
1241
1265
  : projection.expr
1242
1266
  : projection.expr.rewrite(rewriter),
1243
1267
  projection.codecId,
1268
+ projection.refs,
1244
1269
  ),
1245
1270
  ),
1246
1271
  where: this.where?.rewrite(rewriter),
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Builder-pipeline validator pass: every {@link ParamRef} whose `codecId` resolves to a *parameterized* descriptor must carry `refs: { table, column }` so encode-side dispatch can call `contractCodecs.forColumn(table, column)`. Refs-less parameterized `ParamRef`s are a hard error — the codec-id-keyed `forCodecId` fallback cannot disambiguate per-instance codecs (e.g. `vector(1024)` vs. `vector(1536)`), so the dispatch
3
+ * path must reject them at validation time rather than silently bind to the wrong instance.
4
+ *
5
+ * Non-parameterized codec ids (the `voidParamsSchema` case) are always dispatch-safe via codec id alone, so refs-less ParamRefs for those ids are accepted unchanged.
6
+ *
7
+ * The pass runs post-build / pre-execute — the natural location is the SQL runtime's `lower()` step, between any `beforeCompile` rewrites and `encodeParams`. See AC-5 in the codec-registry-unification spec.
8
+ */
9
+
10
+ import { runtimeError } from '@prisma-next/framework-components/runtime';
11
+ import type { CodecDescriptorRegistry } from '../query-lane-context';
12
+ import type { AnyQueryAst, ParamRef } from './types';
13
+ import { collectOrderedParamRefs } from './util';
14
+
15
+ /**
16
+ * Validate that every parameterized-codec `ParamRef` in `plan` carries `refs`. Throws `RUNTIME.PARAM_REF_REFS_REQUIRED` (a runtime envelope) naming the codec id and the binding label when the invariant is violated. Returns the plan unchanged on success — callers that prefer a side-effecting assertion can ignore the return value.
17
+ *
18
+ * The `registry` is consulted via `descriptorFor(codecId).isParameterized` — `true` whenever the descriptor's `paramsSchema` is not the singleton `voidParamsSchema`.
19
+ */
20
+ export function validateParamRefRefs(plan: AnyQueryAst, registry: CodecDescriptorRegistry): void {
21
+ for (const ref of collectOrderedParamRefs(plan)) {
22
+ diagnoseRef(ref, registry);
23
+ }
24
+ }
25
+
26
+ function diagnoseRef(ref: ParamRef, registry: CodecDescriptorRegistry): void {
27
+ if (!ref.codecId) return;
28
+ const descriptor = registry.descriptorFor(ref.codecId);
29
+ if (descriptor === undefined) return;
30
+ if (!descriptor.isParameterized) return;
31
+ if (ref.refs) return;
32
+
33
+ const label = ref.name ?? '<anonymous>';
34
+ throw runtimeError(
35
+ 'RUNTIME.PARAM_REF_REFS_REQUIRED',
36
+ `ParamRef '${label}' for parameterized codec '${ref.codecId}' is missing column refs; column-aware dispatch requires { table, column } at the binding site.`,
37
+ { codecId: ref.codecId, paramName: ref.name },
38
+ );
39
+ }