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

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,6 +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";
3
- import { ifDefined } from "@prisma-next/utils/defined";
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";
4
3
 
5
4
  //#region src/collection-contract.ts
6
5
  function modelsOf(contract) {
@@ -398,83 +397,70 @@ function executeQueryPlan(scope, plan) {
398
397
 
399
398
  //#endregion
400
399
  //#region src/include-strategy.ts
400
+ /**
401
+ * Choose the SQL emission strategy for nested includes based on the
402
+ * contract's declared capabilities.
403
+ *
404
+ * - `'lateral'`: outer SELECT with one LATERAL JOIN per relation,
405
+ * aggregating to JSON. Requires both `lateral` and `jsonAgg`.
406
+ * Postgres has both.
407
+ * - `'correlated'`: outer SELECT with one correlated subquery per
408
+ * relation, aggregating to JSON. Requires `jsonAgg` only.
409
+ * SQLite has `jsonAgg` (via `json_group_array`) but no LATERAL.
410
+ * - `'multiQuery'`: fallback. One SELECT per relation, stitched
411
+ * together in JS via `WHERE pk IN (parent-pk-values)`. Always
412
+ * correct; just N+1 round-trips.
413
+ *
414
+ * The capability flags are looked up under the contract's
415
+ * `targetFamily` and `target` namespaces — the two layers the contract
416
+ * emitter actually populates. Cross-namespace ("`postgres.lateral`
417
+ * found while running SQLite") false positives are impossible because
418
+ * we only inspect the running target's namespaces.
419
+ */
401
420
  function selectIncludeStrategy(contract) {
402
- const capabilities = contract.capabilities;
403
- const hasLateral = hasCapability(capabilities?.["lateral"]);
404
- const hasJsonAgg = hasCapability(capabilities?.["jsonAgg"]);
421
+ const hasLateral = capabilityFlag(contract, "lateral");
422
+ const hasJsonAgg = capabilityFlag(contract, "jsonAgg");
405
423
  if (hasLateral && hasJsonAgg) return "lateral";
406
424
  if (hasJsonAgg) return "correlated";
407
425
  return "multiQuery";
408
426
  }
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);
427
+ /**
428
+ * Read a capability flag from the contract's target/family namespaces.
429
+ *
430
+ * The contract emitter populates `capabilities[targetFamily]` (universal
431
+ * SQL flags like `jsonAgg`, `returning`) and `capabilities[target]`
432
+ * (target-specific flags like `lateral` on Postgres). Either may
433
+ * declare a given flag; the family namespace declares the floor and the
434
+ * target namespace can extend on top.
435
+ */
436
+ function capabilityFlag(contract, flag) {
437
+ return contract.capabilities[contract.targetFamily]?.[flag] === true || contract.capabilities[contract.target]?.[flag] === true;
414
438
  }
415
439
 
416
440
  //#endregion
417
441
  //#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
442
  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
- };
443
+ return { params: collectOrderedParamRefs(ast).map((p) => p.value) };
446
444
  }
447
445
  function resolveTableColumns(contract, tableName) {
448
446
  const table = contract.storage.tables[tableName];
449
447
  if (!table) throw new Error(`Unknown table "${tableName}" in SQL ORM query planner`);
450
448
  return Object.keys(table.columns);
451
449
  }
452
- function buildOrmPlanMeta(contract, paramDescriptors = []) {
450
+ function buildOrmPlanMeta(contract) {
453
451
  return {
454
452
  target: contract.target,
455
453
  targetFamily: contract.targetFamily,
456
454
  storageHash: contract.storage.storageHash,
457
455
  ...contract.profileHash !== void 0 ? { profileHash: contract.profileHash } : {},
458
- lane: "orm-client",
459
- paramDescriptors: [...paramDescriptors]
456
+ lane: "orm-client"
460
457
  };
461
458
  }
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;
459
+ function buildOrmQueryPlan(contract, ast, params) {
470
460
  return Object.freeze({
471
461
  ast,
472
462
  params: [...params],
473
- meta: {
474
- ...buildOrmPlanMeta(contract, paramDescriptors),
475
- ...ifDefined("projectionTypes", projectionTypes),
476
- ...ifDefined("annotations", annotations)
477
- }
463
+ meta: buildOrmPlanMeta(contract)
478
464
  });
479
465
  }
480
466
 
@@ -488,10 +474,21 @@ function combineWhereExprs(filters) {
488
474
 
489
475
  //#endregion
490
476
  //#region src/query-plan-aggregate.ts
491
- function toAggregateExpr(tableName, selector) {
492
- if (selector.fn === "count") return AggregateExpr.count();
477
+ function toAggregateProjection(contract, tableName, selector) {
478
+ if (selector.fn === "count") return {
479
+ expr: AggregateExpr.count(),
480
+ codecId: void 0
481
+ };
493
482
  if (!selector.column) throw new Error(`Aggregate selector "${selector.fn}" requires a field`);
494
- return new AggregateExpr(selector.fn, ColumnRef.of(tableName, selector.column));
483
+ const expr = new AggregateExpr(selector.fn, ColumnRef.of(tableName, selector.column));
484
+ if (selector.fn === "min" || selector.fn === "max") return {
485
+ expr,
486
+ codecId: contract.storage.tables[tableName]?.columns[selector.column]?.codecId
487
+ };
488
+ return {
489
+ expr,
490
+ codecId: void 0
491
+ };
495
492
  }
496
493
  function validateGroupedComparable(value) {
497
494
  switch (value.kind) {
@@ -551,30 +548,39 @@ function validateGroupedHavingExpr(expr) {
551
548
  function compileAggregate(contract, tableName, filters, aggregateSpec) {
552
549
  const entries = Object.entries(aggregateSpec);
553
550
  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)));
551
+ const projection = entries.map(([alias, selector]) => {
552
+ const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
553
+ return ProjectionItem.of(alias, expr, codecId);
554
+ });
555
555
  let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection);
556
556
  const where = combineWhereExprs(filters);
557
557
  if (where) ast = ast.withWhere(where);
558
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
559
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
558
+ const { params } = deriveParamsFromAst(ast);
559
+ return buildOrmQueryPlan(contract, ast, params);
560
560
  }
561
561
  function compileGroupedAggregate(contract, tableName, filters, groupByColumns, aggregateSpec, havingExpr) {
562
562
  if (groupByColumns.length === 0) throw new Error("groupBy() requires at least one field");
563
563
  const entries = Object.entries(aggregateSpec);
564
564
  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)))];
565
+ const table = contract.storage.tables[tableName];
566
+ const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId)), ...entries.map(([alias, selector]) => {
567
+ const { expr, codecId } = toAggregateProjection(contract, tableName, selector);
568
+ return ProjectionItem.of(alias, expr, codecId);
569
+ })];
566
570
  let ast = SelectAst.from(TableSource.named(tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
567
571
  const where = combineWhereExprs(filters);
568
572
  if (where) ast = ast.withWhere(where);
569
573
  if (havingExpr) ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
570
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
571
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
574
+ const { params } = deriveParamsFromAst(ast);
575
+ return buildOrmQueryPlan(contract, ast, params);
572
576
  }
573
577
 
574
578
  //#endregion
575
579
  //#region src/query-plan-mutations.ts
576
580
  function buildReturningColumns(contract, tableName, returningColumns) {
577
- return (returningColumns && returningColumns.length > 0 ? [...returningColumns] : resolveTableColumns(contract, tableName)).map((column) => ColumnRef.of(tableName, column));
581
+ const columns = returningColumns && returningColumns.length > 0 ? [...returningColumns] : resolveTableColumns(contract, tableName);
582
+ const table = contract.storage.tables[tableName];
583
+ return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId));
578
584
  }
579
585
  function toParamAssignments(contract, tableName, values) {
580
586
  const assignments = {};
@@ -622,14 +628,14 @@ function normalizeInsertRows(contract, tableName, rows) {
622
628
  function compileInsertReturning(contract, tableName, rows, returningColumns) {
623
629
  const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
624
630
  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);
631
+ const { params } = deriveParamsFromAst(ast);
632
+ return buildOrmQueryPlan(contract, ast, params);
627
633
  }
628
634
  function compileInsertCount(contract, tableName, rows) {
629
635
  const { rows: normalizedRows } = normalizeInsertRows(contract, tableName, rows);
630
636
  const ast = InsertAst.into(TableSource.named(tableName)).withRows(normalizedRows);
631
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
632
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
637
+ const { params } = deriveParamsFromAst(ast);
638
+ return buildOrmQueryPlan(contract, ast, params);
633
639
  }
634
640
  function stripUndefinedValues(row) {
635
641
  const result = {};
@@ -665,38 +671,38 @@ function compileUpsertReturning(contract, tableName, createValues, updateValues,
665
671
  const updateAssignments = Object.keys(updateValues).length > 0 ? toParamAssignments(contract, tableName, updateValues) : void 0;
666
672
  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
673
  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);
674
+ const { params } = deriveParamsFromAst(ast);
675
+ return buildOrmQueryPlan(contract, ast, params);
670
676
  }
671
677
  function compileUpdateReturning(contract, tableName, setValues, filters, returningColumns) {
672
678
  const where = combineWhereExprs(filters);
673
679
  const { assignments } = toParamAssignments(contract, tableName, setValues);
674
680
  let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments).withReturning(buildReturningColumns(contract, tableName, returningColumns));
675
681
  if (where) ast = ast.withWhere(where);
676
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
677
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
682
+ const { params } = deriveParamsFromAst(ast);
683
+ return buildOrmQueryPlan(contract, ast, params);
678
684
  }
679
685
  function compileUpdateCount(contract, tableName, setValues, filters) {
680
686
  const where = combineWhereExprs(filters);
681
687
  const { assignments } = toParamAssignments(contract, tableName, setValues);
682
688
  let ast = UpdateAst.table(TableSource.named(tableName)).withSet(assignments);
683
689
  if (where) ast = ast.withWhere(where);
684
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
685
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
690
+ const { params } = deriveParamsFromAst(ast);
691
+ return buildOrmQueryPlan(contract, ast, params);
686
692
  }
687
693
  function compileDeleteReturning(contract, tableName, filters, returningColumns) {
688
694
  const where = combineWhereExprs(filters);
689
695
  let ast = DeleteAst.from(TableSource.named(tableName)).withReturning(buildReturningColumns(contract, tableName, returningColumns));
690
696
  if (where) ast = ast.withWhere(where);
691
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
692
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
697
+ const { params } = deriveParamsFromAst(ast);
698
+ return buildOrmQueryPlan(contract, ast, params);
693
699
  }
694
700
  function compileDeleteCount(contract, tableName, filters) {
695
701
  const where = combineWhereExprs(filters);
696
702
  let ast = DeleteAst.from(TableSource.named(tableName));
697
703
  if (where) ast = ast.withWhere(where);
698
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
699
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
704
+ const { params } = deriveParamsFromAst(ast);
705
+ return buildOrmQueryPlan(contract, ast, params);
700
706
  }
701
707
 
702
708
  //#endregion
@@ -796,7 +802,7 @@ function bindSelectAst(contract, ast) {
796
802
  return new SelectAst({
797
803
  from: bindFromSource(contract, ast.from),
798
804
  joins: ast.joins?.map((join) => bindJoin(contract, join)),
799
- projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr))),
805
+ projection: ast.projection.map((projection) => new ProjectionItem(projection.alias, bindProjectionExpr(contract, projection.expr), projection.codecId)),
800
806
  where: ast.where ? bindWhereExprNode(contract, ast.where) : void 0,
801
807
  orderBy: ast.orderBy?.map((orderItem) => bindOrderByItem(contract, orderItem)),
802
808
  distinct: ast.distinct,
@@ -812,7 +818,9 @@ function bindSelectAst(contract, ast) {
812
818
  //#endregion
813
819
  //#region src/query-plan-select.ts
814
820
  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)));
821
+ const columns = selectedFields && selectedFields.length > 0 ? [...selectedFields] : resolveTableColumns(contract, tableName);
822
+ const table = contract.storage.tables[tableName];
823
+ return columns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableRef, column), table?.columns[column]?.codecId));
816
824
  }
817
825
  function createBoundaryExpr(tableName, entry) {
818
826
  return new BinaryExpr(entry.direction === "asc" ? "gt" : "lt", ColumnRef.of(tableName, entry.column), LiteralExpr.of(entry.value));
@@ -946,10 +954,11 @@ function buildMtiJoins(contract, polyInfo, variantName) {
946
954
  const join = joinType === "inner" ? JoinAst.inner(TableSource.named(variant.table), joinOn) : JoinAst.left(TableSource.named(variant.table), joinOn);
947
955
  joins.push(join);
948
956
  const variantColumns = resolveTableColumns(contract, variant.table);
957
+ const variantTable = contract.storage.tables[variant.table];
949
958
  for (const col of variantColumns) {
950
959
  if (col === pkColumn) continue;
951
960
  const alias = `${variant.table}__${col}`;
952
- projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col)));
961
+ projection.push(ProjectionItem.of(alias, ColumnRef.of(variant.table, col), variantTable?.columns[col]?.codecId));
953
962
  }
954
963
  }
955
964
  return {
@@ -967,8 +976,8 @@ function compileSelect(contract, tableName, state, modelName) {
967
976
  joins: mtiArtifacts.joins,
968
977
  includeProjection: mtiArtifacts.projection
969
978
  } : void 0);
970
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
971
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
979
+ const { params } = deriveParamsFromAst(ast);
980
+ return buildOrmQueryPlan(contract, ast, params);
972
981
  }
973
982
  function compileRelationSelect(contract, relatedTableName, targetColumn, parentPks, nestedState) {
974
983
  const inFilter = BinaryExpr.in(ColumnRef.of(relatedTableName, targetColumn), ListExpression.fromValues(parentPks));
@@ -1009,8 +1018,8 @@ function compileSelectWithIncludeStrategy(contract, tableName, state, strategy,
1009
1018
  includeProjection,
1010
1019
  ...topLevelWhere ? { where: topLevelWhere } : {}
1011
1020
  });
1012
- const { params, paramDescriptors } = deriveParamsFromAst(ast);
1013
- return buildOrmQueryPlan(contract, ast, params, paramDescriptors);
1021
+ const { params } = deriveParamsFromAst(ast);
1022
+ return buildOrmQueryPlan(contract, ast, params);
1014
1023
  }
1015
1024
 
1016
1025
  //#endregion
@@ -1401,7 +1410,7 @@ function shorthandToWhereExpr(context, modelName, filters) {
1401
1410
  function assertFieldHasEqualityTrait(context, modelName, fieldName) {
1402
1411
  const fieldType = modelOf(context.contract, modelName)?.fields?.[fieldName]?.type;
1403
1412
  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`);
1413
+ if (!(codecId ? context.codecDescriptors.descriptorFor(codecId)?.traits ?? [] : []).includes("equality")) throw new Error(`Shorthand filter on "${modelName}.${fieldName}": field does not support equality comparisons`);
1405
1414
  }
1406
1415
 
1407
1416
  //#endregion
@@ -1655,12 +1664,13 @@ function createModelAccessor(context, modelName) {
1655
1664
  existing.push(op);
1656
1665
  }
1657
1666
  for (const [name, entry] of Object.entries(context.queryOperations.entries())) {
1658
- const self = entry.args[0];
1659
1667
  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);
1668
+ const self = entry.self;
1669
+ if (!self) continue;
1670
+ if (self.codecId !== void 0) registerOp(self.codecId, op);
1671
+ else if (self.traits !== void 0) for (const descriptor of context.codecDescriptors.values()) {
1672
+ const descriptorTraits = descriptor.traits;
1673
+ if (self.traits.every((t) => descriptorTraits.includes(t))) registerOp(descriptor.codecId, op);
1664
1674
  }
1665
1675
  }
1666
1676
  return new Proxy({}, { get(_target, prop) {
@@ -1668,48 +1678,51 @@ function createModelAccessor(context, modelName) {
1668
1678
  const relation = modelRelations[prop];
1669
1679
  if (relation) return createRelationFilterAccessor(context, modelName, tableName, relation);
1670
1680
  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);
1681
+ const column = resolveColumn(contract, tableName, columnName);
1682
+ if (!column) return;
1683
+ const traits = context.codecDescriptors.descriptorFor(column.codecId)?.traits ?? [];
1684
+ const operations = opsByCodecId.get(column.codecId) ?? [];
1685
+ return createScalarFieldAccessor(tableName, columnName, column.codecId, column.nullable, traits, operations, context);
1674
1686
  } });
1675
1687
  }
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;
1688
+ function resolveColumn(contract, tableName, columnName) {
1689
+ const column = contract.storage.tables?.[tableName]?.columns?.[columnName];
1690
+ if (!column) return void 0;
1691
+ return {
1692
+ codecId: column.codecId,
1693
+ nullable: column.nullable
1694
+ };
1684
1695
  }
1685
- function createScalarFieldAccessor(tableName, columnName, codecId, traits, operations, context) {
1696
+ function createScalarFieldAccessor(tableName, columnName, codecId, nullable, traits, operations, context) {
1686
1697
  const column = ColumnRef.of(tableName, columnName);
1687
- const methods = {};
1698
+ const comparisonEntries = [];
1688
1699
  for (const [name, meta] of Object.entries(COMPARISON_METHODS_META)) {
1689
1700
  if (meta.traits.some((t) => !traits.includes(t))) continue;
1690
- methods[name] = meta.create(column, codecId);
1701
+ comparisonEntries.push([name, meta.create(column, codecId)]);
1691
1702
  }
1692
- for (const [name, entry] of operations) methods[name] = createExtensionMethodFactory(column, name, entry, context);
1693
- return methods;
1703
+ const accessor = {
1704
+ returnType: {
1705
+ codecId,
1706
+ nullable
1707
+ },
1708
+ buildAst: () => column,
1709
+ ...Object.fromEntries(comparisonEntries)
1710
+ };
1711
+ for (const [name, entry] of operations) accessor[name] = createExtensionMethodFactory(accessor, entry, context);
1712
+ return accessor;
1694
1713
  }
1695
- function createExtensionMethodFactory(column, methodName, entry, context) {
1696
- const returnTraits = context.codecs.traitsOf(entry.returns.codecId);
1697
- const isPredicate = returnTraits.includes("boolean");
1714
+ function createExtensionMethodFactory(selfExpr, entry, context) {
1698
1715
  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;
1716
+ const impl = entry.impl;
1717
+ const result = impl(selfExpr, ...args);
1718
+ const returnCodecId = result.returnType.codecId;
1719
+ const returnTraits = context.codecDescriptors.descriptorFor(returnCodecId)?.traits ?? [];
1720
+ if (returnTraits.includes("boolean")) return result.buildAst();
1721
+ const resultAst = result.buildAst();
1709
1722
  const methods = {};
1710
1723
  for (const [resultMethodName, meta] of Object.entries(COMPARISON_METHODS_META)) {
1711
1724
  if (meta.traits.some((t) => !returnTraits.includes(t))) continue;
1712
- methods[resultMethodName] = meta.create(opExpr, entry.returns.codecId);
1725
+ methods[resultMethodName] = meta.create(resultAst, returnCodecId);
1713
1726
  }
1714
1727
  return methods;
1715
1728
  };
@@ -1756,7 +1769,7 @@ function toRelationWhereExpr(context, relatedModelName, predicate) {
1756
1769
  for (const [fieldName, value] of Object.entries(predicate)) {
1757
1770
  if (value === void 0) continue;
1758
1771
  const fieldAccessor = accessor[fieldName];
1759
- if (!fieldAccessor) continue;
1772
+ if (!fieldAccessor) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": field is not defined on the model`);
1760
1773
  if (value === null) {
1761
1774
  if (!fieldAccessor.isNull) throw new Error(`Shorthand filter on "${relatedModelName}.${fieldName}": isNull is unexpectedly missing — this is a bug in trait gating`);
1762
1775
  exprs.push(fieldAccessor.isNull());
@@ -1895,9 +1908,16 @@ async function updateFirstGraph(scope, context, modelName, filters, input) {
1895
1908
  let parentRow = existingRow;
1896
1909
  const mappedUpdateData = mapModelDataToStorageRow(contract, modelName, scalarData);
1897
1910
  if (Object.keys(mappedUpdateData).length > 0) {
1911
+ const tableName = resolveModelTableName(contract, modelName);
1912
+ const appliedUpdateDefaults = context.applyMutationDefaults({
1913
+ op: "update",
1914
+ table: tableName,
1915
+ values: mappedUpdateData
1916
+ });
1917
+ for (const def of appliedUpdateDefaults) mappedUpdateData[def.column] = def.value;
1898
1918
  const pkWhere = shorthandToWhereExpr(context, modelName, buildPrimaryKeyFilterFromRow(contract, modelName, existingRow));
1899
1919
  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];
1920
+ const updatedRaw = (await executeQueryPlan(scope, compileUpdateReturning(contract, tableName, mappedUpdateData, [pkWhere], void 0)).toArray())[0];
1901
1921
  if (updatedRaw) parentRow = mapStorageRowToModelFields(contract, modelName, updatedRaw);
1902
1922
  }
1903
1923
  for (const relationMutation of childOwned) await applyChildOwnedMutation(scope, context, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
@@ -2109,15 +2129,25 @@ function isToWhereExpr(arg) {
2109
2129
  //#endregion
2110
2130
  //#region src/collection.ts
2111
2131
  function applyCreateDefaults(ctx, tableName, rows) {
2132
+ const defaultValueCache = rows.length > 1 ? /* @__PURE__ */ new Map() : void 0;
2112
2133
  for (const row of rows) {
2113
2134
  const applied = ctx.context.applyMutationDefaults({
2114
2135
  op: "create",
2115
2136
  table: tableName,
2116
- values: row
2137
+ values: row,
2138
+ ...defaultValueCache ? { defaultValueCache } : {}
2117
2139
  });
2118
2140
  for (const def of applied) row[def.column] = def.value;
2119
2141
  }
2120
2142
  }
2143
+ function applyUpdateDefaults(ctx, tableName, values) {
2144
+ const applied = ctx.context.applyMutationDefaults({
2145
+ op: "update",
2146
+ table: tableName,
2147
+ values
2148
+ });
2149
+ for (const def of applied) values[def.column] = def.value;
2150
+ }
2121
2151
  function isToWhereExprInput(value) {
2122
2152
  return typeof value === "object" && value !== null && "toWhereExpr" in value && typeof value.toWhereExpr === "function";
2123
2153
  }
@@ -2480,6 +2510,7 @@ var Collection = class Collection {
2480
2510
  applyCreateDefaults(this.ctx, this.tableName, [createValues]);
2481
2511
  const updateValues = mapModelDataToStorageRow(this.contract, this.modelName, input.update);
2482
2512
  const hasUpdateValues = Object.keys(updateValues).length > 0;
2513
+ if (hasUpdateValues) applyUpdateDefaults(this.ctx, this.tableName, updateValues);
2483
2514
  const conflictColumns = resolveUpsertConflictColumns(this.contract, this.modelName, input.conflictOn);
2484
2515
  if (conflictColumns.length === 0) throw new Error(`upsert() for model "${this.modelName}" requires conflict columns`);
2485
2516
  const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
@@ -2526,6 +2557,7 @@ var Collection = class Collection {
2526
2557
  const generator = async function* () {};
2527
2558
  return new AsyncIterableResult(generator());
2528
2559
  }
2560
+ applyUpdateDefaults(this.ctx, this.tableName, mappedData);
2529
2561
  const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
2530
2562
  const { selectedForQuery: selectedForUpdate, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
2531
2563
  const compiled = compileUpdateReturning(this.contract, this.tableName, mappedData, this.state.filters, selectedForUpdate);
@@ -2542,6 +2574,7 @@ var Collection = class Collection {
2542
2574
  async updateCount(data) {
2543
2575
  const mappedData = mapModelDataToStorageRow(this.contract, this.modelName, data);
2544
2576
  if (Object.keys(mappedData).length === 0) return 0;
2577
+ applyUpdateDefaults(this.ctx, this.tableName, mappedData);
2545
2578
  const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.tableName);
2546
2579
  const countState = {
2547
2580
  ...emptyState(),