@prisma-next/sql-orm-client 0.5.0-dev.6 → 0.5.0-dev.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,53 +1,13 @@
1
- import type { Contract, ParamDescriptor, PlanMeta } from '@prisma-next/contract/types';
1
+ import type { Contract, PlanMeta } from '@prisma-next/contract/types';
2
2
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
3
- import type { AnyQueryAst, ParamRef } from '@prisma-next/sql-relational-core/ast';
3
+ import { type AnyQueryAst, collectOrderedParamRefs } from '@prisma-next/sql-relational-core/ast';
4
4
  import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
5
- import { ifDefined } from '@prisma-next/utils/defined';
6
5
 
7
- function resolveProjectionCodecs(
8
- contract: Contract<SqlStorage>,
9
- ast: AnyQueryAst,
10
- ): Record<string, string> | undefined {
11
- const codecs: Record<string, string> = {};
12
-
13
- if (ast.kind === 'select') {
14
- for (const item of ast.projection) {
15
- if (item.expr.kind === 'column-ref') {
16
- const table = contract.storage.tables[item.expr.table];
17
- const col = table?.columns[item.expr.column];
18
- if (col?.codecId) {
19
- codecs[item.alias] = col.codecId;
20
- }
21
- }
22
- }
23
- } else if (ast.returning) {
24
- const tableName = ast.table.name;
25
- const table = contract.storage.tables[tableName];
26
- if (!table) return undefined;
27
-
28
- for (const colRef of ast.returning) {
29
- const col = table.columns[colRef.column];
30
- if (col?.codecId) {
31
- codecs[colRef.column] = col.codecId;
32
- }
33
- }
34
- }
35
-
36
- return Object.keys(codecs).length > 0 ? codecs : undefined;
37
- }
38
-
39
- export function deriveParamsFromAst(ast: { collectParamRefs(): ParamRef[] }): {
6
+ export function deriveParamsFromAst(ast: AnyQueryAst): {
40
7
  params: unknown[];
41
- paramDescriptors: ParamDescriptor[];
42
8
  } {
43
- const collectedParams = [...new Set(ast.collectParamRefs())];
44
9
  return {
45
- params: collectedParams.map((p) => p.value),
46
- paramDescriptors: collectedParams.map((p) => ({
47
- ...ifDefined('name', p.name),
48
- ...ifDefined('codecId', p.codecId),
49
- source: 'dsl' as const,
50
- })),
10
+ params: collectOrderedParamRefs(ast).map((p) => p.value),
51
11
  };
52
12
  }
53
13
 
@@ -59,17 +19,13 @@ export function resolveTableColumns(contract: Contract<SqlStorage>, tableName: s
59
19
  return Object.keys(table.columns);
60
20
  }
61
21
 
62
- export function buildOrmPlanMeta(
63
- contract: Contract<SqlStorage>,
64
- paramDescriptors: readonly ParamDescriptor[] = [],
65
- ): PlanMeta {
22
+ export function buildOrmPlanMeta(contract: Contract<SqlStorage>): PlanMeta {
66
23
  return {
67
24
  target: contract.target,
68
25
  targetFamily: contract.targetFamily,
69
26
  storageHash: contract.storage.storageHash,
70
27
  ...(contract.profileHash !== undefined ? { profileHash: contract.profileHash } : {}),
71
28
  lane: 'orm-client',
72
- paramDescriptors: [...paramDescriptors],
73
29
  };
74
30
  }
75
31
 
@@ -77,26 +33,10 @@ export function buildOrmQueryPlan<Row>(
77
33
  contract: Contract<SqlStorage>,
78
34
  ast: AnyQueryAst,
79
35
  params: readonly unknown[],
80
- paramDescriptors: readonly ParamDescriptor[] = [],
81
36
  ): SqlQueryPlan<Row> {
82
- const projectionTypes = resolveProjectionCodecs(contract, ast);
83
- const codecAnnotations = projectionTypes
84
- ? { codecs: Object.freeze({ ...projectionTypes }) }
85
- : undefined;
86
- const limitAnnotation =
87
- ast.kind === 'select' && ast.limit !== undefined ? { limit: ast.limit } : undefined;
88
- const annotations =
89
- codecAnnotations || limitAnnotation
90
- ? Object.freeze({ ...codecAnnotations, ...limitAnnotation })
91
- : undefined;
92
-
93
37
  return Object.freeze({
94
38
  ast,
95
39
  params: [...params],
96
- meta: {
97
- ...buildOrmPlanMeta(contract, paramDescriptors),
98
- ...ifDefined('projectionTypes', projectionTypes),
99
- ...ifDefined('annotations', annotations),
100
- },
40
+ meta: buildOrmPlanMeta(contract),
101
41
  });
102
42
  }
@@ -8,6 +8,7 @@ import {
8
8
  InsertAst,
9
9
  InsertOnConflict,
10
10
  ParamRef,
11
+ ProjectionItem,
11
12
  TableSource,
12
13
  UpdateAst,
13
14
  } from '@prisma-next/sql-relational-core/ast';
@@ -19,13 +20,16 @@ function buildReturningColumns(
19
20
  contract: Contract<SqlStorage>,
20
21
  tableName: string,
21
22
  returningColumns: readonly string[] | undefined,
22
- ) {
23
+ ): ReadonlyArray<ProjectionItem> {
23
24
  const columns =
24
25
  returningColumns && returningColumns.length > 0
25
26
  ? [...returningColumns]
26
27
  : resolveTableColumns(contract, tableName);
27
28
 
28
- return columns.map((column) => ColumnRef.of(tableName, column));
29
+ const table = contract.storage.tables[tableName];
30
+ return columns.map((column) =>
31
+ ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId),
32
+ );
29
33
  }
30
34
 
31
35
  function toParamAssignments(
@@ -47,7 +51,11 @@ function toParamAssignments(
47
51
  if (!codecId) {
48
52
  throw new Error(`Unknown column "${column}" in table "${tableName}"`);
49
53
  }
50
- assignments[column] = ParamRef.of(value, { name: column, codecId });
54
+ assignments[column] = ParamRef.of(value, {
55
+ name: column,
56
+ codecId,
57
+ refs: { table: tableName, column },
58
+ });
51
59
  }
52
60
 
53
61
  return { assignments };
@@ -93,7 +101,11 @@ function normalizeInsertRows(
93
101
  if (!codecId) {
94
102
  throw new Error(`Unknown column "${column}" in table "${tableName}"`);
95
103
  }
96
- normalizedRow[column] = ParamRef.of(row[column], { name: column, codecId });
104
+ normalizedRow[column] = ParamRef.of(row[column], {
105
+ name: column,
106
+ codecId,
107
+ refs: { table: tableName, column },
108
+ });
97
109
  continue;
98
110
  }
99
111
  normalizedRow[column] = new DefaultValueExpr();
@@ -114,8 +126,8 @@ export function compileInsertReturning(
114
126
  const ast = InsertAst.into(TableSource.named(tableName))
115
127
  .withRows(normalizedRows)
116
128
  .withReturning(buildReturningColumns(contract, tableName, returningColumns));
117
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
118
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
129
+ const { params } = deriveParamsFromAst(ast);
130
+ return buildOrmQueryPlan(contract, ast, params);
119
131
  }
120
132
 
121
133
  export function compileInsertCount(
@@ -125,8 +137,8 @@ export function compileInsertCount(
125
137
  ): SqlQueryPlan<Record<string, unknown>> {
126
138
  const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
127
139
  const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows);
128
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
129
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
140
+ const { params } = deriveParamsFromAst(ast);
141
+ return buildOrmQueryPlan(contract, ast, params);
130
142
  }
131
143
 
132
144
  function stripUndefinedValues(row: Record<string, unknown>): Record<string, unknown> {
@@ -139,11 +151,7 @@ function stripUndefinedValues(row: Record<string, unknown>): Record<string, unkn
139
151
  return result;
140
152
  }
141
153
 
142
- // Groups rows by their set of present columns so each group can be emitted as a
143
- // single INSERT statement. Groups are created in input order — rows with the same
144
- // signature that are non-adjacent produce separate groups. This is deliberate:
145
- // preserving insertion order ensures autogenerated/autoincrement columns are
146
- // assigned in the same order as the caller's input.
154
+ // Groups rows by their set of present columns so each group can be emitted as a single INSERT statement. Groups are created in input order — rows with the same signature that are non-adjacent produce separate groups. This is deliberate: preserving insertion order ensures autogenerated/autoincrement columns are assigned in the same order as the caller's input.
147
155
  function groupRowsByColumnSignature(
148
156
  rows: readonly Record<string, unknown>[],
149
157
  ): ReadonlyArray<readonly Record<string, unknown>[]> {
@@ -224,8 +232,8 @@ export function compileUpsertReturning(
224
232
  .withOnConflict(onConflict)
225
233
  .withReturning(buildReturningColumns(contract, tableName, returningColumns));
226
234
 
227
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
228
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
235
+ const { params } = deriveParamsFromAst(ast);
236
+ return buildOrmQueryPlan(contract, ast, params);
229
237
  }
230
238
 
231
239
  export function compileUpdateReturning(
@@ -243,8 +251,8 @@ export function compileUpdateReturning(
243
251
  if (where) {
244
252
  ast = ast.withWhere(where);
245
253
  }
246
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
247
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
254
+ const { params } = deriveParamsFromAst(ast);
255
+ return buildOrmQueryPlan(contract, ast, params);
248
256
  }
249
257
 
250
258
  export function compileUpdateCount(
@@ -259,8 +267,8 @@ export function compileUpdateCount(
259
267
  if (where) {
260
268
  ast = ast.withWhere(where);
261
269
  }
262
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
263
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
270
+ const { params } = deriveParamsFromAst(ast);
271
+ return buildOrmQueryPlan(contract, ast, params);
264
272
  }
265
273
 
266
274
  export function compileDeleteReturning(
@@ -276,8 +284,8 @@ export function compileDeleteReturning(
276
284
  if (where) {
277
285
  ast = ast.withWhere(where);
278
286
  }
279
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
280
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
287
+ const { params } = deriveParamsFromAst(ast);
288
+ return buildOrmQueryPlan(contract, ast, params);
281
289
  }
282
290
 
283
291
  export function compileDeleteCount(
@@ -290,6 +298,6 @@ export function compileDeleteCount(
290
298
  if (where) {
291
299
  ast = ast.withWhere(where);
292
300
  }
293
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
294
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
301
+ const { params } = deriveParamsFromAst(ast);
302
+ return buildOrmQueryPlan(contract, ast, params);
295
303
  }
@@ -49,7 +49,10 @@ function buildProjection(
49
49
  ? [...selectedFields]
50
50
  : resolveTableColumns(contract, tableName);
51
51
 
52
- return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableRef, column)));
52
+ const table = contract.storage.tables[tableName];
53
+ return columns.map((column) =>
54
+ ProjectionItem.of(column, ColumnRef.of(tableRef, column), table?.columns[column]?.codecId),
55
+ );
53
56
  }
54
57
 
55
58
  function createBoundaryExpr(tableName: string, entry: CursorOrderEntry): AnyExpression {
@@ -403,10 +406,17 @@ function buildMtiJoins(
403
406
  joins.push(join);
404
407
 
405
408
  const variantColumns = resolveTableColumns(contract, variant.table);
409
+ const variantTable = contract.storage.tables[variant.table];
406
410
  for (const col of variantColumns) {
407
411
  if (col === pkColumn) continue;
408
412
  const alias = `${variant.table}__${col}`;
409
- projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col)));
413
+ projection.push(
414
+ ProjectionItem.of(
415
+ alias,
416
+ ColumnRef.of(variant.table, col),
417
+ variantTable?.columns[col]?.codecId,
418
+ ),
419
+ );
410
420
  }
411
421
  }
412
422
 
@@ -437,8 +447,8 @@ export function compileSelect(
437
447
  : undefined,
438
448
  );
439
449
 
440
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
441
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
450
+ const { params } = deriveParamsFromAst(ast);
451
+ return buildOrmQueryPlan(contract, ast, params);
442
452
  }
443
453
 
444
454
  export function compileRelationSelect(
@@ -513,6 +523,6 @@ export function compileSelectWithIncludeStrategy(
513
523
  },
514
524
  );
515
525
 
516
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
517
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
526
+ const { params } = deriveParamsFromAst(ast);
527
+ return buildOrmQueryPlan(contract, ast, params);
518
528
  }
package/src/types.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { Contract, ExecutionPlan } from '@prisma-next/contract/types';
2
- import type { AsyncIterableResult } from '@prisma-next/runtime-executor';
1
+ import type { Contract } from '@prisma-next/contract/types';
3
2
  import type {
4
3
  ExtractCodecTypes,
5
4
  ExtractQueryOperationTypes,
@@ -17,15 +16,12 @@ import {
17
16
  OrderByItem,
18
17
  ParamRef,
19
18
  } from '@prisma-next/sql-relational-core/ast';
20
- import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
19
+ import type { Expression } from '@prisma-next/sql-relational-core/expression';
21
20
  import type { ExecutionContext } from '@prisma-next/sql-relational-core/query-lane-context';
22
- import type { ComputeColumnJsType } from '@prisma-next/sql-relational-core/types';
21
+ import type { ComputeColumnJsType, RuntimeScope } from '@prisma-next/sql-relational-core/types';
22
+ import { ifDefined } from '@prisma-next/utils/defined';
23
23
  import type { RowSelection } from './collection-internal-types';
24
24
 
25
- // ---------------------------------------------------------------------------
26
- // Comparison / Filter / Order / Include
27
- // ---------------------------------------------------------------------------
28
-
29
25
  export type AggregateFn = 'count' | 'sum' | 'avg' | 'min' | 'max';
30
26
 
31
27
  export interface IncludeScalar<Result> extends RowSelection<Result> {
@@ -65,10 +61,6 @@ export interface IncludeExpr {
65
61
  readonly combine: Readonly<Record<string, IncludeCombineBranch>> | undefined;
66
62
  }
67
63
 
68
- // ---------------------------------------------------------------------------
69
- // CollectionState — plain data, no query builder types
70
- // ---------------------------------------------------------------------------
71
-
72
64
  export interface CollectionState {
73
65
  readonly filters: readonly AnyExpression[];
74
66
  readonly includes: readonly IncludeExpr[];
@@ -113,16 +105,6 @@ export type DefaultCollectionTypeState = {
113
105
  readonly variantName: undefined;
114
106
  };
115
107
 
116
- // ---------------------------------------------------------------------------
117
- // CollectionContext — bundles lane context + runtime
118
- // ---------------------------------------------------------------------------
119
-
120
- export interface RuntimeScope {
121
- execute<Row = Record<string, unknown>>(
122
- plan: ExecutionPlan<Row> | SqlQueryPlan<Row>,
123
- ): AsyncIterableResult<Row>;
124
- }
125
-
126
108
  export interface RuntimeConnection extends RuntimeScope {
127
109
  release?(): Promise<void>;
128
110
  transaction?(): Promise<RuntimeTransaction>;
@@ -143,10 +125,6 @@ export interface CollectionContext<TContract extends Contract<SqlStorage>> {
143
125
  readonly context: ExecutionContext<TContract>;
144
126
  }
145
127
 
146
- // ---------------------------------------------------------------------------
147
- // ModelAccessor — type-safe proxy for where() callbacks
148
- // ---------------------------------------------------------------------------
149
-
150
128
  export type ComparisonMethodFns<T> = {
151
129
  eq(value: T): AnyExpression;
152
130
  neq(value: T): AnyExpression;
@@ -164,8 +142,7 @@ export type ComparisonMethodFns<T> = {
164
142
  };
165
143
 
166
144
  /**
167
- * Trait-gated comparison methods. Only methods whose required traits are
168
- * all present in `Traits` are included.
145
+ * Trait-gated comparison methods. Only methods whose required traits are all present in `Traits` are included.
169
146
  *
170
147
  * - `traits: []` → always available (isNull, isNotNull)
171
148
  */
@@ -175,10 +152,6 @@ export type ComparisonMethods<T, Traits> = {
175
152
  : never]: ComparisonMethodFns<T>[K];
176
153
  };
177
154
 
178
- // ---------------------------------------------------------------------------
179
- // Extension operation result — returned by calling an extension method
180
- // ---------------------------------------------------------------------------
181
-
182
155
  type QueryOperationReturnTraits<
183
156
  Returns,
184
157
  TCodecTypes extends Record<string, unknown>,
@@ -203,26 +176,6 @@ type QueryOperationReturnJsType<
203
176
  : unknown
204
177
  : unknown;
205
178
 
206
- type CodecArgJsType<Arg, TCodecTypes extends Record<string, unknown>> = Arg extends {
207
- readonly codecId: infer CId extends string;
208
- readonly nullable: infer N;
209
- }
210
- ? CId extends keyof TCodecTypes
211
- ? TCodecTypes[CId] extends { readonly output: infer O }
212
- ? N extends true
213
- ? O | null
214
- : O
215
- : unknown
216
- : unknown
217
- : unknown;
218
-
219
- type MapArgsToJsTypes<
220
- Args extends readonly unknown[],
221
- TCodecTypes extends Record<string, unknown>,
222
- > = Args extends readonly [infer Head, ...infer Tail]
223
- ? [CodecArgJsType<Head, TCodecTypes>, ...MapArgsToJsTypes<Tail, TCodecTypes>]
224
- : [];
225
-
226
179
  type IsBooleanReturn<Returns, TCodecTypes extends Record<string, unknown>> = Returns extends {
227
180
  readonly codecId: infer Id extends string;
228
181
  }
@@ -235,22 +188,41 @@ type IsBooleanReturn<Returns, TCodecTypes extends Record<string, unknown>> = Ret
235
188
  : false
236
189
  : false;
237
190
 
191
+ /**
192
+ * Extract the `{codecId, nullable}` spec carried inside an `Expression<T>`. Used to recover the op's return spec from its impl signature so the pre-existing `QueryOperationReturn*` helpers can consume it unchanged.
193
+ */
194
+ type SpecOf<E> = E extends Expression<infer T> ? T : never;
195
+
196
+ type ImplReturnSpec<Impl> = Impl extends (...args: never[]) => infer Ret ? SpecOf<Ret> : never;
197
+
198
+ /**
199
+ * Builds the ORM column-method signature for an operation.
200
+ *
201
+ * - User args: drops the impl's first parameter (the column is bound at access time) and forwards the rest unchanged. Each remaining arg keeps its authored `CodecExpression` / `TraitExpression` shape — so callers can pass a raw JS value, another column handle (which itself implements `Expression`), or `null` when nullable.
202
+ * - Return: predicate ops (boolean-traited return) yield `AnyExpression`; non-predicate ops yield `ComparisonMethods<JsType, Traits>` of the return codec.
203
+ */
238
204
  type QueryOperationMethod<Op, TCodecTypes extends Record<string, unknown>> = Op extends {
239
- readonly args: readonly [unknown, ...infer UserArgs];
240
- readonly returns: infer Returns;
205
+ readonly impl: (...args: never[]) => unknown;
241
206
  }
242
- ? IsBooleanReturn<Returns, TCodecTypes> extends true
243
- ? (...args: MapArgsToJsTypes<UserArgs, TCodecTypes>) => AnyExpression
244
- : (
245
- ...args: MapArgsToJsTypes<UserArgs, TCodecTypes>
246
- ) => ComparisonMethods<
247
- QueryOperationReturnJsType<Returns, TCodecTypes>,
248
- QueryOperationReturnTraits<Returns, TCodecTypes>
249
- >
207
+ ? Op['impl'] extends (first: never, ...rest: infer UserArgs extends readonly unknown[]) => unknown
208
+ ? ImplReturnSpec<Op['impl']> extends infer Returns
209
+ ? IsBooleanReturn<Returns, TCodecTypes> extends true
210
+ ? (...args: UserArgs) => AnyExpression
211
+ : (
212
+ ...args: UserArgs
213
+ ) => ComparisonMethods<
214
+ QueryOperationReturnJsType<Returns, TCodecTypes>,
215
+ QueryOperationReturnTraits<Returns, TCodecTypes>
216
+ >
217
+ : never
218
+ : never
250
219
  : never;
251
220
 
221
+ /**
222
+ * Tests whether an operation's `self` dispatch hint reaches a field with the given codec identity. Codec hints match by identity; trait hints match when every required trait is present in the field codec's trait set.
223
+ */
252
224
  type OpMatchesField<Op, CodecId extends string, CT extends Record<string, unknown>> = Op extends {
253
- readonly args: readonly [infer Self, ...(readonly unknown[])];
225
+ readonly self: infer Self;
254
226
  }
255
227
  ? Self extends { readonly codecId: CodecId }
256
228
  ? true
@@ -283,21 +255,43 @@ type FieldOperations<
283
255
  : unknown
284
256
  : unknown;
285
257
 
286
- // ---------------------------------------------------------------------------
287
- // COMPARISON_METHODS_METAsingle source of truth for traits + factories
288
- // ---------------------------------------------------------------------------
258
+ /**
259
+ * Resolve the unique column ref carried by `left` so its surrounding `ParamRef` can dispatch through `forColumn`. The previous implementation only matched bare `column-ref` expressions, which lost refs the moment the expression got wrapped (`upper(f.id)`, `BinaryExpr`, function-call expressions, etc.) and column-aware dispatch (AC-5) silently degraded for any predicate that touched a computed column.
260
+ *
261
+ * Walking via `collectColumnRefs()` and accepting only a single unambiguous ref gives `eq(upper(f.id), value)` and friends correct dispatch without inventing refs for ambiguous shapes (e.g. `eq(concat(f.firstName, f.lastName), value)` returns `undefined` and falls through to the codec-id path, which is correct for non-parameterized comparisons).
262
+ */
263
+ function refsFromLeft(left: AnyExpression): { table: string; column: string } | undefined {
264
+ if (left.kind === 'column-ref') {
265
+ return { table: left.table, column: left.column };
266
+ }
267
+ const columnRefs = left.collectColumnRefs();
268
+ if (columnRefs.length !== 1) return undefined;
269
+ const single = columnRefs[0];
270
+ if (!single) return undefined;
271
+ return { table: single.table, column: single.column };
272
+ }
289
273
 
290
- function param(codecId: string | undefined, value: unknown): ParamRef {
291
- return codecId ? ParamRef.of(value, { codecId }) : ParamRef.of(value);
274
+ function param(
275
+ codecId: string | undefined,
276
+ value: unknown,
277
+ refs: { table: string; column: string } | undefined,
278
+ ): ParamRef {
279
+ if (codecId === undefined && refs === undefined) return ParamRef.of(value);
280
+ return ParamRef.of(value, {
281
+ ...ifDefined('codecId', codecId),
282
+ ...ifDefined('refs', refs),
283
+ });
292
284
  }
293
285
 
294
- function paramList(codecId: string | undefined, values: readonly unknown[]): ListExpression {
295
- return ListExpression.of(values.map((value) => param(codecId, value)));
286
+ function paramList(
287
+ codecId: string | undefined,
288
+ values: readonly unknown[],
289
+ refs: { table: string; column: string } | undefined,
290
+ ): ListExpression {
291
+ return ListExpression.of(values.map((value) => param(codecId, value, refs)));
296
292
  }
297
293
 
298
- // never[] is intentional: factories have heterogeneous signatures (value: unknown,
299
- // values: readonly unknown[], pattern: string, etc.) but are only called through
300
- // the typed ComparisonMethodFns interface, never through this type directly.
294
+ // never[] is intentional: factories have heterogeneous signatures (value: unknown, values: readonly unknown[], pattern: string, etc.) but are only called through the typed ComparisonMethodFns interface, never through this type directly.
301
295
  type MethodFactory = (
302
296
  left: AnyExpression,
303
297
  codecId: string | undefined,
@@ -310,12 +304,16 @@ type ComparisonMethodMeta = {
310
304
 
311
305
  function scalarComparisonMethod(op: BinaryOp) {
312
306
  return ((left, codecId) => (value: unknown) =>
313
- new BinaryExpr(op, left, param(codecId, value))) satisfies MethodFactory;
307
+ new BinaryExpr(op, left, param(codecId, value, refsFromLeft(left)))) satisfies MethodFactory;
314
308
  }
315
309
 
316
310
  function listComparisonMethod(op: BinaryOp) {
317
311
  return ((left, codecId) => (values: readonly unknown[]) =>
318
- new BinaryExpr(op, left, paramList(codecId, values))) satisfies MethodFactory;
312
+ new BinaryExpr(
313
+ op,
314
+ left,
315
+ paramList(codecId, values, refsFromLeft(left)),
316
+ )) satisfies MethodFactory;
319
317
  }
320
318
 
321
319
  /**
@@ -400,10 +398,11 @@ export type RelationFilterAccessor<
400
398
  };
401
399
 
402
400
  type ScalarModelAccessor<TContract extends Contract<SqlStorage>, ModelName extends string> = {
403
- [K in keyof FieldsOf<TContract, ModelName> & string]: ComparisonMethods<
404
- FieldJsType<TContract, ModelName, K>,
405
- FieldTraits<TContract, ModelName, K>
406
- > &
401
+ [K in keyof FieldsOf<TContract, ModelName> & string]: Expression<{
402
+ codecId: FieldCodecId<TContract, ModelName, K>;
403
+ nullable: FieldNullable<TContract, ModelName, K>;
404
+ }> &
405
+ ComparisonMethods<FieldJsType<TContract, ModelName, K>, FieldTraits<TContract, ModelName, K>> &
407
406
  FieldOperations<TContract, ModelName, K>;
408
407
  };
409
408
 
@@ -419,18 +418,10 @@ export type ModelAccessor<
419
418
  ModelName extends string,
420
419
  > = ScalarModelAccessor<TContract, ModelName> & RelationModelAccessor<TContract, ModelName>;
421
420
 
422
- // ---------------------------------------------------------------------------
423
- // DefaultModelRow — all scalar fields with JS types
424
- // ---------------------------------------------------------------------------
425
-
426
421
  export type DefaultModelRow<TContract extends Contract<SqlStorage>, ModelName extends string> = {
427
422
  [K in keyof FieldsOf<TContract, ModelName> & string]: FieldJsType<TContract, ModelName, K>;
428
423
  };
429
424
 
430
- // ---------------------------------------------------------------------------
431
- // InferRootRow — discriminated union for polymorphic base models
432
- // ---------------------------------------------------------------------------
433
-
434
425
  type Simplify<T> = { [K in keyof T]: T[K] } & {};
435
426
 
436
427
  type VariantRow<TContract extends Contract<SqlStorage>, ModelName extends string> = ModelDef<
@@ -551,10 +542,6 @@ export type ShorthandWhereFilter<
551
542
  | undefined;
552
543
  }>;
553
544
 
554
- // ---------------------------------------------------------------------------
555
- // Helpers for extracting fields / types from the contract
556
- // ---------------------------------------------------------------------------
557
-
558
545
  type ModelsOf<TContract extends Contract<SqlStorage>> =
559
546
  TContract['models'] extends Record<string, unknown> ? TContract['models'] : Record<string, never>;
560
547
 
@@ -668,10 +655,6 @@ type FieldStorageColumn<
668
655
  FieldName extends string,
669
656
  > = ResolvedStorageColumn<TContract, ModelName, FieldName>;
670
657
 
671
- // ---------------------------------------------------------------------------
672
- // Field trait resolution from contract CodecTypes
673
- // ---------------------------------------------------------------------------
674
-
675
658
  type FieldCodecId<
676
659
  TContract extends Contract<SqlStorage>,
677
660
  ModelName extends string,
@@ -682,6 +665,16 @@ type FieldCodecId<
682
665
  ? Id
683
666
  : never;
684
667
 
668
+ type FieldNullable<
669
+ TContract extends Contract<SqlStorage>,
670
+ ModelName extends string,
671
+ FieldName extends string,
672
+ > = FieldStorageColumn<TContract, ModelName, FieldName> extends {
673
+ readonly nullable: infer N extends boolean;
674
+ }
675
+ ? N
676
+ : false;
677
+
685
678
  type FieldTraits<
686
679
  TContract extends Contract<SqlStorage>,
687
680
  ModelName extends string,
@@ -782,10 +775,6 @@ export type CreateInput<TContract extends Contract<SqlStorage>, ModelName extend
782
775
  > &
783
776
  RelationMutationFields<TContract, ModelName>;
784
777
 
785
- // ---------------------------------------------------------------------------
786
- // Polymorphic write gating
787
- // ---------------------------------------------------------------------------
788
-
789
778
  type IsPolymorphicBase<TContract extends Contract<SqlStorage>, ModelName extends string> = ModelDef<
790
779
  TContract,
791
780
  ModelName
@@ -1057,10 +1046,6 @@ export type MutationUpdateInput<
1057
1046
  ModelName extends string,
1058
1047
  > = Partial<DefaultModelRow<TContract, ModelName>> & RelationMutationFields<TContract, ModelName>;
1059
1048
 
1060
- // ---------------------------------------------------------------------------
1061
- // Relation helpers
1062
- // ---------------------------------------------------------------------------
1063
-
1064
1049
  type ModelRelations<TContract extends Contract<SqlStorage>, ModelName extends string> = ModelDef<
1065
1050
  TContract,
1066
1051
  ModelName
@@ -122,7 +122,10 @@ function createParamRef(
122
122
  if (!codecId) {
123
123
  throw new Error(`Unknown column "${columnRef.column}" in table "${columnRef.table}"`);
124
124
  }
125
- return ParamRef.of(value, { codecId });
125
+ return ParamRef.of(value, {
126
+ codecId,
127
+ refs: { table: columnRef.table, column: columnRef.column },
128
+ });
126
129
  }
127
130
 
128
131
  function createExpressionBinder(contract: Contract<SqlStorage>): ExpressionRewriter {
@@ -170,7 +173,12 @@ function bindSelectAst(contract: Contract<SqlStorage>, ast: SelectAst): SelectAs
170
173
  joins: ast.joins?.map((join) => bindJoin(contract, join)),
171
174
  projection: ast.projection.map(
172
175
  (projection) =>
173
- new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr)),
176
+ new ProjectionItem(
177
+ projection.alias,
178
+ bindProjectionExpr(contract, projection.expr),
179
+ projection.codecId,
180
+ projection.refs,
181
+ ),
174
182
  ),
175
183
  where: ast.where ? bindWhereExprNode(contract, ast.where) : undefined,
176
184
  orderBy: ast.orderBy?.map((orderItem) => bindOrderByItem(contract, orderItem)),