@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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { AsyncIterableResult } from "@prisma-next/runtime-executor";
2
- import { AggregateExpr, AndExpr, BinaryExpr, ColumnRef, DefaultValueExpr, DeleteAst, DerivedTableSource, EqColJoinOn, ExistsExpr, InsertAst, InsertOnConflict, JoinAst, JsonArrayAggExpr, JsonObjectExpr, ListExpression, LiteralExpr, NotExpr, NullCheckExpr, OperationExpr, OrExpr, OrderByItem, ParamRef, ProjectionItem, SelectAst, SubqueryExpr, TableSource, UpdateAst, isWhereExpr } from "@prisma-next/sql-relational-core/ast";
1
+ import { AsyncIterableResult } from "@prisma-next/framework-components/runtime";
2
+ import { AggregateExpr, AndExpr, BinaryExpr, ColumnRef, DefaultValueExpr, DeleteAst, DerivedTableSource, EqColJoinOn, ExistsExpr, InsertAst, InsertOnConflict, JoinAst, JsonArrayAggExpr, JsonObjectExpr, ListExpression, LiteralExpr, NotExpr, NullCheckExpr, OrExpr, OrderByItem, ParamRef, ProjectionItem, SelectAst, SubqueryExpr, TableSource, UpdateAst, collectOrderedParamRefs, isWhereExpr } from "@prisma-next/sql-relational-core/ast";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
 
5
5
  //#region src/collection-contract.ts
@@ -398,83 +398,70 @@ function executeQueryPlan(scope, plan) {
398
398
 
399
399
  //#endregion
400
400
  //#region src/include-strategy.ts
401
+ /**
402
+ * Choose the SQL emission strategy for nested includes based on the
403
+ * contract's declared capabilities.
404
+ *
405
+ * - `'lateral'`: outer SELECT with one LATERAL JOIN per relation,
406
+ * aggregating to JSON. Requires both `lateral` and `jsonAgg`.
407
+ * Postgres has both.
408
+ * - `'correlated'`: outer SELECT with one correlated subquery per
409
+ * relation, aggregating to JSON. Requires `jsonAgg` only.
410
+ * SQLite has `jsonAgg` (via `json_group_array`) but no LATERAL.
411
+ * - `'multiQuery'`: fallback. One SELECT per relation, stitched
412
+ * together in JS via `WHERE pk IN (parent-pk-values)`. Always
413
+ * correct; just N+1 round-trips.
414
+ *
415
+ * The capability flags are looked up under the contract's
416
+ * `targetFamily` and `target` namespaces — the two layers the contract
417
+ * emitter actually populates. Cross-namespace ("`postgres.lateral`
418
+ * found while running SQLite") false positives are impossible because
419
+ * we only inspect the running target's namespaces.
420
+ */
401
421
  function selectIncludeStrategy(contract) {
402
- const capabilities = contract.capabilities;
403
- const hasLateral = hasCapability(capabilities?.["lateral"]);
404
- const hasJsonAgg = hasCapability(capabilities?.["jsonAgg"]);
422
+ const hasLateral = capabilityFlag(contract, "lateral");
423
+ const hasJsonAgg = capabilityFlag(contract, "jsonAgg");
405
424
  if (hasLateral && hasJsonAgg) return "lateral";
406
425
  if (hasJsonAgg) return "correlated";
407
426
  return "multiQuery";
408
427
  }
409
- function hasCapability(value) {
410
- if (value === true) return true;
411
- if (typeof value !== "object" || value === null) return false;
412
- const flags = value;
413
- return Object.values(flags).some((flag) => flag === true);
428
+ /**
429
+ * Read a capability flag from the contract's target/family namespaces.
430
+ *
431
+ * The contract emitter populates `capabilities[targetFamily]` (universal
432
+ * SQL flags like `jsonAgg`, `returning`) and `capabilities[target]`
433
+ * (target-specific flags like `lateral` on Postgres). Either may
434
+ * declare a given flag; the family namespace declares the floor and the
435
+ * target namespace can extend on top.
436
+ */
437
+ function capabilityFlag(contract, flag) {
438
+ return contract.capabilities[contract.targetFamily]?.[flag] === true || contract.capabilities[contract.target]?.[flag] === true;
414
439
  }
415
440
 
416
441
  //#endregion
417
442
  //#region src/query-plan-meta.ts
418
- function resolveProjectionCodecs(contract, ast) {
419
- const codecs = {};
420
- if (ast.kind === "select") {
421
- for (const item of ast.projection) if (item.expr.kind === "column-ref") {
422
- const col = contract.storage.tables[item.expr.table]?.columns[item.expr.column];
423
- if (col?.codecId) codecs[item.alias] = col.codecId;
424
- }
425
- } else if (ast.returning) {
426
- const tableName = ast.table.name;
427
- const table = contract.storage.tables[tableName];
428
- if (!table) return void 0;
429
- for (const colRef of ast.returning) {
430
- const col = table.columns[colRef.column];
431
- if (col?.codecId) codecs[colRef.column] = col.codecId;
432
- }
433
- }
434
- return Object.keys(codecs).length > 0 ? codecs : void 0;
435
- }
436
443
  function deriveParamsFromAst(ast) {
437
- const collectedParams = [...new Set(ast.collectParamRefs())];
438
- return {
439
- params: collectedParams.map((p) => p.value),
440
- paramDescriptors: collectedParams.map((p) => ({
441
- ...ifDefined("name", p.name),
442
- ...ifDefined("codecId", p.codecId),
443
- source: "dsl"
444
- }))
445
- };
444
+ return { params: collectOrderedParamRefs(ast).map((p) => p.value) };
446
445
  }
447
446
  function resolveTableColumns(contract, tableName) {
448
447
  const table = contract.storage.tables[tableName];
449
448
  if (!table) throw new Error(`Unknown table "${tableName}" in SQL ORM query planner`);
450
449
  return Object.keys(table.columns);
451
450
  }
452
- function buildOrmPlanMeta(contract, paramDescriptors = []) {
451
+ function buildOrmPlanMeta(contract) {
453
452
  return {
454
453
  target: contract.target,
455
454
  targetFamily: contract.targetFamily,
456
455
  storageHash: contract.storage.storageHash,
457
456
  ...contract.profileHash !== void 0 ? { profileHash: contract.profileHash } : {},
458
- lane: "orm-client",
459
- paramDescriptors: [...paramDescriptors]
457
+ lane: "orm-client"
460
458
  };
461
459
  }
462
- function buildOrmQueryPlan(contract, ast, params, paramDescriptors = []) {
463
- const projectionTypes = resolveProjectionCodecs(contract, ast);
464
- const codecAnnotations = projectionTypes ? { codecs: Object.freeze({ ...projectionTypes }) } : void 0;
465
- const limitAnnotation = ast.kind === "select" && ast.limit !== void 0 ? { limit: ast.limit } : void 0;
466
- const annotations = codecAnnotations || limitAnnotation ? Object.freeze({
467
- ...codecAnnotations,
468
- ...limitAnnotation
469
- }) : void 0;
460
+ function buildOrmQueryPlan(contract, ast, params) {
470
461
  return Object.freeze({
471
462
  ast,
472
463
  params: [...params],
473
- meta: {
474
- ...buildOrmPlanMeta(contract, paramDescriptors),
475
- ...ifDefined("projectionTypes", projectionTypes),
476
- ...ifDefined("annotations", annotations)
477
- }
464
+ meta: buildOrmPlanMeta(contract)
478
465
  });
479
466
  }
480
467
 
@@ -488,10 +475,21 @@ function combineWhereExprs(filters) {
488
475
 
489
476
  //#endregion
490
477
  //#region src/query-plan-aggregate.ts
491
- function toAggregateExpr(tableName, selector) {
492
- if (selector.fn === "count") return AggregateExpr.count();
478
+ function toAggregateProjection(contract, tableName, selector) {
479
+ if (selector.fn === "count") return {
480
+ expr: AggregateExpr.count(),
481
+ codecId: void 0
482
+ };
493
483
  if (!selector.column) throw new Error(`Aggregate selector "${selector.fn}" requires a field`);
494
- return new AggregateExpr(selector.fn, ColumnRef.of(tableName, selector.column));
484
+ const expr = new AggregateExpr(selector.fn, ColumnRef.of(tableName, selector.column));
485
+ if (selector.fn === "min" || selector.fn === "max") return {
486
+ expr,
487
+ codecId: contract.storage.tables[tableName]?.columns[selector.column]?.codecId
488
+ };
489
+ return {
490
+ expr,
491
+ codecId: void 0
492
+ };
495
493
  }
496
494
  function validateGroupedComparable(value) {
497
495
  switch (value.kind) {
@@ -551,30 +549,39 @@ function validateGroupedHavingExpr(expr) {
551
549
  function compileAggregate(contract, tableName, filters, aggregateSpec) {
552
550
  const entries = Object.entries(aggregateSpec);
553
551
  if (entries.length === 0) throw new Error("aggregate() requires at least one aggregation selector");
554
- const projection = entries.map(([alias, selector]) => ProjectionItem.of(alias, toAggregateExpr(tableName, selector)));
552
+ const projection = entries.map(([alias, selector]) => {
553
+ const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
554
+ return ProjectionItem.of(alias, expr, codecId);
555
+ });
555
556
  let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection);
556
557
  const where = combineWhereExprs(filters);
557
558
  if (where) ast = ast.withWhere(where);
558
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
559
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
559
+ const { params } = deriveParamsFromAst(ast);
560
+ return buildOrmQueryPlan(contract, ast, params);
560
561
  }
561
562
  function compileGroupedAggregate(contract, tableName, filters, groupByColumns, aggregateSpec, havingExpr) {
562
563
  if (groupByColumns.length === 0) throw new Error("groupBy() requires at least one field");
563
564
  const entries = Object.entries(aggregateSpec);
564
565
  if (entries.length === 0) throw new Error("groupBy().aggregate() requires at least one aggregation selector");
565
- const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column))), ...entries.map(([alias, selector]) => ProjectionItem.of(alias, toAggregateExpr(tableName, selector)))];
566
+ const table = contract.storage.tables[tableName];
567
+ const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId)), ...entries.map(([alias, selector]) => {
568
+ const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
569
+ return ProjectionItem.of(alias, expr, codecId);
570
+ })];
566
571
  let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
567
572
  const where = combineWhereExprs(filters);
568
573
  if (where) ast = ast.withWhere(where);
569
574
  if (havingExpr) ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
570
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
571
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
575
+ const { params } = deriveParamsFromAst(ast);
576
+ return buildOrmQueryPlan(contract, ast, params);
572
577
  }
573
578
 
574
579
  //#endregion
575
580
  //#region src/query-plan-mutations.ts
576
581
  function buildReturningColumns(contract, tableName, returningColumns) {
577
- return (returningColumns && returningColumns.length > 0 ? [...returningColumns] : resolveTableColumns(contract, tableName)).map((column) => ColumnRef.of(tableName, column));
582
+ const columns = returningColumns && returningColumns.length > 0 ? [...returningColumns] : resolveTableColumns(contract, tableName);
583
+ const table = contract.storage.tables[tableName];
584
+ return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId));
578
585
  }
579
586
  function toParamAssignments(contract, tableName, values) {
580
587
  const assignments = {};
@@ -585,7 +592,11 @@ function toParamAssignments(contract, tableName, values) {
585
592
  if (!codecId) throw new Error(`Unknown column "${column}" in table "${tableName}"`);
586
593
  assignments[column] = ParamRef.of(value, {
587
594
  name: column,
588
- codecId
595
+ codecId,
596
+ refs: {
597
+ table: tableName,
598
+ column
599
+ }
589
600
  });
590
601
  }
591
602
  return { assignments };
@@ -610,7 +621,11 @@ function normalizeInsertRows(contract, tableName, rows) {
610
621
  if (!codecId) throw new Error(`Unknown column "${column}" in table "${tableName}"`);
611
622
  normalizedRow[column] = ParamRef.of(row[column], {
612
623
  name: column,
613
- codecId
624
+ codecId,
625
+ refs: {
626
+ table: tableName,
627
+ column
628
+ }
614
629
  });
615
630
  continue;
616
631
  }
@@ -622,14 +637,14 @@ function normalizeInsertRows(contract, tableName, rows) {
622
637
  function compileInsertReturning(contract, tableName, rows, returningColumns) {
623
638
  const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
624
639
  const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows).withReturning(buildReturningColumns(contract, tableName, returningColumns));
625
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
626
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
640
+ const { params } = deriveParamsFromAst(ast);
641
+ return buildOrmQueryPlan(contract, ast, params);
627
642
  }
628
643
  function compileInsertCount(contract, tableName, rows) {
629
644
  const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
630
645
  const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows);
631
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
632
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
646
+ const { params } = deriveParamsFromAst(ast);
647
+ return buildOrmQueryPlan(contract, ast, params);
633
648
  }
634
649
  function stripUndefinedValues(row) {
635
650
  const result = {};
@@ -665,38 +680,38 @@ function compileUpsertReturning(contract, tableName, createValues, updateValues,
665
680
  const updateAssignments = Object.keys(updateValues).length > 0 ? toParamAssignments(contract, tableName, updateValues) : void 0;
666
681
  const onConflict = updateAssignments ? InsertOnConflict.on(conflictColumns.map((column) => ColumnRef.of(tableName, column))).doUpdateSet(updateAssignments.assignments) : InsertOnConflict.on(conflictColumns.map((column) => ColumnRef.of(tableName, column))).doNothing();
667
682
  const ast = InsertAst.into(TableSource.named(tableName)).withValues(createAssignments.assignments).withOnConflict(onConflict).withReturning(buildReturningColumns(contract, tableName, returningColumns));
668
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
669
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
683
+ const { params } = deriveParamsFromAst(ast);
684
+ return buildOrmQueryPlan(contract, ast, params);
670
685
  }
671
686
  function compileUpdateReturning(contract, tableName, setValues, filters, returningColumns) {
672
687
  const where = combineWhereExprs(filters);
673
688
  const { assignments } = toParamAssignments(contract, tableName, setValues);
674
689
  let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments).withReturning(buildReturningColumns(contract, tableName, returningColumns));
675
690
  if (where) ast = ast.withWhere(where);
676
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
677
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
691
+ const { params } = deriveParamsFromAst(ast);
692
+ return buildOrmQueryPlan(contract, ast, params);
678
693
  }
679
694
  function compileUpdateCount(contract, tableName, setValues, filters) {
680
695
  const where = combineWhereExprs(filters);
681
696
  const { assignments } = toParamAssignments(contract, tableName, setValues);
682
697
  let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments);
683
698
  if (where) ast = ast.withWhere(where);
684
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
685
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
699
+ const { params } = deriveParamsFromAst(ast);
700
+ return buildOrmQueryPlan(contract, ast, params);
686
701
  }
687
702
  function compileDeleteReturning(contract, tableName, filters, returningColumns) {
688
703
  const where = combineWhereExprs(filters);
689
704
  let ast = DeleteAst.from(TableSource.named(tableName)).withReturning(buildReturningColumns(contract, tableName, returningColumns));
690
705
  if (where) ast = ast.withWhere(where);
691
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
692
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
706
+ const { params } = deriveParamsFromAst(ast);
707
+ return buildOrmQueryPlan(contract, ast, params);
693
708
  }
694
709
  function compileDeleteCount(contract, tableName, filters) {
695
710
  const where = combineWhereExprs(filters);
696
711
  let ast = DeleteAst.from(TableSource.named(tableName));
697
712
  if (where) ast = ast.withWhere(where);
698
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
699
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
713
+ const { params } = deriveParamsFromAst(ast);
714
+ return buildOrmQueryPlan(contract, ast, params);
700
715
  }
701
716
 
702
717
  //#endregion
@@ -767,7 +782,13 @@ function bindComparable(contract, comparable, bindingColumn) {
767
782
  function createParamRef(contract, columnRef, value) {
768
783
  const codecId = contract.storage.tables[columnRef.table]?.columns[columnRef.column]?.codecId;
769
784
  if (!codecId) throw new Error(`Unknown column "${columnRef.column}" in table "${columnRef.table}"`);
770
- return ParamRef.of(value, { codecId });
785
+ return ParamRef.of(value, {
786
+ codecId,
787
+ refs: {
788
+ table: columnRef.table,
789
+ column: columnRef.column
790
+ }
791
+ });
771
792
  }
772
793
  function createExpressionBinder(contract) {
773
794
  return { select: (ast) => bindSelectAst(contract, ast) };
@@ -796,7 +817,7 @@ function bindSelectAst(contract, ast) {
796
817
  return new SelectAst({
797
818
  from: bindFromSource(contract, ast.from),
798
819
  joins: ast.joins?.map((join) => bindJoin(contract, join)),
799
- projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr))),
820
+ projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr), projection.codecId, projection.refs)),
800
821
  where: ast.where ? bindWhereExprNode(contract, ast.where) : void 0,
801
822
  orderBy: ast.orderBy?.map((orderItem) => bindOrderByItem(contract, orderItem)),
802
823
  distinct: ast.distinct,
@@ -812,7 +833,9 @@ function bindSelectAst(contract, ast) {
812
833
  //#endregion
813
834
  //#region src/query-plan-select.ts
814
835
  function buildProjection(contract, tableName, selectedFields, tableRef = tableName) {
815
- return (selectedFields && selectedFields.length > 0 ? [...selectedFields] : resolveTableColumns(contract, tableName)).map((column) => ProjectionItem.of(column, ColumnRef.of(tableRef, column)));
836
+ const columns = selectedFields && selectedFields.length > 0 ? [...selectedFields] : resolveTableColumns(contract, tableName);
837
+ const table = contract.storage.tables[tableName];
838
+ return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableRef, column), table?.columns[column]?.codecId));
816
839
  }
817
840
  function createBoundaryExpr(tableName, entry) {
818
841
  return new BinaryExpr(entry.direction === "asc" ? "gt" : "lt", ColumnRef.of(tableName, entry.column), LiteralExpr.of(entry.value));
@@ -946,10 +969,11 @@ function buildMtiJoins(contract, polyInfo, variantName) {
946
969
  const join = joinType === "inner" ? JoinAst.inner(TableSource.named(variant.table), joinOn) : JoinAst.left(TableSource.named(variant.table), joinOn);
947
970
  joins.push(join);
948
971
  const variantColumns = resolveTableColumns(contract, variant.table);
972
+ const variantTable = contract.storage.tables[variant.table];
949
973
  for (const col of variantColumns) {
950
974
  if (col === pkColumn) continue;
951
975
  const alias = `${variant.table}__${col}`;
952
- projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col)));
976
+ projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col), variantTable?.columns[col]?.codecId));
953
977
  }
954
978
  }
955
979
  return {
@@ -967,8 +991,8 @@ function compileSelect(contract, tableName, state, modelName) {
967
991
  joins: mtiArtifacts.joins,
968
992
  includeProjection: mtiArtifacts.projection
969
993
  } : void 0);
970
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
971
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
994
+ const { params } = deriveParamsFromAst(ast);
995
+ return buildOrmQueryPlan(contract, ast, params);
972
996
  }
973
997
  function compileRelationSelect(contract, relatedTableName, targetColumn, parentPks, nestedState) {
974
998
  const inFilter = BinaryExpr.in(ColumnRef.of(relatedTableName, targetColumn), ListExpression.fromValues(parentPks));
@@ -1009,8 +1033,8 @@ function compileSelectWithIncludeStrategy(contract, tableName, state, strategy,
1009
1033
  includeProjection,
1010
1034
  ...topLevelWhere ? { where: topLevelWhere } : {}
1011
1035
  });
1012
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
1013
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
1036
+ const { params } = deriveParamsFromAst(ast);
1037
+ return buildOrmQueryPlan(contract, ast, params);
1014
1038
  }
1015
1039
 
1016
1040
  //#endregion
@@ -1401,7 +1425,7 @@ function shorthandToWhereExpr(context, modelName, filters) {
1401
1425
  function assertFieldHasEqualityTrait(context, modelName, fieldName) {
1402
1426
  const fieldType = modelOf(context.contract, modelName)?.fields?.[fieldName]?.type;
1403
1427
  const codecId = fieldType?.kind === "scalar" ? fieldType.codecId : void 0;
1404
- if (!(codecId ? context.codecs.traitsOf(codecId) : []).includes("equality")) throw new Error(`Shorthand filter on "${modelName}.${fieldName}": field does not support equality comparisons`);
1428
+ if (!(codecId ? context.codecDescriptors.descriptorFor(codecId)?.traits ?? [] : []).includes("equality")) throw new Error(`Shorthand filter on "${modelName}.${fieldName}": field does not support equality comparisons`);
1405
1429
  }
1406
1430
 
1407
1431
  //#endregion
@@ -1565,17 +1589,40 @@ function emptyState() {
1565
1589
  variantName: void 0
1566
1590
  };
1567
1591
  }
1568
- function param(codecId, value) {
1569
- return codecId ? ParamRef.of(value, { codecId }) : ParamRef.of(value);
1592
+ /**
1593
+ * 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.
1594
+ *
1595
+ * 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).
1596
+ */
1597
+ function refsFromLeft(left) {
1598
+ if (left.kind === "column-ref") return {
1599
+ table: left.table,
1600
+ column: left.column
1601
+ };
1602
+ const columnRefs = left.collectColumnRefs();
1603
+ if (columnRefs.length !== 1) return void 0;
1604
+ const single = columnRefs[0];
1605
+ if (!single) return void 0;
1606
+ return {
1607
+ table: single.table,
1608
+ column: single.column
1609
+ };
1610
+ }
1611
+ function param(codecId, value, refs) {
1612
+ if (codecId === void 0 && refs === void 0) return ParamRef.of(value);
1613
+ return ParamRef.of(value, {
1614
+ ...ifDefined("codecId", codecId),
1615
+ ...ifDefined("refs", refs)
1616
+ });
1570
1617
  }
1571
- function paramList(codecId, values) {
1572
- return ListExpression.of(values.map((value) => param(codecId, value)));
1618
+ function paramList(codecId, values, refs) {
1619
+ return ListExpression.of(values.map((value) => param(codecId, value, refs)));
1573
1620
  }
1574
1621
  function scalarComparisonMethod(op) {
1575
- return ((left, codecId) => (value) => new BinaryExpr(op, left, param(codecId, value)));
1622
+ return ((left, codecId) => (value) => new BinaryExpr(op, left, param(codecId, value, refsFromLeft(left))));
1576
1623
  }
1577
1624
  function listComparisonMethod(op) {
1578
- return ((left, codecId) => (values) => new BinaryExpr(op, left, paramList(codecId, values)));
1625
+ return ((left, codecId) => (values) => new BinaryExpr(op, left, paramList(codecId, values, refsFromLeft(left))));
1579
1626
  }
1580
1627
  /**
1581
1628
  * Declares trait requirements and runtime factory for each comparison method.
@@ -1655,12 +1702,13 @@ function createModelAccessor(context, modelName) {
1655
1702
  existing.push(op);
1656
1703
  }
1657
1704
  for (const [name, entry] of Object.entries(context.queryOperations.entries())) {
1658
- const self = entry.args[0];
1659
1705
  const op = [name, entry];
1660
- if (self?.codecId) registerOp(self.codecId, op);
1661
- else if (self?.traits) for (const codec of context.codecs.values()) {
1662
- const codecTraits = codec.traits ?? [];
1663
- if (self.traits.every((t) => codecTraits.includes(t))) registerOp(codec.id, op);
1706
+ const self = entry.self;
1707
+ if (!self) continue;
1708
+ if (self.codecId !== void 0) registerOp(self.codecId, op);
1709
+ else if (self.traits !== void 0) for (const descriptor of context.codecDescriptors.values()) {
1710
+ const descriptorTraits = descriptor.traits;
1711
+ if (self.traits.every((t) => descriptorTraits.includes(t))) registerOp(descriptor.codecId, op);
1664
1712
  }
1665
1713
  }
1666
1714
  return new Proxy({}, { get(_target, prop) {
@@ -1668,48 +1716,51 @@ function createModelAccessor(context, modelName) {
1668
1716
  const relation = modelRelations[prop];
1669
1717
  if (relation) return createRelationFilterAccessor(context, modelName, tableName, relation);
1670
1718
  const columnName = fieldToColumn[prop] ?? prop;
1671
- const traits = resolveFieldTraits(contract, modelName, prop, context);
1672
- const codecId = resolveFieldCodecId(contract, tableName, columnName);
1673
- return createScalarFieldAccessor(tableName, columnName, codecId, traits, codecId ? opsByCodecId.get(codecId) ?? [] : [], context);
1719
+ const column = resolveColumn(contract, tableName, columnName);
1720
+ if (!column) return;
1721
+ const traits = context.codecDescriptors.descriptorFor(column.codecId)?.traits ?? [];
1722
+ const operations = opsByCodecId.get(column.codecId) ?? [];
1723
+ return createScalarFieldAccessor(tableName, columnName, column.codecId, column.nullable, traits, operations, context);
1674
1724
  } });
1675
1725
  }
1676
- function resolveFieldTraits(contract, modelName, fieldName, context) {
1677
- const fieldType = modelOf(contract, modelName)?.fields?.[fieldName]?.type;
1678
- const codecId = fieldType?.kind === "scalar" ? fieldType.codecId : void 0;
1679
- if (!codecId) return [];
1680
- return context.codecs.traitsOf(codecId);
1681
- }
1682
- function resolveFieldCodecId(contract, tableName, columnName) {
1683
- return (contract.storage.tables?.[tableName])?.columns?.[columnName]?.codecId;
1726
+ function resolveColumn(contract, tableName, columnName) {
1727
+ const column = contract.storage.tables?.[tableName]?.columns?.[columnName];
1728
+ if (!column) return void 0;
1729
+ return {
1730
+ codecId: column.codecId,
1731
+ nullable: column.nullable
1732
+ };
1684
1733
  }
1685
- function createScalarFieldAccessor(tableName, columnName, codecId, traits, operations, context) {
1734
+ function createScalarFieldAccessor(tableName, columnName, codecId, nullable, traits, operations, context) {
1686
1735
  const column = ColumnRef.of(tableName, columnName);
1687
- const methods = {};
1736
+ const comparisonEntries = [];
1688
1737
  for (const [name, meta] of Object.entries(COMPARISON_METHODS_META)) {
1689
1738
  if (meta.traits.some((t) => !traits.includes(t))) continue;
1690
- methods[name] = meta.create(column, codecId);
1739
+ comparisonEntries.push([name, meta.create(column, codecId)]);
1691
1740
  }
1692
- for (const [name, entry] of operations) methods[name] = createExtensionMethodFactory(column, name, entry, context);
1693
- return methods;
1741
+ const accessor = {
1742
+ returnType: {
1743
+ codecId,
1744
+ nullable
1745
+ },
1746
+ buildAst: () => column,
1747
+ ...Object.fromEntries(comparisonEntries)
1748
+ };
1749
+ for (const [name, entry] of operations) accessor[name] = createExtensionMethodFactory(accessor, entry, context);
1750
+ return accessor;
1694
1751
  }
1695
- function createExtensionMethodFactory(column, methodName, entry, context) {
1696
- const returnTraits = context.codecs.traitsOf(entry.returns.codecId);
1697
- const isPredicate = returnTraits.includes("boolean");
1752
+ function createExtensionMethodFactory(selfExpr, entry, context) {
1698
1753
  return (...args) => {
1699
- const opExpr = new OperationExpr({
1700
- method: methodName,
1701
- self: column,
1702
- args: entry.args.slice(1).map((argSpec, i) => {
1703
- return ParamRef.of(args[i], argSpec.codecId ? { codecId: argSpec.codecId } : void 0);
1704
- }),
1705
- returns: entry.returns,
1706
- lowering: entry.lowering
1707
- });
1708
- if (isPredicate) return opExpr;
1754
+ const impl = entry.impl;
1755
+ const result = impl(selfExpr, ...args);
1756
+ const returnCodecId = result.returnType.codecId;
1757
+ const returnTraits = context.codecDescriptors.descriptorFor(returnCodecId)?.traits ?? [];
1758
+ if (returnTraits.includes("boolean")) return result.buildAst();
1759
+ const resultAst = result.buildAst();
1709
1760
  const methods = {};
1710
1761
  for (const [resultMethodName, meta] of Object.entries(COMPARISON_METHODS_META)) {
1711
1762
  if (meta.traits.some((t) => !returnTraits.includes(t))) continue;
1712
- methods[resultMethodName] = meta.create(opExpr, entry.returns.codecId);
1763
+ methods[resultMethodName] = meta.create(resultAst, returnCodecId);
1713
1764
  }
1714
1765
  return methods;
1715
1766
  };
@@ -1756,7 +1807,7 @@ function toRelationWhereExpr(context, relatedModelName, predicate) {
1756
1807
  for (const [fieldName, value] of Object.entries(predicate)) {
1757
1808
  if (value === void 0) continue;
1758
1809
  const fieldAccessor = accessor[fieldName];
1759
- if (!fieldAccessor) continue;
1810
+ if (!fieldAccessor) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": field is not defined on the model`);
1760
1811
  if (value === null) {
1761
1812
  if (!fieldAccessor.isNull) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": isNull is unexpectedly missing — this is a bug in trait gating`);
1762
1813
  exprs.push(fieldAccessor.isNull());
@@ -1895,9 +1946,16 @@ async function updateFirstGraph(scope, context, modelName, filters, input) {
1895
1946
  let parentRow = existingRow;
1896
1947
  const mappedUpdateData = mapModelDataToStorageRow(contract, modelName, scalarData);
1897
1948
  if (Object.keys(mappedUpdateData).length > 0) {
1949
+ const tableName = resolveModelTableName(contract, modelName);
1950
+ const appliedUpdateDefaults = context.applyMutationDefaults({
1951
+ op: "update",
1952
+ table: tableName,
1953
+ values: mappedUpdateData
1954
+ });
1955
+ for (const def of appliedUpdateDefaults) mappedUpdateData[def.column] = def.value;
1898
1956
  const pkWhere = shorthandToWhereExpr(context, modelName, buildPrimaryKeyFilterFromRow(contract, modelName, existingRow));
1899
1957
  if (!pkWhere) throw new Error(`Failed to build primary key filter for model "${modelName}"`);
1900
- const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract, resolveModelTableName(contract, modelName), mappedUpdateData, [pkWhere], void 0)).toArray())[0];
1958
+ const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract, tableName, mappedUpdateData, [pkWhere], void 0)).toArray())[0];
1901
1959
  if (updatedRaw) parentRow = mapStorageRowToModelFields(contract, modelName, updatedRaw);
1902
1960
  }
1903
1961
  for (const relationMutation of childOwned) await applyChildOwnedMutation(scope, context, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
@@ -2109,15 +2167,25 @@ function isToWhereExpr(arg) {
2109
2167
  //#endregion
2110
2168
  //#region src/collection.ts
2111
2169
  function applyCreateDefaults(ctx, tableName, rows) {
2170
+ const defaultValueCache = rows.length > 1 ? /* @__PURE__ */ new Map() : void 0;
2112
2171
  for (const row of rows) {
2113
2172
  const applied = ctx.context.applyMutationDefaults({
2114
2173
  op: "create",
2115
2174
  table: tableName,
2116
- values: row
2175
+ values: row,
2176
+ ...defaultValueCache ? { defaultValueCache } : {}
2117
2177
  });
2118
2178
  for (const def of applied) row[def.column] = def.value;
2119
2179
  }
2120
2180
  }
2181
+ function applyUpdateDefaults(ctx, tableName, values) {
2182
+ const applied = ctx.context.applyMutationDefaults({
2183
+ op: "update",
2184
+ table: tableName,
2185
+ values
2186
+ });
2187
+ for (const def of applied) values[def.column] = def.value;
2188
+ }
2121
2189
  function isToWhereExprInput(value) {
2122
2190
  return typeof value === "object" && value !== null && "toWhereExpr" in value && typeof value.toWhereExpr === "function";
2123
2191
  }
@@ -2480,6 +2548,7 @@ var Collection = class Collection {
2480
2548
  applyCreateDefaults(this.ctx, this.tableName, [createValues]);
2481
2549
  const updateValues = mapModelDataToStorageRow(this.contract, this.modelName, input.update);
2482
2550
  const hasUpdateValues = Object.keys(updateValues).length > 0;
2551
+ if (hasUpdateValues) applyUpdateDefaults(this.ctx, this.tableName, updateValues);
2483
2552
  const conflictColumns = resolveUpsertConflictColumns(this.contract, this.modelName, input.conflictOn);
2484
2553
  if (conflictColumns.length === 0) throw new Error(`upsert() for model "${this.modelName}" requires conflict columns`);
2485
2554
  const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
@@ -2526,6 +2595,7 @@ var Collection = class Collection {
2526
2595
  const generator = async function* () {};
2527
2596
  return new AsyncIterableResult(generator());
2528
2597
  }
2598
+ applyUpdateDefaults(this.ctx, this.tableName, mappedData);
2529
2599
  const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
2530
2600
  const { selectedForQuery: selectedForUpdate, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
2531
2601
  const compiled = compileUpdateReturning(this.contract, this.tableName, mappedData, this.state.filters, selectedForUpdate);
@@ -2542,6 +2612,7 @@ var Collection = class Collection {
2542
2612
  async updateCount(data) {
2543
2613
  const mappedData = mapModelDataToStorageRow(this.contract, this.modelName, data);
2544
2614
  if (Object.keys(mappedData).length === 0) return 0;
2615
+ applyUpdateDefaults(this.ctx, this.tableName, mappedData);
2545
2616
  const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.tableName);
2546
2617
  const countState = {
2547
2618
  ...emptyState(),