@atscript/db 0.1.41 → 0.1.43

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.
@@ -399,7 +399,11 @@ declare class TableMetadata {
399
399
  booleanFields: Set<string>;
400
400
  decimalFields: Set<string>;
401
401
  allPhysicalFields: string[];
402
+ /** Precomputed parent path → child physical column names for fast null-setting. */
403
+ childrenByParent: Map<string, string[]>;
402
404
  requiresMappings: boolean;
405
+ /** True when the only mappings needed are simple `@db.column` renames (no nesting/JSON). */
406
+ onlyColumnRenames: boolean;
403
407
  toStorageFormatters?: Map<string, (value: unknown) => unknown>;
404
408
  fromStorageFormatters?: Map<string, (value: unknown) => unknown>;
405
409
  /** Leaf field descriptors indexed by physical column name (read path). */
@@ -958,13 +962,20 @@ declare abstract class FieldMappingStrategy {
958
962
  abstract translateQuery(query: Uniquery, meta: TableMetadata): DbQuery;
959
963
  abstract translateAggregateQuery(query: AggregateQuery, meta: TableMetadata): DbQuery;
960
964
  /**
961
- * Recursively walks a filter expression, applying adapter-specific value
962
- * formatting via `formatFilterValue`. Shared by both document and relational
963
- * mappers (relational adds key-renaming via `translateFilterWithRename`).
965
+ * Recursively walks a filter expression, applying `@db.column` key renames
966
+ * via `columnMap` and adapter-specific value formatting via `formatFilterValue`.
967
+ *
968
+ * The relational mapper overrides this to use `leafByLogical` for deeper
969
+ * key resolution (flattened nested paths).
964
970
  */
965
971
  translateFilter(filter: FilterExpr, meta: TableMetadata): FilterExpr;
966
972
  abstract prepareForWrite(payload: Record<string, unknown>, meta: TableMetadata, adapter: BaseDbAdapter): Record<string, unknown>;
967
973
  abstract translatePatchKeys(update: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
974
+ /**
975
+ * Reverse-maps `@db.column` renames on a row read from storage.
976
+ * Renames physical keys back to logical names in-place.
977
+ */
978
+ protected reverseColumnRenames(row: Record<string, unknown>, meta: TableMetadata): void;
968
979
  /**
969
980
  * Coerces field values from storage representation to JS types
970
981
  * (booleans from 0/1, decimals from number to string).
@@ -399,7 +399,11 @@ declare class TableMetadata {
399
399
  booleanFields: Set<string>;
400
400
  decimalFields: Set<string>;
401
401
  allPhysicalFields: string[];
402
+ /** Precomputed parent path → child physical column names for fast null-setting. */
403
+ childrenByParent: Map<string, string[]>;
402
404
  requiresMappings: boolean;
405
+ /** True when the only mappings needed are simple `@db.column` renames (no nesting/JSON). */
406
+ onlyColumnRenames: boolean;
403
407
  toStorageFormatters?: Map<string, (value: unknown) => unknown>;
404
408
  fromStorageFormatters?: Map<string, (value: unknown) => unknown>;
405
409
  /** Leaf field descriptors indexed by physical column name (read path). */
@@ -958,13 +962,20 @@ declare abstract class FieldMappingStrategy {
958
962
  abstract translateQuery(query: Uniquery, meta: TableMetadata): DbQuery;
959
963
  abstract translateAggregateQuery(query: AggregateQuery, meta: TableMetadata): DbQuery;
960
964
  /**
961
- * Recursively walks a filter expression, applying adapter-specific value
962
- * formatting via `formatFilterValue`. Shared by both document and relational
963
- * mappers (relational adds key-renaming via `translateFilterWithRename`).
965
+ * Recursively walks a filter expression, applying `@db.column` key renames
966
+ * via `columnMap` and adapter-specific value formatting via `formatFilterValue`.
967
+ *
968
+ * The relational mapper overrides this to use `leafByLogical` for deeper
969
+ * key resolution (flattened nested paths).
964
970
  */
965
971
  translateFilter(filter: FilterExpr, meta: TableMetadata): FilterExpr;
966
972
  abstract prepareForWrite(payload: Record<string, unknown>, meta: TableMetadata, adapter: BaseDbAdapter): Record<string, unknown>;
967
973
  abstract translatePatchKeys(update: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
974
+ /**
975
+ * Reverse-maps `@db.column` renames on a row read from storage.
976
+ * Renames physical keys back to logical names in-place.
977
+ */
978
+ protected reverseColumnRenames(row: Record<string, unknown>, meta: TableMetadata): void;
968
979
  /**
969
980
  * Coerces field values from storage representation to JS types
970
981
  * (booleans from 0/1, decimals from number to string).
@@ -1,6 +1,6 @@
1
- import { F as TDbInsertResult, H as TFkLookupResolver, O as TDbDeleteResult, P as TDbInsertManyResult, S as TCascadeResolver, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata, t as AtscriptDbReadable, z as TDbUpdateResult } from "./db-readable-Bbr4CjMb.mjs";
2
- import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
1
+ import { F as TDbInsertResult, H as TFkLookupResolver, O as TDbDeleteResult, P as TDbInsertManyResult, S as TCascadeResolver, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata, t as AtscriptDbReadable, z as TDbUpdateResult } from "./db-readable-C3C-qPcP.cjs";
3
2
  import { FilterExpr } from "@uniqu/core";
3
+ import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/strategies/integrity.d.ts
6
6
  /**
@@ -130,9 +130,17 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
130
130
  */
131
131
  protected _applyDefaults(data: Record<string, unknown>): Record<string, unknown>;
132
132
  /**
133
- * Extracts primary key field(s) from a payload to build a filter.
133
+ * Extracts a record-identifying filter from a payload.
134
+ *
135
+ * Resolution order:
136
+ * 1. Primary key field(s) — if all PK fields are present in the payload.
137
+ * 2. Single-field unique index — first `@db.index.unique` field found.
138
+ * 3. Compound unique index — first compound unique index whose fields are all present.
139
+ *
140
+ * Throws when no identifying fields can be found.
134
141
  */
135
- protected _extractPrimaryKeyFilter(payload: Record<string, unknown>): FilterExpr;
142
+ protected _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
143
+ private _prepareFilterValue;
136
144
  /**
137
145
  * Pre-validate items (type validation + FK constraints) without inserting them.
138
146
  * Used by parent tables to validate FROM children before the main insert,
@@ -1,6 +1,6 @@
1
- import { F as TDbInsertResult, H as TFkLookupResolver, O as TDbDeleteResult, P as TDbInsertManyResult, S as TCascadeResolver, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata, t as AtscriptDbReadable, z as TDbUpdateResult } from "./db-readable-BQQzfguJ.cjs";
2
- import { FilterExpr } from "@uniqu/core";
1
+ import { F as TDbInsertResult, H as TFkLookupResolver, O as TDbDeleteResult, P as TDbInsertManyResult, S as TCascadeResolver, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata, t as AtscriptDbReadable, z as TDbUpdateResult } from "./db-readable-Cc868K54.mjs";
3
2
  import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAtscriptAnnotatedType, TAtscriptDataType, Validator } from "@atscript/typescript/utils";
3
+ import { FilterExpr } from "@uniqu/core";
4
4
 
5
5
  //#region src/strategies/integrity.d.ts
6
6
  /**
@@ -130,9 +130,17 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
130
130
  */
131
131
  protected _applyDefaults(data: Record<string, unknown>): Record<string, unknown>;
132
132
  /**
133
- * Extracts primary key field(s) from a payload to build a filter.
133
+ * Extracts a record-identifying filter from a payload.
134
+ *
135
+ * Resolution order:
136
+ * 1. Primary key field(s) — if all PK fields are present in the payload.
137
+ * 2. Single-field unique index — first `@db.index.unique` field found.
138
+ * 3. Compound unique index — first compound unique index whose fields are all present.
139
+ *
140
+ * Throws when no identifying fields can be found.
134
141
  */
135
- protected _extractPrimaryKeyFilter(payload: Record<string, unknown>): FilterExpr;
142
+ protected _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
143
+ private _prepareFilterValue;
136
144
  /**
137
145
  * Pre-validate items (type validation + FK constraints) without inserting them.
138
146
  * Used by parent tables to validate FROM children before the main insert,
@@ -5,6 +5,8 @@ interface DbValidationContext {
5
5
  mode: "insert" | "replace" | "patch";
6
6
  /** Flat map from the table — used to check if an array is a top-level array. */
7
7
  flatMap?: Map<string, TAtscriptAnnotatedType>;
8
+ /** Precomputed nav field names — used to skip field-op validation inside TO/FROM/VIA relations. */
9
+ navFields?: ReadonlySet<string>;
8
10
  }
9
11
  /**
10
12
  * Validator plugin for database operations.
@@ -5,6 +5,8 @@ interface DbValidationContext {
5
5
  mode: "insert" | "replace" | "patch";
6
6
  /** Flat map from the table — used to check if an array is a top-level array. */
7
7
  flatMap?: Map<string, TAtscriptAnnotatedType>;
8
+ /** Precomputed nav field names — used to skip field-op validation inside TO/FROM/VIA relations. */
9
+ navFields?: ReadonlySet<string>;
8
10
  }
9
11
  /**
10
12
  * Validator plugin for database operations.
@@ -1,4 +1,4 @@
1
- const require_nested_writer = require("./nested-writer-BDXsDMPP.cjs");
1
+ const require_nested_writer = require("./nested-writer-BIQ6EfaR.cjs");
2
2
  const require_agg = require("./agg.cjs");
3
3
  const require_relation_helpers = require("./relation-helpers-BYvsE1tR.cjs");
4
4
  const require_ops = require("./ops.cjs");
@@ -69,7 +69,11 @@ var TableMetadata = class {
69
69
  booleanFields = /* @__PURE__ */ new Set();
70
70
  decimalFields = /* @__PURE__ */ new Set();
71
71
  allPhysicalFields = [];
72
+ /** Precomputed parent path → child physical column names for fast null-setting. */
73
+ childrenByParent = /* @__PURE__ */ new Map();
72
74
  requiresMappings = false;
75
+ /** True when the only mappings needed are simple `@db.column` renames (no nesting/JSON). */
76
+ onlyColumnRenames = false;
73
77
  toStorageFormatters;
74
78
  fromStorageFormatters;
75
79
  /** Leaf field descriptors indexed by physical column name (read path). */
@@ -300,7 +304,8 @@ var TableMetadata = class {
300
304
  for (const [path, physical] of this.pathToPhysical) if (path.startsWith(prefix)) leaves.push(physical);
301
305
  if (leaves.length > 0) this.selectExpansion.set(parentPath, leaves);
302
306
  }
303
- this.requiresMappings = this.flattenedParents.size > 0 || this.jsonFields.size > 0;
307
+ this.onlyColumnRenames = this.columnMap.size > 0 && this.flattenedParents.size === 0 && this.jsonFields.size === 0;
308
+ this.requiresMappings = this.flattenedParents.size > 0 || this.jsonFields.size > 0 || this.onlyColumnRenames;
304
309
  }
305
310
  /** Returns the `__`-separated parent prefix for a dot-separated path, or empty string for top-level paths. */
306
311
  _flattenedPrefix(path) {
@@ -317,6 +322,12 @@ var TableMetadata = class {
317
322
  this.leafByPhysical.set(fd.physicalName, fd);
318
323
  this.leafByLogical.set(fd.path, fd);
319
324
  }
325
+ for (const parentPath of this.flattenedParents) {
326
+ const prefix = `${parentPath}.`;
327
+ const children = [];
328
+ for (const [path, fd] of this.leafByLogical.entries()) if (path.startsWith(prefix)) children.push(fd.physicalName);
329
+ if (children.length > 0) this.childrenByParent.set(parentPath, children);
330
+ }
320
331
  }
321
332
  /**
322
333
  * Builds field descriptors, physical-name lookup, and value formatters.
@@ -540,21 +551,36 @@ function toDecimalString(value) {
540
551
  */
541
552
  var FieldMappingStrategy = class {
542
553
  /**
543
- * Recursively walks a filter expression, applying adapter-specific value
544
- * formatting via `formatFilterValue`. Shared by both document and relational
545
- * mappers (relational adds key-renaming via `translateFilterWithRename`).
554
+ * Recursively walks a filter expression, applying `@db.column` key renames
555
+ * via `columnMap` and adapter-specific value formatting via `formatFilterValue`.
556
+ *
557
+ * The relational mapper overrides this to use `leafByLogical` for deeper
558
+ * key resolution (flattened nested paths).
546
559
  */
547
560
  translateFilter(filter, meta) {
548
561
  if (!filter || typeof filter !== "object") return filter;
549
- if (!meta.toStorageFormatters) return filter;
562
+ if (!meta.toStorageFormatters && meta.columnMap.size === 0) return filter;
550
563
  const result = {};
551
564
  for (const [key, value] of Object.entries(filter)) if (key === "$and" || key === "$or") result[key] = value.map((f) => this.translateFilter(f, meta));
552
565
  else if (key === "$not") result[key] = this.translateFilter(value, meta);
553
566
  else if (key.startsWith("$")) result[key] = value;
554
- else result[key] = this.formatFilterValue(key, value, meta);
567
+ else {
568
+ const physical = meta.columnMap.get(key) ?? key;
569
+ result[physical] = this.formatFilterValue(physical, value, meta);
570
+ }
555
571
  return result;
556
572
  }
557
573
  /**
574
+ * Reverse-maps `@db.column` renames on a row read from storage.
575
+ * Renames physical keys back to logical names in-place.
576
+ */
577
+ reverseColumnRenames(row, meta) {
578
+ for (const [logical, physical] of meta.columnMap.entries()) if (physical in row) {
579
+ row[logical] = row[physical];
580
+ delete row[physical];
581
+ }
582
+ }
583
+ /**
558
584
  * Coerces field values from storage representation to JS types
559
585
  * (booleans from 0/1, decimals from number to string).
560
586
  */
@@ -662,12 +688,15 @@ var FieldMappingStrategy = class {
662
688
  */
663
689
  var DocumentFieldMapper = class extends FieldMappingStrategy {
664
690
  reconstructFromRead(row, meta) {
665
- return this.applyFromStorageFormatters(this.coerceFieldValues(row, meta), meta);
691
+ this.coerceFieldValues(row, meta);
692
+ this.applyFromStorageFormatters(row, meta);
693
+ if (meta.columnMap.size > 0) this.reverseColumnRenames(row, meta);
694
+ return row;
666
695
  }
667
696
  translateQuery(query, meta) {
668
697
  const controls = query.controls;
669
698
  return {
670
- filter: meta.toStorageFormatters ? this.translateFilter(query.filter, meta) : query.filter,
699
+ filter: this.translateFilter(query.filter, meta),
671
700
  controls: {
672
701
  ...controls,
673
702
  $with: void 0,
@@ -679,7 +708,7 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
679
708
  translateAggregateQuery(query, meta) {
680
709
  const controls = query.controls;
681
710
  return {
682
- filter: meta.toStorageFormatters ? this.translateFilter(query.filter, meta) : query.filter ?? {},
711
+ filter: this.translateFilter(query.filter ?? {}, meta),
683
712
  controls: {
684
713
  ...controls,
685
714
  $with: void 0,
@@ -698,6 +727,11 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
698
727
  return this.formatWriteValues(data, meta);
699
728
  }
700
729
  translatePatchKeys(update, meta) {
730
+ if (meta.columnMap.size > 0) {
731
+ const result = {};
732
+ for (const key of Object.keys(update)) result[meta.columnMap.get(key) ?? key] = update[key];
733
+ return this.formatWriteValues(result, meta);
734
+ }
701
735
  return this.formatWriteValues(update, meta);
702
736
  }
703
737
  };
@@ -712,6 +746,12 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
712
746
  var RelationalFieldMapper = class extends FieldMappingStrategy {
713
747
  reconstructFromRead(row, meta) {
714
748
  if (!meta.requiresMappings) return this.applyFromStorageFormatters(this.coerceFieldValues(row, meta), meta);
749
+ if (meta.onlyColumnRenames) {
750
+ this.coerceFieldValues(row, meta);
751
+ this.applyFromStorageFormatters(row, meta);
752
+ this.reverseColumnRenames(row, meta);
753
+ return row;
754
+ }
715
755
  const result = {};
716
756
  const fromFmts = meta.fromStorageFormatters;
717
757
  for (const physical of Object.keys(row)) {
@@ -794,6 +834,15 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
794
834
  };
795
835
  }
796
836
  /**
837
+ * Overrides the base `translateFilter` to use `leafByLogical` for key resolution
838
+ * (handles flattened nested paths like `contact.email` → `contact__email`).
839
+ */
840
+ translateFilter(filter, meta) {
841
+ if (!filter || typeof filter !== "object") return filter;
842
+ if (!meta.requiresMappings && !meta.toStorageFormatters) return filter;
843
+ return this.translateFilterWithRename(filter, meta);
844
+ }
845
+ /**
797
846
  * Translates filter with key renaming from logical to physical names.
798
847
  * Used by the relational query path where field paths must be mapped
799
848
  * to `__`-separated column names.
@@ -813,7 +862,8 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
813
862
  prepareForWrite(payload, meta, adapter) {
814
863
  const data = { ...payload };
815
864
  this.prepareCommon(data, meta, adapter);
816
- if (!meta.requiresMappings) {
865
+ if (!meta.requiresMappings) return this.formatWriteValues(data, meta);
866
+ if (meta.onlyColumnRenames) {
817
867
  for (const [logical, physical] of meta.columnMap.entries()) if (logical in data) {
818
868
  data[physical] = data[logical];
819
869
  delete data[logical];
@@ -824,6 +874,11 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
824
874
  }
825
875
  translatePatchKeys(update, meta) {
826
876
  if (!meta.requiresMappings && !meta.toStorageFormatters) return update;
877
+ if (meta.onlyColumnRenames && !meta.toStorageFormatters) {
878
+ const result = {};
879
+ for (const key of Object.keys(update)) result[meta.leafByLogical.get(key)?.physicalName ?? key] = update[key];
880
+ return result;
881
+ }
827
882
  const result = {};
828
883
  const updateKeys = Object.keys(update);
829
884
  for (const key of updateKeys) {
@@ -921,8 +976,8 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
921
976
  * When a parent object is null/undefined, set all its flattened children to null.
922
977
  */
923
978
  setFlattenedChildrenNull(parentPath, result, meta) {
924
- const prefix = `${parentPath}.`;
925
- for (const [path, fd] of meta.leafByLogical.entries()) if (path.startsWith(prefix)) result[fd.physicalName] = null;
979
+ const children = meta.childrenByParent.get(parentPath);
980
+ if (children) for (const physical of children) result[physical] = null;
926
981
  }
927
982
  };
928
983
  //#endregion
@@ -2108,28 +2163,42 @@ function createDbValidatorPlugin() {
2108
2163
  const isFrom = def.metadata.has("db.rel.from");
2109
2164
  const isVia = def.metadata.has("db.rel.via");
2110
2165
  if (isTo || isFrom || isVia) return handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia);
2111
- if (dbCtx.mode === "patch" && require_ops.isDbFieldOp(value)) {
2112
- if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap)) {
2113
- ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
2114
- return false;
2166
+ if (dbCtx.mode === "patch") {
2167
+ if (require_ops.isDbFieldOp(value)) {
2168
+ if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap, dbCtx.navFields)) {
2169
+ ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
2170
+ return false;
2171
+ }
2172
+ if (!(def.type.kind === "" && def.type.designType === "number")) {
2173
+ ctx.error("Field operations ($inc/$dec/$mul) can only be applied to numeric fields");
2174
+ return false;
2175
+ }
2176
+ return true;
2177
+ }
2178
+ if (def.type.kind === "array" && dbCtx.flatMap) {
2179
+ const flatEntry = dbCtx.flatMap.get(ctx.path);
2180
+ if (flatEntry?.metadata?.has("db.__topLevelArray") && !flatEntry.metadata.has("db.json")) return handleArrayPatch(ctx, def, value);
2115
2181
  }
2116
- return true;
2117
- }
2118
- if (dbCtx.mode === "patch" && def.type.kind === "array" && dbCtx.flatMap) {
2119
- const flatEntry = dbCtx.flatMap.get(ctx.path);
2120
- if (flatEntry?.metadata?.has("db.__topLevelArray") && !flatEntry.metadata.has("db.json")) return handleArrayPatch(ctx, def, value);
2121
2182
  }
2122
2183
  };
2123
2184
  }
2124
2185
  /**
2125
- * Checks whether a field op is valid at `path` by walking up ancestors
2126
- * in the flatMap. Rejects if any ancestor is @db.json or a nested object
2127
- * without @db.patch.strategy "merge".
2186
+ * Checks whether a field op is valid at `path`.
2187
+ *
2188
+ * Rejects when:
2189
+ * - The field itself is `@db.json` (ops on opaque blobs are meaningless).
2190
+ * - Any ancestor is `@db.json`.
2191
+ * - Any ancestor is a nested object without `@db.patch.strategy "merge"`.
2192
+ *
2193
+ * Accepts immediately when an ancestor is a navigation field (TO/FROM/VIA) —
2194
+ * the nested data is extracted and validated against its own table separately.
2128
2195
  */
2129
- function isFieldOpAllowed(path, flatMap) {
2196
+ function isFieldOpAllowed(path, flatMap, navFields) {
2197
+ if (flatMap.get(path)?.metadata.has("db.json")) return false;
2130
2198
  let pos = path.length;
2131
2199
  while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) {
2132
2200
  const ancestor = path.slice(0, pos);
2201
+ if (navFields?.has(ancestor)) return true;
2133
2202
  const entry = flatMap.get(ancestor);
2134
2203
  if (!entry) continue;
2135
2204
  if (entry.metadata.has("db.json")) return false;
@@ -2138,8 +2207,8 @@ function isFieldOpAllowed(path, flatMap) {
2138
2207
  return true;
2139
2208
  }
2140
2209
  function handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia) {
2141
- const pathParts = ctx.path.split(".");
2142
- const fieldName = pathParts[pathParts.length - 1] || ctx.path;
2210
+ const dotIdx = ctx.path.lastIndexOf(".");
2211
+ const fieldName = dotIdx === -1 ? ctx.path : ctx.path.slice(dotIdx + 1);
2143
2212
  if (value === null) {
2144
2213
  ctx.error(`Cannot process null navigation property '${fieldName}'`);
2145
2214
  return false;
@@ -2576,7 +2645,10 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2576
2645
  if (!canNest && this._meta.navFields.size > 0) require_nested_writer.checkDepthOverflow(payloads, maxDepth, this._meta);
2577
2646
  return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2578
2647
  const items = payloads.map((p) => this._applyDefaults({ ...p }));
2579
- require_nested_writer.validateBatch(this.getValidator("insert"), items, { mode: "insert" });
2648
+ require_nested_writer.validateBatch(this.getValidator("insert"), items, {
2649
+ mode: "insert",
2650
+ navFields: this._meta.navFields
2651
+ });
2580
2652
  const host = this;
2581
2653
  if (canNest) await require_nested_writer.batchInsertNestedTo(host, items, maxDepth, depth);
2582
2654
  const prepared = [];
@@ -2616,7 +2688,10 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2616
2688
  return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2617
2689
  const items = payloads.map((p) => this._applyDefaults({ ...p }));
2618
2690
  const originals = canNest ? payloads.map((p) => ({ ...p })) : [];
2619
- require_nested_writer.validateBatch(this.getValidator("bulkReplace"), items, { mode: "replace" });
2691
+ require_nested_writer.validateBatch(this.getValidator("bulkReplace"), items, {
2692
+ mode: "replace",
2693
+ navFields: this._meta.navFields
2694
+ });
2620
2695
  const host = this;
2621
2696
  if (canNest) await require_nested_writer.batchReplaceNestedTo(host, items, maxDepth, depth);
2622
2697
  await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver);
@@ -2625,7 +2700,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2625
2700
  let modifiedCount = 0;
2626
2701
  for (const data of items) {
2627
2702
  for (const navField of this._meta.navFields) delete data[navField];
2628
- const filter = this._extractPrimaryKeyFilter(data);
2703
+ const filter = this._extractRecordFilter(data);
2629
2704
  const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
2630
2705
  const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared);
2631
2706
  matchedCount += result.matchedCount;
@@ -2662,7 +2737,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2662
2737
  return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2663
2738
  require_nested_writer.validateBatch(this.getValidator("bulkUpdate"), payloads, {
2664
2739
  mode: "patch",
2665
- flatMap: this.flatMap
2740
+ flatMap: this.flatMap,
2741
+ navFields: this._meta.navFields
2666
2742
  });
2667
2743
  const originals = canNest ? payloads.map((p) => ({ ...p })) : [];
2668
2744
  const host = this;
@@ -2673,8 +2749,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2673
2749
  for (const payload of payloads) {
2674
2750
  const data = { ...payload };
2675
2751
  for (const navField of this._meta.navFields) delete data[navField];
2676
- const filter = this._extractPrimaryKeyFilter(data);
2677
- for (const pk of this._meta.primaryKeys) delete data[pk];
2752
+ const filter = this._extractRecordFilter(data);
2753
+ for (const key of Object.keys(filter)) delete data[key];
2678
2754
  if (_isEmptyObj(data)) {
2679
2755
  matchedCount += 1;
2680
2756
  modifiedCount += 0;
@@ -2685,7 +2761,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2685
2761
  if (this.adapter.supportsNativePatch()) {
2686
2762
  const ops = require_ops.separateFieldOps(data);
2687
2763
  const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
2688
- result = await this.adapter.nativePatch(translatedFilter, data, translatedOps);
2764
+ const translatedData = this._fieldMapper.translatePatchKeys(data, this._meta);
2765
+ result = await this.adapter.nativePatch(translatedFilter, translatedData, translatedOps);
2689
2766
  } else {
2690
2767
  const update = decomposePatch(data, this);
2691
2768
  const ops = require_ops.separateFieldOps(update);
@@ -2730,10 +2807,11 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2730
2807
  async updateMany(filter, data) {
2731
2808
  this._ensureBuilt();
2732
2809
  await this._integrity.validateForeignKeys([data], this._meta, this._fkLookupResolver, this._writeTableResolver, true);
2733
- const dataCopy = { ...data };
2734
- const ops = require_ops.separateFieldOps(dataCopy);
2810
+ const update = decomposePatch({ ...data }, this);
2811
+ const ops = require_ops.separateFieldOps(update);
2735
2812
  const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
2736
- return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.updateMany(this._fieldMapper.translateFilter(filter, this._meta), this._fieldMapper.prepareForWrite(dataCopy, this._meta, this.adapter), translatedOps));
2813
+ const translatedUpdate = this._fieldMapper.translatePatchKeys(update, this._meta);
2814
+ return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.updateMany(this._fieldMapper.translateFilter(filter, this._meta), translatedUpdate, translatedOps));
2737
2815
  }
2738
2816
  async replaceMany(filter, data) {
2739
2817
  this._ensureBuilt();
@@ -2786,24 +2864,55 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2786
2864
  return data;
2787
2865
  }
2788
2866
  /**
2789
- * Extracts primary key field(s) from a payload to build a filter.
2867
+ * Extracts a record-identifying filter from a payload.
2868
+ *
2869
+ * Resolution order:
2870
+ * 1. Primary key field(s) — if all PK fields are present in the payload.
2871
+ * 2. Single-field unique index — first `@db.index.unique` field found.
2872
+ * 3. Compound unique index — first compound unique index whose fields are all present.
2873
+ *
2874
+ * Throws when no identifying fields can be found.
2790
2875
  */
2791
- _extractPrimaryKeyFilter(payload) {
2876
+ _extractRecordFilter(payload) {
2792
2877
  const pkFields = this.primaryKeys;
2878
+ if (pkFields.length > 0) {
2879
+ let allPresent = true;
2880
+ for (const field of pkFields) if (payload[field] === void 0) {
2881
+ allPresent = false;
2882
+ break;
2883
+ }
2884
+ if (allPresent) {
2885
+ const filter = {};
2886
+ for (const field of pkFields) filter[field] = this._prepareFilterValue(field, payload[field]);
2887
+ return filter;
2888
+ }
2889
+ }
2890
+ for (const prop of this.uniqueProps) if (payload[prop] !== void 0) return { [prop]: this._prepareFilterValue(prop, payload[prop]) };
2891
+ for (const index of this._meta.indexes.values()) {
2892
+ if (index.type !== "unique" || index.fields.length < 2) continue;
2893
+ let allPresent = true;
2894
+ for (const indexField of index.fields) if (payload[indexField.name] === void 0) {
2895
+ allPresent = false;
2896
+ break;
2897
+ }
2898
+ if (allPresent) {
2899
+ const filter = {};
2900
+ for (const indexField of index.fields) filter[indexField.name] = this._prepareFilterValue(indexField.name, payload[indexField.name]);
2901
+ return filter;
2902
+ }
2903
+ }
2793
2904
  if (pkFields.length === 0) throw new require_nested_writer.DbError("NOT_FOUND", [{
2794
2905
  path: "",
2795
2906
  message: "No primary key defined — cannot extract filter"
2796
2907
  }]);
2797
- const filter = {};
2798
- for (const field of pkFields) {
2799
- if (payload[field] === void 0) throw new require_nested_writer.DbError("NOT_FOUND", [{
2800
- path: field,
2801
- message: `Missing primary key field "${field}" in payload`
2802
- }]);
2803
- const fieldType = this.flatMap.get(field);
2804
- filter[field] = fieldType ? this.adapter.prepareId(payload[field], fieldType) : payload[field];
2805
- }
2806
- return filter;
2908
+ throw new require_nested_writer.DbError("NOT_FOUND", [{
2909
+ path: pkFields[0],
2910
+ message: `Missing primary key field "${pkFields[0]}" in payload`
2911
+ }]);
2912
+ }
2913
+ _prepareFilterValue(field, value) {
2914
+ const fieldType = this.flatMap.get(field);
2915
+ return fieldType ? this.adapter.prepareId(value, fieldType) : value;
2807
2916
  }
2808
2917
  /**
2809
2918
  * Pre-validate items (type validation + FK constraints) without inserting them.
@@ -2814,7 +2923,12 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2814
2923
  */
2815
2924
  async preValidateItems(items, opts) {
2816
2925
  this._ensureBuilt();
2817
- require_nested_writer.validateBatch(this.getValidator("insert"), items.map((raw) => this._applyDefaults({ ...raw })), { mode: "insert" });
2926
+ const validator = this.getValidator("insert");
2927
+ const ctx = {
2928
+ mode: "insert",
2929
+ navFields: this._meta.navFields
2930
+ };
2931
+ require_nested_writer.validateBatch(validator, items.map((raw) => this._applyDefaults({ ...raw })), ctx);
2818
2932
  await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver, false, opts?.excludeFkTargetTable);
2819
2933
  }
2820
2934
  /**
@@ -1,4 +1,4 @@
1
- import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, h as DbError, i as batchPatchNestedFrom, l as batchReplaceNestedVia, m as remapDeleteFkViolation, n as batchInsertNestedTo, o as batchPatchNestedVia, p as enrichFkViolation, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-Dmm1gbZV.mjs";
1
+ import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, h as DbError, i as batchPatchNestedFrom, l as batchReplaceNestedVia, m as remapDeleteFkViolation, n as batchInsertNestedTo, o as batchPatchNestedVia, p as enrichFkViolation, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-CNDyhg2L.mjs";
2
2
  import { resolveAlias } from "./agg.mjs";
3
3
  import { n as findRemoteFK, t as findFKForRelation } from "./relation-helpers-CLasawQq.mjs";
4
4
  import { isDbFieldOp, separateFieldOps } from "./ops.mjs";
@@ -69,7 +69,11 @@ var TableMetadata = class {
69
69
  booleanFields = /* @__PURE__ */ new Set();
70
70
  decimalFields = /* @__PURE__ */ new Set();
71
71
  allPhysicalFields = [];
72
+ /** Precomputed parent path → child physical column names for fast null-setting. */
73
+ childrenByParent = /* @__PURE__ */ new Map();
72
74
  requiresMappings = false;
75
+ /** True when the only mappings needed are simple `@db.column` renames (no nesting/JSON). */
76
+ onlyColumnRenames = false;
73
77
  toStorageFormatters;
74
78
  fromStorageFormatters;
75
79
  /** Leaf field descriptors indexed by physical column name (read path). */
@@ -300,7 +304,8 @@ var TableMetadata = class {
300
304
  for (const [path, physical] of this.pathToPhysical) if (path.startsWith(prefix)) leaves.push(physical);
301
305
  if (leaves.length > 0) this.selectExpansion.set(parentPath, leaves);
302
306
  }
303
- this.requiresMappings = this.flattenedParents.size > 0 || this.jsonFields.size > 0;
307
+ this.onlyColumnRenames = this.columnMap.size > 0 && this.flattenedParents.size === 0 && this.jsonFields.size === 0;
308
+ this.requiresMappings = this.flattenedParents.size > 0 || this.jsonFields.size > 0 || this.onlyColumnRenames;
304
309
  }
305
310
  /** Returns the `__`-separated parent prefix for a dot-separated path, or empty string for top-level paths. */
306
311
  _flattenedPrefix(path) {
@@ -317,6 +322,12 @@ var TableMetadata = class {
317
322
  this.leafByPhysical.set(fd.physicalName, fd);
318
323
  this.leafByLogical.set(fd.path, fd);
319
324
  }
325
+ for (const parentPath of this.flattenedParents) {
326
+ const prefix = `${parentPath}.`;
327
+ const children = [];
328
+ for (const [path, fd] of this.leafByLogical.entries()) if (path.startsWith(prefix)) children.push(fd.physicalName);
329
+ if (children.length > 0) this.childrenByParent.set(parentPath, children);
330
+ }
320
331
  }
321
332
  /**
322
333
  * Builds field descriptors, physical-name lookup, and value formatters.
@@ -540,21 +551,36 @@ function toDecimalString(value) {
540
551
  */
541
552
  var FieldMappingStrategy = class {
542
553
  /**
543
- * Recursively walks a filter expression, applying adapter-specific value
544
- * formatting via `formatFilterValue`. Shared by both document and relational
545
- * mappers (relational adds key-renaming via `translateFilterWithRename`).
554
+ * Recursively walks a filter expression, applying `@db.column` key renames
555
+ * via `columnMap` and adapter-specific value formatting via `formatFilterValue`.
556
+ *
557
+ * The relational mapper overrides this to use `leafByLogical` for deeper
558
+ * key resolution (flattened nested paths).
546
559
  */
547
560
  translateFilter(filter, meta) {
548
561
  if (!filter || typeof filter !== "object") return filter;
549
- if (!meta.toStorageFormatters) return filter;
562
+ if (!meta.toStorageFormatters && meta.columnMap.size === 0) return filter;
550
563
  const result = {};
551
564
  for (const [key, value] of Object.entries(filter)) if (key === "$and" || key === "$or") result[key] = value.map((f) => this.translateFilter(f, meta));
552
565
  else if (key === "$not") result[key] = this.translateFilter(value, meta);
553
566
  else if (key.startsWith("$")) result[key] = value;
554
- else result[key] = this.formatFilterValue(key, value, meta);
567
+ else {
568
+ const physical = meta.columnMap.get(key) ?? key;
569
+ result[physical] = this.formatFilterValue(physical, value, meta);
570
+ }
555
571
  return result;
556
572
  }
557
573
  /**
574
+ * Reverse-maps `@db.column` renames on a row read from storage.
575
+ * Renames physical keys back to logical names in-place.
576
+ */
577
+ reverseColumnRenames(row, meta) {
578
+ for (const [logical, physical] of meta.columnMap.entries()) if (physical in row) {
579
+ row[logical] = row[physical];
580
+ delete row[physical];
581
+ }
582
+ }
583
+ /**
558
584
  * Coerces field values from storage representation to JS types
559
585
  * (booleans from 0/1, decimals from number to string).
560
586
  */
@@ -662,12 +688,15 @@ var FieldMappingStrategy = class {
662
688
  */
663
689
  var DocumentFieldMapper = class extends FieldMappingStrategy {
664
690
  reconstructFromRead(row, meta) {
665
- return this.applyFromStorageFormatters(this.coerceFieldValues(row, meta), meta);
691
+ this.coerceFieldValues(row, meta);
692
+ this.applyFromStorageFormatters(row, meta);
693
+ if (meta.columnMap.size > 0) this.reverseColumnRenames(row, meta);
694
+ return row;
666
695
  }
667
696
  translateQuery(query, meta) {
668
697
  const controls = query.controls;
669
698
  return {
670
- filter: meta.toStorageFormatters ? this.translateFilter(query.filter, meta) : query.filter,
699
+ filter: this.translateFilter(query.filter, meta),
671
700
  controls: {
672
701
  ...controls,
673
702
  $with: void 0,
@@ -679,7 +708,7 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
679
708
  translateAggregateQuery(query, meta) {
680
709
  const controls = query.controls;
681
710
  return {
682
- filter: meta.toStorageFormatters ? this.translateFilter(query.filter, meta) : query.filter ?? {},
711
+ filter: this.translateFilter(query.filter ?? {}, meta),
683
712
  controls: {
684
713
  ...controls,
685
714
  $with: void 0,
@@ -698,6 +727,11 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
698
727
  return this.formatWriteValues(data, meta);
699
728
  }
700
729
  translatePatchKeys(update, meta) {
730
+ if (meta.columnMap.size > 0) {
731
+ const result = {};
732
+ for (const key of Object.keys(update)) result[meta.columnMap.get(key) ?? key] = update[key];
733
+ return this.formatWriteValues(result, meta);
734
+ }
701
735
  return this.formatWriteValues(update, meta);
702
736
  }
703
737
  };
@@ -712,6 +746,12 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
712
746
  var RelationalFieldMapper = class extends FieldMappingStrategy {
713
747
  reconstructFromRead(row, meta) {
714
748
  if (!meta.requiresMappings) return this.applyFromStorageFormatters(this.coerceFieldValues(row, meta), meta);
749
+ if (meta.onlyColumnRenames) {
750
+ this.coerceFieldValues(row, meta);
751
+ this.applyFromStorageFormatters(row, meta);
752
+ this.reverseColumnRenames(row, meta);
753
+ return row;
754
+ }
715
755
  const result = {};
716
756
  const fromFmts = meta.fromStorageFormatters;
717
757
  for (const physical of Object.keys(row)) {
@@ -794,6 +834,15 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
794
834
  };
795
835
  }
796
836
  /**
837
+ * Overrides the base `translateFilter` to use `leafByLogical` for key resolution
838
+ * (handles flattened nested paths like `contact.email` → `contact__email`).
839
+ */
840
+ translateFilter(filter, meta) {
841
+ if (!filter || typeof filter !== "object") return filter;
842
+ if (!meta.requiresMappings && !meta.toStorageFormatters) return filter;
843
+ return this.translateFilterWithRename(filter, meta);
844
+ }
845
+ /**
797
846
  * Translates filter with key renaming from logical to physical names.
798
847
  * Used by the relational query path where field paths must be mapped
799
848
  * to `__`-separated column names.
@@ -813,7 +862,8 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
813
862
  prepareForWrite(payload, meta, adapter) {
814
863
  const data = { ...payload };
815
864
  this.prepareCommon(data, meta, adapter);
816
- if (!meta.requiresMappings) {
865
+ if (!meta.requiresMappings) return this.formatWriteValues(data, meta);
866
+ if (meta.onlyColumnRenames) {
817
867
  for (const [logical, physical] of meta.columnMap.entries()) if (logical in data) {
818
868
  data[physical] = data[logical];
819
869
  delete data[logical];
@@ -824,6 +874,11 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
824
874
  }
825
875
  translatePatchKeys(update, meta) {
826
876
  if (!meta.requiresMappings && !meta.toStorageFormatters) return update;
877
+ if (meta.onlyColumnRenames && !meta.toStorageFormatters) {
878
+ const result = {};
879
+ for (const key of Object.keys(update)) result[meta.leafByLogical.get(key)?.physicalName ?? key] = update[key];
880
+ return result;
881
+ }
827
882
  const result = {};
828
883
  const updateKeys = Object.keys(update);
829
884
  for (const key of updateKeys) {
@@ -921,8 +976,8 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
921
976
  * When a parent object is null/undefined, set all its flattened children to null.
922
977
  */
923
978
  setFlattenedChildrenNull(parentPath, result, meta) {
924
- const prefix = `${parentPath}.`;
925
- for (const [path, fd] of meta.leafByLogical.entries()) if (path.startsWith(prefix)) result[fd.physicalName] = null;
979
+ const children = meta.childrenByParent.get(parentPath);
980
+ if (children) for (const physical of children) result[physical] = null;
926
981
  }
927
982
  };
928
983
  //#endregion
@@ -2108,28 +2163,42 @@ function createDbValidatorPlugin() {
2108
2163
  const isFrom = def.metadata.has("db.rel.from");
2109
2164
  const isVia = def.metadata.has("db.rel.via");
2110
2165
  if (isTo || isFrom || isVia) return handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia);
2111
- if (dbCtx.mode === "patch" && isDbFieldOp(value)) {
2112
- if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap)) {
2113
- ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
2114
- return false;
2166
+ if (dbCtx.mode === "patch") {
2167
+ if (isDbFieldOp(value)) {
2168
+ if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap, dbCtx.navFields)) {
2169
+ ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
2170
+ return false;
2171
+ }
2172
+ if (!(def.type.kind === "" && def.type.designType === "number")) {
2173
+ ctx.error("Field operations ($inc/$dec/$mul) can only be applied to numeric fields");
2174
+ return false;
2175
+ }
2176
+ return true;
2177
+ }
2178
+ if (def.type.kind === "array" && dbCtx.flatMap) {
2179
+ const flatEntry = dbCtx.flatMap.get(ctx.path);
2180
+ if (flatEntry?.metadata?.has("db.__topLevelArray") && !flatEntry.metadata.has("db.json")) return handleArrayPatch(ctx, def, value);
2115
2181
  }
2116
- return true;
2117
- }
2118
- if (dbCtx.mode === "patch" && def.type.kind === "array" && dbCtx.flatMap) {
2119
- const flatEntry = dbCtx.flatMap.get(ctx.path);
2120
- if (flatEntry?.metadata?.has("db.__topLevelArray") && !flatEntry.metadata.has("db.json")) return handleArrayPatch(ctx, def, value);
2121
2182
  }
2122
2183
  };
2123
2184
  }
2124
2185
  /**
2125
- * Checks whether a field op is valid at `path` by walking up ancestors
2126
- * in the flatMap. Rejects if any ancestor is @db.json or a nested object
2127
- * without @db.patch.strategy "merge".
2186
+ * Checks whether a field op is valid at `path`.
2187
+ *
2188
+ * Rejects when:
2189
+ * - The field itself is `@db.json` (ops on opaque blobs are meaningless).
2190
+ * - Any ancestor is `@db.json`.
2191
+ * - Any ancestor is a nested object without `@db.patch.strategy "merge"`.
2192
+ *
2193
+ * Accepts immediately when an ancestor is a navigation field (TO/FROM/VIA) —
2194
+ * the nested data is extracted and validated against its own table separately.
2128
2195
  */
2129
- function isFieldOpAllowed(path, flatMap) {
2196
+ function isFieldOpAllowed(path, flatMap, navFields) {
2197
+ if (flatMap.get(path)?.metadata.has("db.json")) return false;
2130
2198
  let pos = path.length;
2131
2199
  while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) {
2132
2200
  const ancestor = path.slice(0, pos);
2201
+ if (navFields?.has(ancestor)) return true;
2133
2202
  const entry = flatMap.get(ancestor);
2134
2203
  if (!entry) continue;
2135
2204
  if (entry.metadata.has("db.json")) return false;
@@ -2138,8 +2207,8 @@ function isFieldOpAllowed(path, flatMap) {
2138
2207
  return true;
2139
2208
  }
2140
2209
  function handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia) {
2141
- const pathParts = ctx.path.split(".");
2142
- const fieldName = pathParts[pathParts.length - 1] || ctx.path;
2210
+ const dotIdx = ctx.path.lastIndexOf(".");
2211
+ const fieldName = dotIdx === -1 ? ctx.path : ctx.path.slice(dotIdx + 1);
2143
2212
  if (value === null) {
2144
2213
  ctx.error(`Cannot process null navigation property '${fieldName}'`);
2145
2214
  return false;
@@ -2576,7 +2645,10 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2576
2645
  if (!canNest && this._meta.navFields.size > 0) checkDepthOverflow(payloads, maxDepth, this._meta);
2577
2646
  return enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2578
2647
  const items = payloads.map((p) => this._applyDefaults({ ...p }));
2579
- validateBatch(this.getValidator("insert"), items, { mode: "insert" });
2648
+ validateBatch(this.getValidator("insert"), items, {
2649
+ mode: "insert",
2650
+ navFields: this._meta.navFields
2651
+ });
2580
2652
  const host = this;
2581
2653
  if (canNest) await batchInsertNestedTo(host, items, maxDepth, depth);
2582
2654
  const prepared = [];
@@ -2616,7 +2688,10 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2616
2688
  return enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2617
2689
  const items = payloads.map((p) => this._applyDefaults({ ...p }));
2618
2690
  const originals = canNest ? payloads.map((p) => ({ ...p })) : [];
2619
- validateBatch(this.getValidator("bulkReplace"), items, { mode: "replace" });
2691
+ validateBatch(this.getValidator("bulkReplace"), items, {
2692
+ mode: "replace",
2693
+ navFields: this._meta.navFields
2694
+ });
2620
2695
  const host = this;
2621
2696
  if (canNest) await batchReplaceNestedTo(host, items, maxDepth, depth);
2622
2697
  await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver);
@@ -2625,7 +2700,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2625
2700
  let modifiedCount = 0;
2626
2701
  for (const data of items) {
2627
2702
  for (const navField of this._meta.navFields) delete data[navField];
2628
- const filter = this._extractPrimaryKeyFilter(data);
2703
+ const filter = this._extractRecordFilter(data);
2629
2704
  const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
2630
2705
  const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared);
2631
2706
  matchedCount += result.matchedCount;
@@ -2662,7 +2737,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2662
2737
  return enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2663
2738
  validateBatch(this.getValidator("bulkUpdate"), payloads, {
2664
2739
  mode: "patch",
2665
- flatMap: this.flatMap
2740
+ flatMap: this.flatMap,
2741
+ navFields: this._meta.navFields
2666
2742
  });
2667
2743
  const originals = canNest ? payloads.map((p) => ({ ...p })) : [];
2668
2744
  const host = this;
@@ -2673,8 +2749,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2673
2749
  for (const payload of payloads) {
2674
2750
  const data = { ...payload };
2675
2751
  for (const navField of this._meta.navFields) delete data[navField];
2676
- const filter = this._extractPrimaryKeyFilter(data);
2677
- for (const pk of this._meta.primaryKeys) delete data[pk];
2752
+ const filter = this._extractRecordFilter(data);
2753
+ for (const key of Object.keys(filter)) delete data[key];
2678
2754
  if (_isEmptyObj(data)) {
2679
2755
  matchedCount += 1;
2680
2756
  modifiedCount += 0;
@@ -2685,7 +2761,8 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2685
2761
  if (this.adapter.supportsNativePatch()) {
2686
2762
  const ops = separateFieldOps(data);
2687
2763
  const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
2688
- result = await this.adapter.nativePatch(translatedFilter, data, translatedOps);
2764
+ const translatedData = this._fieldMapper.translatePatchKeys(data, this._meta);
2765
+ result = await this.adapter.nativePatch(translatedFilter, translatedData, translatedOps);
2689
2766
  } else {
2690
2767
  const update = decomposePatch(data, this);
2691
2768
  const ops = separateFieldOps(update);
@@ -2730,10 +2807,11 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2730
2807
  async updateMany(filter, data) {
2731
2808
  this._ensureBuilt();
2732
2809
  await this._integrity.validateForeignKeys([data], this._meta, this._fkLookupResolver, this._writeTableResolver, true);
2733
- const dataCopy = { ...data };
2734
- const ops = separateFieldOps(dataCopy);
2810
+ const update = decomposePatch({ ...data }, this);
2811
+ const ops = separateFieldOps(update);
2735
2812
  const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
2736
- return enrichFkViolation(this._meta, () => this.adapter.updateMany(this._fieldMapper.translateFilter(filter, this._meta), this._fieldMapper.prepareForWrite(dataCopy, this._meta, this.adapter), translatedOps));
2813
+ const translatedUpdate = this._fieldMapper.translatePatchKeys(update, this._meta);
2814
+ return enrichFkViolation(this._meta, () => this.adapter.updateMany(this._fieldMapper.translateFilter(filter, this._meta), translatedUpdate, translatedOps));
2737
2815
  }
2738
2816
  async replaceMany(filter, data) {
2739
2817
  this._ensureBuilt();
@@ -2786,24 +2864,55 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2786
2864
  return data;
2787
2865
  }
2788
2866
  /**
2789
- * Extracts primary key field(s) from a payload to build a filter.
2867
+ * Extracts a record-identifying filter from a payload.
2868
+ *
2869
+ * Resolution order:
2870
+ * 1. Primary key field(s) — if all PK fields are present in the payload.
2871
+ * 2. Single-field unique index — first `@db.index.unique` field found.
2872
+ * 3. Compound unique index — first compound unique index whose fields are all present.
2873
+ *
2874
+ * Throws when no identifying fields can be found.
2790
2875
  */
2791
- _extractPrimaryKeyFilter(payload) {
2876
+ _extractRecordFilter(payload) {
2792
2877
  const pkFields = this.primaryKeys;
2878
+ if (pkFields.length > 0) {
2879
+ let allPresent = true;
2880
+ for (const field of pkFields) if (payload[field] === void 0) {
2881
+ allPresent = false;
2882
+ break;
2883
+ }
2884
+ if (allPresent) {
2885
+ const filter = {};
2886
+ for (const field of pkFields) filter[field] = this._prepareFilterValue(field, payload[field]);
2887
+ return filter;
2888
+ }
2889
+ }
2890
+ for (const prop of this.uniqueProps) if (payload[prop] !== void 0) return { [prop]: this._prepareFilterValue(prop, payload[prop]) };
2891
+ for (const index of this._meta.indexes.values()) {
2892
+ if (index.type !== "unique" || index.fields.length < 2) continue;
2893
+ let allPresent = true;
2894
+ for (const indexField of index.fields) if (payload[indexField.name] === void 0) {
2895
+ allPresent = false;
2896
+ break;
2897
+ }
2898
+ if (allPresent) {
2899
+ const filter = {};
2900
+ for (const indexField of index.fields) filter[indexField.name] = this._prepareFilterValue(indexField.name, payload[indexField.name]);
2901
+ return filter;
2902
+ }
2903
+ }
2793
2904
  if (pkFields.length === 0) throw new DbError("NOT_FOUND", [{
2794
2905
  path: "",
2795
2906
  message: "No primary key defined — cannot extract filter"
2796
2907
  }]);
2797
- const filter = {};
2798
- for (const field of pkFields) {
2799
- if (payload[field] === void 0) throw new DbError("NOT_FOUND", [{
2800
- path: field,
2801
- message: `Missing primary key field "${field}" in payload`
2802
- }]);
2803
- const fieldType = this.flatMap.get(field);
2804
- filter[field] = fieldType ? this.adapter.prepareId(payload[field], fieldType) : payload[field];
2805
- }
2806
- return filter;
2908
+ throw new DbError("NOT_FOUND", [{
2909
+ path: pkFields[0],
2910
+ message: `Missing primary key field "${pkFields[0]}" in payload`
2911
+ }]);
2912
+ }
2913
+ _prepareFilterValue(field, value) {
2914
+ const fieldType = this.flatMap.get(field);
2915
+ return fieldType ? this.adapter.prepareId(value, fieldType) : value;
2807
2916
  }
2808
2917
  /**
2809
2918
  * Pre-validate items (type validation + FK constraints) without inserting them.
@@ -2814,7 +2923,12 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
2814
2923
  */
2815
2924
  async preValidateItems(items, opts) {
2816
2925
  this._ensureBuilt();
2817
- validateBatch(this.getValidator("insert"), items.map((raw) => this._applyDefaults({ ...raw })), { mode: "insert" });
2926
+ const validator = this.getValidator("insert");
2927
+ const ctx = {
2928
+ mode: "insert",
2929
+ navFields: this._meta.navFields
2930
+ };
2931
+ validateBatch(validator, items.map((raw) => this._applyDefaults({ ...raw })), ctx);
2818
2932
  await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver, false, opts?.excludeFkTargetTable);
2819
2933
  }
2820
2934
  /**
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_nested_writer = require("./nested-writer-BDXsDMPP.cjs");
3
- const require_db_view = require("./db-view-CMI9TOo1.cjs");
2
+ const require_nested_writer = require("./nested-writer-BIQ6EfaR.cjs");
3
+ const require_db_view = require("./db-view-CcUjETIF.cjs");
4
4
  const require_ops = require("./ops.cjs");
5
5
  let _uniqu_core = require("@uniqu/core");
6
6
  //#region src/table/db-space.ts
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { $ as Uniquery, A as TDbForeignKey, B as TExistingColumn, C as TCascadeTarget, D as TDbDefaultValue, E as TDbDefaultFn, F as TDbInsertResult, G as TMetadataOverrides, H as TFkLookupResolver, I as TDbReferentialAction, J as TTableOptionDiff, K as TSearchIndexInfo, L as TDbRelation, M as TDbIndexField, N as TDbIndexType, O as TDbDeleteResult, P as TDbInsertManyResult, Q as TypedWithRelation, R as TDbStorageType, S as TCascadeResolver, T as TDbCollation, U as TFkLookupTarget, V as TExistingTableOption, W as TIdDescriptor, X as TValueFormatterPair, Y as TTableResolver, Z as TWriteTableResolver, _ as DbQuery, a as FieldMappingStrategy, b as NavPropsOf, c as NoopLogger, d as AggregateExpr, et as UniqueryControls, f as AggregateFn, g as DbControls, h as AtscriptDbWritable, i as DocumentFieldMapper, j as TDbIndex, k as TDbFieldMeta, l as TGenericLogger, m as AggregateResult, n as DbResponse, nt as UniquSelect, o as BaseDbAdapter, p as AggregateQuery, q as TSyncColumnResult, r as resolveDesignType, s as TableMetadata, t as AtscriptDbReadable, tt as WithRelation, u as AggregateControls, v as FieldOpsFor, w as TColumnDiff, x as OwnPropsOf, y as FilterExpr, z as TDbUpdateResult } from "./db-readable-BQQzfguJ.cjs";
1
+ import { $ as Uniquery, A as TDbForeignKey, B as TExistingColumn, C as TCascadeTarget, D as TDbDefaultValue, E as TDbDefaultFn, F as TDbInsertResult, G as TMetadataOverrides, H as TFkLookupResolver, I as TDbReferentialAction, J as TTableOptionDiff, K as TSearchIndexInfo, L as TDbRelation, M as TDbIndexField, N as TDbIndexType, O as TDbDeleteResult, P as TDbInsertManyResult, Q as TypedWithRelation, R as TDbStorageType, S as TCascadeResolver, T as TDbCollation, U as TFkLookupTarget, V as TExistingTableOption, W as TIdDescriptor, X as TValueFormatterPair, Y as TTableResolver, Z as TWriteTableResolver, _ as DbQuery, a as FieldMappingStrategy, b as NavPropsOf, c as NoopLogger, d as AggregateExpr, et as UniqueryControls, f as AggregateFn, g as DbControls, h as AtscriptDbWritable, i as DocumentFieldMapper, j as TDbIndex, k as TDbFieldMeta, l as TGenericLogger, m as AggregateResult, n as DbResponse, nt as UniquSelect, o as BaseDbAdapter, p as AggregateQuery, q as TSyncColumnResult, r as resolveDesignType, s as TableMetadata, t as AtscriptDbReadable, tt as WithRelation, u as AggregateControls, v as FieldOpsFor, w as TColumnDiff, x as OwnPropsOf, y as FilterExpr, z as TDbUpdateResult } from "./db-readable-C3C-qPcP.cjs";
2
2
  import { d as getDbFieldOp, f as isDbFieldOp, l as TDbFieldOp, p as separateFieldOps, u as TFieldOps } from "./ops-DXJ4Zw0P.cjs";
3
- import { a as AtscriptQueryComparison, c as AtscriptRef, d as translateQueryTree, f as AtscriptDbTable, i as TViewColumnMapping, l as TViewJoin, m as NativeIntegrity, n as TAdapterFactory, o as AtscriptQueryFieldRef, p as IntegrityStrategy, r as AtscriptDbView, s as AtscriptQueryNode, t as DbSpace, u as TViewPlan } from "./db-space-Vxpcnyt5.cjs";
4
- import { n as createDbValidatorPlugin, t as DbValidationContext } from "./db-validator-plugin-07kDiis2.cjs";
3
+ import { a as AtscriptQueryComparison, c as AtscriptRef, d as translateQueryTree, f as AtscriptDbTable, i as TViewColumnMapping, l as TViewJoin, m as NativeIntegrity, n as TAdapterFactory, o as AtscriptQueryFieldRef, p as IntegrityStrategy, r as AtscriptDbView, s as AtscriptQueryNode, t as DbSpace, u as TViewPlan } from "./db-space-Dgf-CphX.cjs";
4
+ import { n as createDbValidatorPlugin, t as DbValidationContext } from "./db-validator-plugin-Cz4QoDWg.cjs";
5
5
  import { AggregateQuery as AggregateQuery$1, FilterExpr as FilterExpr$1, FilterVisitor, Uniquery as Uniquery$1, computeInsights, isPrimitive, walkFilter } from "@uniqu/core";
6
6
  import { TAtscriptAnnotatedType, TAtscriptTypeArray } from "@atscript/typescript/utils";
7
7
 
@@ -16,6 +16,11 @@ declare class RelationalFieldMapper extends FieldMappingStrategy {
16
16
  reconstructFromRead(row: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
17
17
  translateQuery(query: Uniquery$1, meta: TableMetadata): DbQuery;
18
18
  translateAggregateQuery(query: AggregateQuery$1, meta: TableMetadata): DbQuery;
19
+ /**
20
+ * Overrides the base `translateFilter` to use `leafByLogical` for key resolution
21
+ * (handles flattened nested paths like `contact.email` → `contact__email`).
22
+ */
23
+ translateFilter(filter: FilterExpr$1, meta: TableMetadata): FilterExpr$1;
19
24
  /**
20
25
  * Translates filter with key renaming from logical to physical names.
21
26
  * Used by the relational query path where field paths must be mapped
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { $ as Uniquery, A as TDbForeignKey, B as TExistingColumn, C as TCascadeTarget, D as TDbDefaultValue, E as TDbDefaultFn, F as TDbInsertResult, G as TMetadataOverrides, H as TFkLookupResolver, I as TDbReferentialAction, J as TTableOptionDiff, K as TSearchIndexInfo, L as TDbRelation, M as TDbIndexField, N as TDbIndexType, O as TDbDeleteResult, P as TDbInsertManyResult, Q as TypedWithRelation, R as TDbStorageType, S as TCascadeResolver, T as TDbCollation, U as TFkLookupTarget, V as TExistingTableOption, W as TIdDescriptor, X as TValueFormatterPair, Y as TTableResolver, Z as TWriteTableResolver, _ as DbQuery, a as FieldMappingStrategy, b as NavPropsOf, c as NoopLogger, d as AggregateExpr, et as UniqueryControls, f as AggregateFn, g as DbControls, h as AtscriptDbWritable, i as DocumentFieldMapper, j as TDbIndex, k as TDbFieldMeta, l as TGenericLogger, m as AggregateResult, n as DbResponse, nt as UniquSelect, o as BaseDbAdapter, p as AggregateQuery, q as TSyncColumnResult, r as resolveDesignType, s as TableMetadata, t as AtscriptDbReadable, tt as WithRelation, u as AggregateControls, v as FieldOpsFor, w as TColumnDiff, x as OwnPropsOf, y as FilterExpr, z as TDbUpdateResult } from "./db-readable-Bbr4CjMb.mjs";
1
+ import { $ as Uniquery, A as TDbForeignKey, B as TExistingColumn, C as TCascadeTarget, D as TDbDefaultValue, E as TDbDefaultFn, F as TDbInsertResult, G as TMetadataOverrides, H as TFkLookupResolver, I as TDbReferentialAction, J as TTableOptionDiff, K as TSearchIndexInfo, L as TDbRelation, M as TDbIndexField, N as TDbIndexType, O as TDbDeleteResult, P as TDbInsertManyResult, Q as TypedWithRelation, R as TDbStorageType, S as TCascadeResolver, T as TDbCollation, U as TFkLookupTarget, V as TExistingTableOption, W as TIdDescriptor, X as TValueFormatterPair, Y as TTableResolver, Z as TWriteTableResolver, _ as DbQuery, a as FieldMappingStrategy, b as NavPropsOf, c as NoopLogger, d as AggregateExpr, et as UniqueryControls, f as AggregateFn, g as DbControls, h as AtscriptDbWritable, i as DocumentFieldMapper, j as TDbIndex, k as TDbFieldMeta, l as TGenericLogger, m as AggregateResult, n as DbResponse, nt as UniquSelect, o as BaseDbAdapter, p as AggregateQuery, q as TSyncColumnResult, r as resolveDesignType, s as TableMetadata, t as AtscriptDbReadable, tt as WithRelation, u as AggregateControls, v as FieldOpsFor, w as TColumnDiff, x as OwnPropsOf, y as FilterExpr, z as TDbUpdateResult } from "./db-readable-Cc868K54.mjs";
2
2
  import { d as getDbFieldOp, f as isDbFieldOp, l as TDbFieldOp, p as separateFieldOps, u as TFieldOps } from "./ops-BdRAFLKY.mjs";
3
- import { a as AtscriptQueryComparison, c as AtscriptRef, d as translateQueryTree, f as AtscriptDbTable, i as TViewColumnMapping, l as TViewJoin, m as NativeIntegrity, n as TAdapterFactory, o as AtscriptQueryFieldRef, p as IntegrityStrategy, r as AtscriptDbView, s as AtscriptQueryNode, t as DbSpace, u as TViewPlan } from "./db-space-BUrQ5BFm.mjs";
4
- import { n as createDbValidatorPlugin, t as DbValidationContext } from "./db-validator-plugin-CiqsHTI_.mjs";
3
+ import { a as AtscriptQueryComparison, c as AtscriptRef, d as translateQueryTree, f as AtscriptDbTable, i as TViewColumnMapping, l as TViewJoin, m as NativeIntegrity, n as TAdapterFactory, o as AtscriptQueryFieldRef, p as IntegrityStrategy, r as AtscriptDbView, s as AtscriptQueryNode, t as DbSpace, u as TViewPlan } from "./db-space-crCFv3DF.mjs";
4
+ import { n as createDbValidatorPlugin, t as DbValidationContext } from "./db-validator-plugin-BLMVdi9z.mjs";
5
5
  import { TAtscriptAnnotatedType, TAtscriptTypeArray } from "@atscript/typescript/utils";
6
6
  import { AggregateQuery as AggregateQuery$1, FilterExpr as FilterExpr$1, FilterVisitor, Uniquery as Uniquery$1, computeInsights, isPrimitive, walkFilter } from "@uniqu/core";
7
7
 
@@ -16,6 +16,11 @@ declare class RelationalFieldMapper extends FieldMappingStrategy {
16
16
  reconstructFromRead(row: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
17
17
  translateQuery(query: Uniquery$1, meta: TableMetadata): DbQuery;
18
18
  translateAggregateQuery(query: AggregateQuery$1, meta: TableMetadata): DbQuery;
19
+ /**
20
+ * Overrides the base `translateFilter` to use `leafByLogical` for key resolution
21
+ * (handles flattened nested paths like `contact.email` → `contact__email`).
22
+ */
23
+ translateFilter(filter: FilterExpr$1, meta: TableMetadata): FilterExpr$1;
19
24
  /**
20
25
  * Translates filter with key renaming from logical to physical names.
21
26
  * Used by the relational query path where field paths must be mapped
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { h as DbError } from "./nested-writer-Dmm1gbZV.mjs";
2
- import { _ as NoopLogger, a as getKeyProps, c as IntegrityStrategy, d as resolveDesignType, f as RelationalFieldMapper, g as TableMetadata, h as UniquSelect, i as createDbValidatorPlugin, l as NativeIntegrity, m as FieldMappingStrategy, n as AtscriptDbTable, o as ApplicationIntegrity, p as DocumentFieldMapper, r as decomposePatch, s as BaseDbAdapter, t as AtscriptDbView, u as AtscriptDbReadable } from "./db-view-Esy2fDxw.mjs";
1
+ import { h as DbError } from "./nested-writer-CNDyhg2L.mjs";
2
+ import { _ as NoopLogger, a as getKeyProps, c as IntegrityStrategy, d as resolveDesignType, f as RelationalFieldMapper, g as TableMetadata, h as UniquSelect, i as createDbValidatorPlugin, l as NativeIntegrity, m as FieldMappingStrategy, n as AtscriptDbTable, o as ApplicationIntegrity, p as DocumentFieldMapper, r as decomposePatch, s as BaseDbAdapter, t as AtscriptDbView, u as AtscriptDbReadable } from "./db-view-DSK76uc9.mjs";
3
3
  import { getDbFieldOp, isDbFieldOp, separateFieldOps } from "./ops.mjs";
4
4
  import { computeInsights, isPrimitive, walkFilter } from "@uniqu/core";
5
5
  //#region src/table/db-space.ts
@@ -316,7 +316,7 @@ async function batchPatchNestedTo(host, items, maxDepth, depth) {
316
316
  const patch = { ...nested };
317
317
  let fkValue = fk.localFields.length === 1 ? item[fk.localFields[0]] : void 0;
318
318
  if (fkValue === void 0) {
319
- const pkFilter = host._extractPrimaryKeyFilter(item);
319
+ const pkFilter = host._extractRecordFilter(item);
320
320
  const current = await host.findOne({
321
321
  filter: pkFilter,
322
322
  controls: {}
@@ -316,7 +316,7 @@ async function batchPatchNestedTo(host, items, maxDepth, depth) {
316
316
  const patch = { ...nested };
317
317
  let fkValue = fk.localFields.length === 1 ? item[fk.localFields[0]] : void 0;
318
318
  if (fkValue === void 0) {
319
- const pkFilter = host._extractPrimaryKeyFilter(item);
319
+ const pkFilter = host._extractRecordFilter(item);
320
320
  const current = await host.findOne({
321
321
  filter: pkFilter,
322
322
  controls: {}
package/dist/rel.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_relation_loader = require("./relation-loader-CRC5LcqM.cjs");
3
- const require_nested_writer = require("./nested-writer-BDXsDMPP.cjs");
3
+ const require_nested_writer = require("./nested-writer-BIQ6EfaR.cjs");
4
4
  const require_relation_helpers = require("./relation-helpers-BYvsE1tR.cjs");
5
5
  exports.batchInsertNestedFrom = require_nested_writer.batchInsertNestedFrom;
6
6
  exports.batchInsertNestedTo = require_nested_writer.batchInsertNestedTo;
package/dist/rel.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as TDbForeignKey, L as TDbRelation, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata } from "./db-readable-BQQzfguJ.cjs";
2
- import { t as DbValidationContext } from "./db-validator-plugin-07kDiis2.cjs";
1
+ import { A as TDbForeignKey, L as TDbRelation, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata } from "./db-readable-C3C-qPcP.cjs";
2
+ import { t as DbValidationContext } from "./db-validator-plugin-Cz4QoDWg.cjs";
3
3
  import { FilterExpr, WithRelation } from "@uniqu/core";
4
4
  import { Validator } from "@atscript/typescript/utils";
5
5
 
@@ -55,7 +55,7 @@ interface TNestedWriterHost {
55
55
  _findRemoteFK(targetTable: {
56
56
  foreignKeys: ReadonlyMap<string, TDbForeignKey>;
57
57
  }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
58
- _extractPrimaryKeyFilter(payload: Record<string, unknown>): FilterExpr;
58
+ _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
59
59
  findOne(query: {
60
60
  filter: FilterExpr;
61
61
  controls: Record<string, never>;
package/dist/rel.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as TDbForeignKey, L as TDbRelation, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata } from "./db-readable-Bbr4CjMb.mjs";
2
- import { t as DbValidationContext } from "./db-validator-plugin-CiqsHTI_.mjs";
1
+ import { A as TDbForeignKey, L as TDbRelation, Y as TTableResolver, Z as TWriteTableResolver, l as TGenericLogger, o as BaseDbAdapter, s as TableMetadata } from "./db-readable-Cc868K54.mjs";
2
+ import { t as DbValidationContext } from "./db-validator-plugin-BLMVdi9z.mjs";
3
3
  import { Validator } from "@atscript/typescript/utils";
4
4
  import { FilterExpr, WithRelation } from "@uniqu/core";
5
5
 
@@ -55,7 +55,7 @@ interface TNestedWriterHost {
55
55
  _findRemoteFK(targetTable: {
56
56
  foreignKeys: ReadonlyMap<string, TDbForeignKey>;
57
57
  }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
58
- _extractPrimaryKeyFilter(payload: Record<string, unknown>): FilterExpr;
58
+ _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
59
59
  findOne(query: {
60
60
  filter: FilterExpr;
61
61
  controls: Record<string, never>;
package/dist/rel.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  import { t as loadRelationsImpl } from "./relation-loader-BEOTXNcq.mjs";
2
- import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, i as batchPatchNestedFrom, l as batchReplaceNestedVia, n as batchInsertNestedTo, o as batchPatchNestedVia, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-Dmm1gbZV.mjs";
2
+ import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, i as batchPatchNestedFrom, l as batchReplaceNestedVia, n as batchInsertNestedTo, o as batchPatchNestedVia, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-CNDyhg2L.mjs";
3
3
  import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-CLasawQq.mjs";
4
4
  export { batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
package/dist/sync.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./nested-writer-BDXsDMPP.cjs");
3
- const require_db_view = require("./db-view-CMI9TOo1.cjs");
2
+ require("./nested-writer-BIQ6EfaR.cjs");
3
+ const require_db_view = require("./db-view-CcUjETIF.cjs");
4
4
  //#region src/schema/schema-hash.ts
5
5
  /** Extracts sorted field snapshots from a readable's field descriptors. */
6
6
  function extractFieldSnapshots(fields, typeMapper) {
package/dist/sync.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as TDbForeignKey, B as TExistingColumn, D as TDbDefaultValue, J as TTableOptionDiff, R as TDbStorageType, V as TExistingTableOption, k as TDbFieldMeta, l as TGenericLogger, t as AtscriptDbReadable, w as TColumnDiff } from "./db-readable-BQQzfguJ.cjs";
2
- import { r as AtscriptDbView, t as DbSpace } from "./db-space-Vxpcnyt5.cjs";
1
+ import { A as TDbForeignKey, B as TExistingColumn, D as TDbDefaultValue, J as TTableOptionDiff, R as TDbStorageType, V as TExistingTableOption, k as TDbFieldMeta, l as TGenericLogger, t as AtscriptDbReadable, w as TColumnDiff } from "./db-readable-C3C-qPcP.cjs";
2
+ import { r as AtscriptDbView, t as DbSpace } from "./db-space-Dgf-CphX.cjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
package/dist/sync.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as TDbForeignKey, B as TExistingColumn, D as TDbDefaultValue, J as TTableOptionDiff, R as TDbStorageType, V as TExistingTableOption, k as TDbFieldMeta, l as TGenericLogger, t as AtscriptDbReadable, w as TColumnDiff } from "./db-readable-Bbr4CjMb.mjs";
2
- import { r as AtscriptDbView, t as DbSpace } from "./db-space-BUrQ5BFm.mjs";
1
+ import { A as TDbForeignKey, B as TExistingColumn, D as TDbDefaultValue, J as TTableOptionDiff, R as TDbStorageType, V as TExistingTableOption, k as TDbFieldMeta, l as TGenericLogger, t as AtscriptDbReadable, w as TColumnDiff } from "./db-readable-Cc868K54.mjs";
2
+ import { r as AtscriptDbView, t as DbSpace } from "./db-space-crCFv3DF.mjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
package/dist/sync.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as NoopLogger } from "./db-view-Esy2fDxw.mjs";
1
+ import { _ as NoopLogger } from "./db-view-DSK76uc9.mjs";
2
2
  //#region src/schema/schema-hash.ts
3
3
  /** Extracts sorted field snapshots from a readable's field descriptors. */
4
4
  function extractFieldSnapshots(fields, typeMapper) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "Database adapter utilities for atscript.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -62,14 +62,14 @@
62
62
  "access": "public"
63
63
  },
64
64
  "devDependencies": {
65
- "@atscript/core": "^0.1.39",
66
- "@atscript/typescript": "^0.1.39",
65
+ "@atscript/core": "^0.1.41",
66
+ "@atscript/typescript": "^0.1.41",
67
67
  "@uniqu/core": "^0.1.2",
68
- "unplugin-atscript": "^0.1.39"
68
+ "unplugin-atscript": "^0.1.41"
69
69
  },
70
70
  "peerDependencies": {
71
- "@atscript/core": "^0.1.39",
72
- "@atscript/typescript": "^0.1.39",
71
+ "@atscript/core": "^0.1.41",
72
+ "@atscript/typescript": "^0.1.41",
73
73
  "@uniqu/core": "^0.1.2"
74
74
  },
75
75
  "scripts": {