@atscript/db 0.1.128 → 0.1.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/agg.cjs +31 -0
  2. package/dist/agg.d.cts +30 -1
  3. package/dist/agg.d.mts +30 -1
  4. package/dist/agg.mjs +31 -1
  5. package/dist/{db-error-DXwEzmYJ.cjs → db-error-C4JuLcvb.cjs} +27 -0
  6. package/dist/{db-error-BHPXOKzc.mjs → db-error-COrO58t5.mjs} +22 -1
  7. package/dist/{db-readable-CkbZn_z-.d.cts → db-readable-B7eYWS5q.d.cts} +10 -1
  8. package/dist/{db-readable-BTl4NzNN.d.mts → db-readable-Bn1bV_eC.d.mts} +10 -1
  9. package/dist/{db-space-BEB5Jl8M.d.mts → db-space-C2UCnGHd.d.cts} +28 -2
  10. package/dist/{db-space-BevOyVrC.d.cts → db-space-DdIPYD0Q.d.mts} +28 -2
  11. package/dist/{db-view-Ck0I-PMI.mjs → db-view-BkF2xEGd.mjs} +98 -8
  12. package/dist/{db-view-C8-MsjND.cjs → db-view-hkwQ-fHJ.cjs} +98 -8
  13. package/dist/index.cjs +4 -3
  14. package/dist/index.d.cts +4 -4
  15. package/dist/index.d.mts +4 -4
  16. package/dist/index.mjs +4 -4
  17. package/dist/{nested-writer-DI-HeTky.mjs → nested-writer-CkDo-ZfH.mjs} +1 -1
  18. package/dist/{nested-writer-DoDhl3X3.cjs → nested-writer-DxPhmWFz.cjs} +1 -1
  19. package/dist/ops.cjs +1 -1
  20. package/dist/ops.mjs +1 -1
  21. package/dist/rel.cjs +2 -2
  22. package/dist/rel.d.cts +1 -1
  23. package/dist/rel.d.mts +1 -1
  24. package/dist/rel.mjs +2 -2
  25. package/dist/{relation-loader-BnUgJsUG.cjs → relation-loader-C8GOpNYJ.cjs} +1 -1
  26. package/dist/{relation-loader-BmeOMj0b.mjs → relation-loader-CUGcxJ18.mjs} +1 -1
  27. package/dist/sync.cjs +280 -143
  28. package/dist/sync.d.cts +24 -6
  29. package/dist/sync.d.mts +24 -6
  30. package/dist/sync.mjs +280 -143
  31. package/dist/{validator-eKYWf3xf.mjs → validator-CeD_fqyW.mjs} +1 -1
  32. package/dist/{validator-BNUHCXIE.cjs → validator-lkCJKuoo.cjs} +1 -1
  33. package/dist/{validator-DuAmWc8L.d.cts → validator-wBARmD68.d.cts} +21 -2
  34. package/dist/{validator-DuAmWc8L.d.mts → validator-wBARmD68.d.mts} +21 -2
  35. package/dist/validator.cjs +2 -2
  36. package/dist/validator.d.cts +1 -1
  37. package/dist/validator.d.mts +1 -1
  38. package/dist/validator.mjs +2 -2
  39. package/package.json +1 -1
package/dist/agg.cjs CHANGED
@@ -4,5 +4,36 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  function resolveAlias(expr) {
5
5
  return expr.$as ?? `${expr.$fn}_${expr.$field}`;
6
6
  }
7
+ /**
8
+ * Reads the `$search` / `$index` controls of an aggregate query.
9
+ *
10
+ * Search and grouping are orthogonal: `$search` narrows the ROWS, `$groupBy`
11
+ * shapes what is left. Every adapter must therefore apply the search predicate
12
+ * BEFORE grouping, so the groups — and `$count`, which counts them — only ever
13
+ * describe rows that matched. An adapter that silently ignored the term would
14
+ * report totals for rows the same search excludes from the leaf list.
15
+ *
16
+ * Two rules the leaf search path applies and the grouped path must NOT:
17
+ * - **No implicit relevance ordering.** Relevance is a property of a row, not
18
+ * of a group; after `$group` there is no per-row score left to sort on.
19
+ * Grouped results order by `$sort` or not at all.
20
+ * - **No implicit row cap.** The leaf runners cap an unbounded search at 1000
21
+ * rows; applying that before `$group` would silently truncate group counts.
22
+ *
23
+ * Returns `undefined` when there is nothing to search for, so callers fall
24
+ * straight through to the plain grouped path.
25
+ */
26
+ function resolveAggregateSearch(controls) {
27
+ const raw = controls?.$search;
28
+ if (typeof raw !== "string") return;
29
+ const text = raw.trim();
30
+ if (!text) return;
31
+ const index = controls?.$index;
32
+ return {
33
+ text,
34
+ indexName: typeof index === "string" && index ? index : void 0
35
+ };
36
+ }
7
37
  //#endregion
38
+ exports.resolveAggregateSearch = resolveAggregateSearch;
8
39
  exports.resolveAlias = resolveAlias;
package/dist/agg.d.cts CHANGED
@@ -3,5 +3,34 @@ import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, Agg
3
3
  //#region src/agg.d.ts
4
4
  /** Resolves output alias: $as if provided, otherwise `{fn}_{field}`. */
5
5
  declare function resolveAlias(expr: AggregateExpr$1): string;
6
+ /** The text-search request a grouped query carries, once normalised. */
7
+ interface TAggregateSearch {
8
+ /** Non-empty, trimmed search term. */
9
+ text: string;
10
+ /** Named search index to target, when the caller picked one. */
11
+ indexName?: string;
12
+ }
13
+ /**
14
+ * Reads the `$search` / `$index` controls of an aggregate query.
15
+ *
16
+ * Search and grouping are orthogonal: `$search` narrows the ROWS, `$groupBy`
17
+ * shapes what is left. Every adapter must therefore apply the search predicate
18
+ * BEFORE grouping, so the groups — and `$count`, which counts them — only ever
19
+ * describe rows that matched. An adapter that silently ignored the term would
20
+ * report totals for rows the same search excludes from the leaf list.
21
+ *
22
+ * Two rules the leaf search path applies and the grouped path must NOT:
23
+ * - **No implicit relevance ordering.** Relevance is a property of a row, not
24
+ * of a group; after `$group` there is no per-row score left to sort on.
25
+ * Grouped results order by `$sort` or not at all.
26
+ * - **No implicit row cap.** The leaf runners cap an unbounded search at 1000
27
+ * rows; applying that before `$group` would silently truncate group counts.
28
+ *
29
+ * Returns `undefined` when there is nothing to search for, so callers fall
30
+ * straight through to the plain grouped path.
31
+ */
32
+ declare function resolveAggregateSearch(controls: {
33
+ [key: `$${string}`]: unknown;
34
+ } | undefined): TAggregateSearch | undefined;
6
35
  //#endregion
7
- export { type AggregateControls, type AggregateExpr, type AggregateFn, type AggregateQuery, type AggregateResult, resolveAlias };
36
+ export { type AggregateControls, type AggregateExpr, type AggregateFn, type AggregateQuery, type AggregateResult, resolveAggregateSearch, resolveAlias };
package/dist/agg.d.mts CHANGED
@@ -3,5 +3,34 @@ import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, Agg
3
3
  //#region src/agg.d.ts
4
4
  /** Resolves output alias: $as if provided, otherwise `{fn}_{field}`. */
5
5
  declare function resolveAlias(expr: AggregateExpr$1): string;
6
+ /** The text-search request a grouped query carries, once normalised. */
7
+ interface TAggregateSearch {
8
+ /** Non-empty, trimmed search term. */
9
+ text: string;
10
+ /** Named search index to target, when the caller picked one. */
11
+ indexName?: string;
12
+ }
13
+ /**
14
+ * Reads the `$search` / `$index` controls of an aggregate query.
15
+ *
16
+ * Search and grouping are orthogonal: `$search` narrows the ROWS, `$groupBy`
17
+ * shapes what is left. Every adapter must therefore apply the search predicate
18
+ * BEFORE grouping, so the groups — and `$count`, which counts them — only ever
19
+ * describe rows that matched. An adapter that silently ignored the term would
20
+ * report totals for rows the same search excludes from the leaf list.
21
+ *
22
+ * Two rules the leaf search path applies and the grouped path must NOT:
23
+ * - **No implicit relevance ordering.** Relevance is a property of a row, not
24
+ * of a group; after `$group` there is no per-row score left to sort on.
25
+ * Grouped results order by `$sort` or not at all.
26
+ * - **No implicit row cap.** The leaf runners cap an unbounded search at 1000
27
+ * rows; applying that before `$group` would silently truncate group counts.
28
+ *
29
+ * Returns `undefined` when there is nothing to search for, so callers fall
30
+ * straight through to the plain grouped path.
31
+ */
32
+ declare function resolveAggregateSearch(controls: {
33
+ [key: `$${string}`]: unknown;
34
+ } | undefined): TAggregateSearch | undefined;
6
35
  //#endregion
7
- export { type AggregateControls, type AggregateExpr, type AggregateFn, type AggregateQuery, type AggregateResult, resolveAlias };
36
+ export { type AggregateControls, type AggregateExpr, type AggregateFn, type AggregateQuery, type AggregateResult, resolveAggregateSearch, resolveAlias };
package/dist/agg.mjs CHANGED
@@ -3,5 +3,35 @@
3
3
  function resolveAlias(expr) {
4
4
  return expr.$as ?? `${expr.$fn}_${expr.$field}`;
5
5
  }
6
+ /**
7
+ * Reads the `$search` / `$index` controls of an aggregate query.
8
+ *
9
+ * Search and grouping are orthogonal: `$search` narrows the ROWS, `$groupBy`
10
+ * shapes what is left. Every adapter must therefore apply the search predicate
11
+ * BEFORE grouping, so the groups — and `$count`, which counts them — only ever
12
+ * describe rows that matched. An adapter that silently ignored the term would
13
+ * report totals for rows the same search excludes from the leaf list.
14
+ *
15
+ * Two rules the leaf search path applies and the grouped path must NOT:
16
+ * - **No implicit relevance ordering.** Relevance is a property of a row, not
17
+ * of a group; after `$group` there is no per-row score left to sort on.
18
+ * Grouped results order by `$sort` or not at all.
19
+ * - **No implicit row cap.** The leaf runners cap an unbounded search at 1000
20
+ * rows; applying that before `$group` would silently truncate group counts.
21
+ *
22
+ * Returns `undefined` when there is nothing to search for, so callers fall
23
+ * straight through to the plain grouped path.
24
+ */
25
+ function resolveAggregateSearch(controls) {
26
+ const raw = controls?.$search;
27
+ if (typeof raw !== "string") return;
28
+ const text = raw.trim();
29
+ if (!text) return;
30
+ const index = controls?.$index;
31
+ return {
32
+ text,
33
+ indexName: typeof index === "string" && index ? index : void 0
34
+ };
35
+ }
6
36
  //#endregion
7
- export { resolveAlias };
37
+ export { resolveAggregateSearch, resolveAlias };
@@ -50,6 +50,27 @@ var CasExhaustedError = class extends DbError {
50
50
  this.lastSeenVersion = lastSeenVersion;
51
51
  }
52
52
  };
53
+ /**
54
+ * Thrown by `AtscriptDbTable.touchMany` (`require: 'all'`, the default) when
55
+ * fewer rows than keys matched their expected version — at least one row is
56
+ * stale or missing. Nothing was written (the pre-count refused before the
57
+ * first statement, or the SQL transaction rolled every bump back). Surfaced
58
+ * as HTTP 409 by moost-db.
59
+ */
60
+ var CasMismatchError = class extends DbError {
61
+ matched;
62
+ expected;
63
+ name = "CasMismatchError";
64
+ constructor(matched, expected) {
65
+ const message = `touchMany: ${matched} of ${expected} rows matched — stale or missing rows`;
66
+ super("CAS_MISMATCH", [{
67
+ path: "$cas",
68
+ message
69
+ }], message);
70
+ this.matched = matched;
71
+ this.expected = expected;
72
+ }
73
+ };
53
74
  //#endregion
54
75
  Object.defineProperty(exports, "CasExhaustedError", {
55
76
  enumerable: true,
@@ -57,6 +78,12 @@ Object.defineProperty(exports, "CasExhaustedError", {
57
78
  return CasExhaustedError;
58
79
  }
59
80
  });
81
+ Object.defineProperty(exports, "CasMismatchError", {
82
+ enumerable: true,
83
+ get: function() {
84
+ return CasMismatchError;
85
+ }
86
+ });
60
87
  Object.defineProperty(exports, "DbError", {
61
88
  enumerable: true,
62
89
  get: function() {
@@ -50,5 +50,26 @@ var CasExhaustedError = class extends DbError {
50
50
  this.lastSeenVersion = lastSeenVersion;
51
51
  }
52
52
  };
53
+ /**
54
+ * Thrown by `AtscriptDbTable.touchMany` (`require: 'all'`, the default) when
55
+ * fewer rows than keys matched their expected version — at least one row is
56
+ * stale or missing. Nothing was written (the pre-count refused before the
57
+ * first statement, or the SQL transaction rolled every bump back). Surfaced
58
+ * as HTTP 409 by moost-db.
59
+ */
60
+ var CasMismatchError = class extends DbError {
61
+ matched;
62
+ expected;
63
+ name = "CasMismatchError";
64
+ constructor(matched, expected) {
65
+ const message = `touchMany: ${matched} of ${expected} rows matched — stale or missing rows`;
66
+ super("CAS_MISMATCH", [{
67
+ path: "$cas",
68
+ message
69
+ }], message);
70
+ this.matched = matched;
71
+ this.expected = expected;
72
+ }
73
+ };
53
74
  //#endregion
54
- export { DbError as n, DepthLimitExceededError as r, CasExhaustedError as t };
75
+ export { DepthLimitExceededError as i, CasMismatchError as n, DbError as r, CasExhaustedError as t };
@@ -828,6 +828,15 @@ interface TDbRemoveGuardContext<Row = Record<string, unknown>> {
828
828
  type TDbWriteGuard<Row = Record<string, unknown>> = (ctx: TDbWriteGuardContext<Row>) => void | Promise<void>;
829
829
  /** A validated-stage delete guard — see {@link TDeleteOptions.guard}. */
830
830
  type TDbRemoveGuard<Row = Record<string, unknown>> = (ctx: TDbRemoveGuardContext<Row>) => void | Promise<void>;
831
+ /** Options of `AtscriptDbTable.touchMany` (since 0.1.129). */
832
+ interface TTouchManyOptions {
833
+ /**
834
+ * `'all'` (default): every key must match its stored version — a stale or
835
+ * missing row throws `DbError("CAS_MISMATCH")` and no version moves.
836
+ * `'any'`: bump whatever matches and report the honest counts.
837
+ */
838
+ require?: "all" | "any";
839
+ }
831
840
  /** Options of `insertOne/Many`, `replaceOne` / `bulkReplace`, `updateOne` / `bulkUpdate`. */
832
841
  interface TWriteOptions<Row = Record<string, unknown>> {
833
842
  /** Nested-relation write recursion limit (default 3). */
@@ -2006,4 +2015,4 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
2006
2015
  }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
2007
2016
  }
2008
2017
  //#endregion
2009
- export { TDbWriteAction as $, TCrudPermissions as A, isGeoIndexableType as At, TDbForeignKey as B, NullableOptional as C, TWriteTableResolver as Ct, TCascadeTarget as D, WithRelation$1 as Dt, TCascadeResolver as E, UniqueryControls$1 as Et, TDbCollation as F, TDbInsertResult as G, TDbIndexField as H, TDbDefaultFn as I, TDbRelation as J, TDbObjectKind as K, TDbDefaultValue as L, TDbActionIntent as M, NoopLogger as Mt, TDbActionLevel as N, TGenericLogger as Nt, TColumnDiff as O, TableMetadata as Ot, TDbActionProcessor as P, UniquSelect as Pt, TDbUpdateResult as Q, TDbDeleteResult as R, NavPropsOf$1 as S, TWriteOptions as St, PrimaryKeyOf$1 as T, Uniquery$1 as Tt, TDbIndexType as U, TDbIndex as V, TDbInsertManyResult as W, TDbRemoveGuardContext as X, TDbRemoveGuard as Y, TDbStorageType as Z, DbQuery as _, TSearchIndexInfo as _t, TDbEncryptionOptions as a, TExistingForeignKey as at, FilterExpr$1 as b, TTableResolver as bt, BaseDbAdapter as c, TFkLookupResolver as ct, AggregateFn as d, TIdentification as dt, TDbWriteGuard as et, AggregateQuery$1 as f, TMetaResponse as ft, DbPatch as g, TRelationInfo as gt, DbControls as h, TReferencingForeignKey as ht, DbEncryption as i, TExistingColumn as it, TDbActionInfo as j, isGeoPointType as jt, TCrudOp as k, findAncestorInSet as kt, AggregateControls as l, TFkLookupTarget as lt, AtscriptDbWritable as m, TPrimaryKeyChange as mt, DbResponse as n, TDeleteOptions as nt, DocumentFieldMapper as o, TExistingTableOption as ot, AggregateResult as p, TMetadataOverrides as pt, TDbReferentialAction as q, resolveDesignType as r, TEnsureTableOptions as rt, FieldMappingStrategy as s, TFieldMeta as st, AtscriptDbReadable as t, TDbWriteGuardContext as tt, AggregateExpr$1 as u, TIdDescriptor as ut, DbRow as v, TSyncColumnResult as vt, OwnPropsOf$1 as w, TypedWithRelation as wt, FlatOf$1 as x, TValueFormatterPair as xt, FieldOpsFor as y, TTableOptionDiff as yt, TDbFieldMeta as z };
2018
+ export { TDbWriteAction as $, TCrudPermissions as A, findAncestorInSet as At, TDbForeignKey as B, NullableOptional as C, TWriteOptions as Ct, TCascadeTarget as D, UniqueryControls$1 as Dt, TCascadeResolver as E, Uniquery$1 as Et, TDbCollation as F, UniquSelect as Ft, TDbInsertResult as G, TDbIndexField as H, TDbDefaultFn as I, TDbRelation as J, TDbObjectKind as K, TDbDefaultValue as L, TDbActionIntent as M, isGeoPointType as Mt, TDbActionLevel as N, NoopLogger as Nt, TColumnDiff as O, WithRelation$1 as Ot, TDbActionProcessor as P, TGenericLogger as Pt, TDbUpdateResult as Q, TDbDeleteResult as R, NavPropsOf$1 as S, TValueFormatterPair as St, PrimaryKeyOf$1 as T, TypedWithRelation as Tt, TDbIndexType as U, TDbIndex as V, TDbInsertManyResult as W, TDbRemoveGuardContext as X, TDbRemoveGuard as Y, TDbStorageType as Z, DbQuery as _, TSearchIndexInfo as _t, TDbEncryptionOptions as a, TExistingForeignKey as at, FilterExpr$1 as b, TTableResolver as bt, BaseDbAdapter as c, TFkLookupResolver as ct, AggregateFn as d, TIdentification as dt, TDbWriteGuard as et, AggregateQuery$1 as f, TMetaResponse as ft, DbPatch as g, TRelationInfo as gt, DbControls as h, TReferencingForeignKey as ht, DbEncryption as i, TExistingColumn as it, TDbActionInfo as j, isGeoIndexableType as jt, TCrudOp as k, TableMetadata as kt, AggregateControls as l, TFkLookupTarget as lt, AtscriptDbWritable as m, TPrimaryKeyChange as mt, DbResponse as n, TDeleteOptions as nt, DocumentFieldMapper as o, TExistingTableOption as ot, AggregateResult as p, TMetadataOverrides as pt, TDbReferentialAction as q, resolveDesignType as r, TEnsureTableOptions as rt, FieldMappingStrategy as s, TFieldMeta as st, AtscriptDbReadable as t, TDbWriteGuardContext as tt, AggregateExpr$1 as u, TIdDescriptor as ut, DbRow as v, TSyncColumnResult as vt, OwnPropsOf$1 as w, TWriteTableResolver as wt, FlatOf$1 as x, TTouchManyOptions as xt, FieldOpsFor as y, TTableOptionDiff as yt, TDbFieldMeta as z };
@@ -828,6 +828,15 @@ interface TDbRemoveGuardContext<Row = Record<string, unknown>> {
828
828
  type TDbWriteGuard<Row = Record<string, unknown>> = (ctx: TDbWriteGuardContext<Row>) => void | Promise<void>;
829
829
  /** A validated-stage delete guard — see {@link TDeleteOptions.guard}. */
830
830
  type TDbRemoveGuard<Row = Record<string, unknown>> = (ctx: TDbRemoveGuardContext<Row>) => void | Promise<void>;
831
+ /** Options of `AtscriptDbTable.touchMany` (since 0.1.129). */
832
+ interface TTouchManyOptions {
833
+ /**
834
+ * `'all'` (default): every key must match its stored version — a stale or
835
+ * missing row throws `DbError("CAS_MISMATCH")` and no version moves.
836
+ * `'any'`: bump whatever matches and report the honest counts.
837
+ */
838
+ require?: "all" | "any";
839
+ }
831
840
  /** Options of `insertOne/Many`, `replaceOne` / `bulkReplace`, `updateOne` / `bulkUpdate`. */
832
841
  interface TWriteOptions<Row = Record<string, unknown>> {
833
842
  /** Nested-relation write recursion limit (default 3). */
@@ -2006,4 +2015,4 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
2006
2015
  }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
2007
2016
  }
2008
2017
  //#endregion
2009
- export { TDbWriteAction as $, TCrudPermissions as A, isGeoIndexableType as At, TDbForeignKey as B, NullableOptional as C, TWriteTableResolver as Ct, TCascadeTarget as D, WithRelation$1 as Dt, TCascadeResolver as E, UniqueryControls$1 as Et, TDbCollation as F, TDbInsertResult as G, TDbIndexField as H, TDbDefaultFn as I, TDbRelation as J, TDbObjectKind as K, TDbDefaultValue as L, TDbActionIntent as M, NoopLogger as Mt, TDbActionLevel as N, TGenericLogger as Nt, TColumnDiff as O, TableMetadata as Ot, TDbActionProcessor as P, UniquSelect as Pt, TDbUpdateResult as Q, TDbDeleteResult as R, NavPropsOf$1 as S, TWriteOptions as St, PrimaryKeyOf$1 as T, Uniquery$1 as Tt, TDbIndexType as U, TDbIndex as V, TDbInsertManyResult as W, TDbRemoveGuardContext as X, TDbRemoveGuard as Y, TDbStorageType as Z, DbQuery as _, TSearchIndexInfo as _t, TDbEncryptionOptions as a, TExistingForeignKey as at, FilterExpr$1 as b, TTableResolver as bt, BaseDbAdapter as c, TFkLookupResolver as ct, AggregateFn as d, TIdentification as dt, TDbWriteGuard as et, AggregateQuery$1 as f, TMetaResponse as ft, DbPatch as g, TRelationInfo as gt, DbControls as h, TReferencingForeignKey as ht, DbEncryption as i, TExistingColumn as it, TDbActionInfo as j, isGeoPointType as jt, TCrudOp as k, findAncestorInSet as kt, AggregateControls as l, TFkLookupTarget as lt, AtscriptDbWritable as m, TPrimaryKeyChange as mt, DbResponse as n, TDeleteOptions as nt, DocumentFieldMapper as o, TExistingTableOption as ot, AggregateResult as p, TMetadataOverrides as pt, TDbReferentialAction as q, resolveDesignType as r, TEnsureTableOptions as rt, FieldMappingStrategy as s, TFieldMeta as st, AtscriptDbReadable as t, TDbWriteGuardContext as tt, AggregateExpr$1 as u, TIdDescriptor as ut, DbRow as v, TSyncColumnResult as vt, OwnPropsOf$1 as w, TypedWithRelation as wt, FlatOf$1 as x, TValueFormatterPair as xt, FieldOpsFor as y, TTableOptionDiff as yt, TDbFieldMeta as z };
2018
+ export { TDbWriteAction as $, TCrudPermissions as A, findAncestorInSet as At, TDbForeignKey as B, NullableOptional as C, TWriteOptions as Ct, TCascadeTarget as D, UniqueryControls$1 as Dt, TCascadeResolver as E, Uniquery$1 as Et, TDbCollation as F, UniquSelect as Ft, TDbInsertResult as G, TDbIndexField as H, TDbDefaultFn as I, TDbRelation as J, TDbObjectKind as K, TDbDefaultValue as L, TDbActionIntent as M, isGeoPointType as Mt, TDbActionLevel as N, NoopLogger as Nt, TColumnDiff as O, WithRelation$1 as Ot, TDbActionProcessor as P, TGenericLogger as Pt, TDbUpdateResult as Q, TDbDeleteResult as R, NavPropsOf$1 as S, TValueFormatterPair as St, PrimaryKeyOf$1 as T, TypedWithRelation as Tt, TDbIndexType as U, TDbIndex as V, TDbInsertManyResult as W, TDbRemoveGuardContext as X, TDbRemoveGuard as Y, TDbStorageType as Z, DbQuery as _, TSearchIndexInfo as _t, TDbEncryptionOptions as a, TExistingForeignKey as at, FilterExpr$1 as b, TTableResolver as bt, BaseDbAdapter as c, TFkLookupResolver as ct, AggregateFn as d, TIdentification as dt, TDbWriteGuard as et, AggregateQuery$1 as f, TMetaResponse as ft, DbPatch as g, TRelationInfo as gt, DbControls as h, TReferencingForeignKey as ht, DbEncryption as i, TExistingColumn as it, TDbActionInfo as j, isGeoIndexableType as jt, TCrudOp as k, TableMetadata as kt, AggregateControls as l, TFkLookupTarget as lt, AtscriptDbWritable as m, TPrimaryKeyChange as mt, DbResponse as n, TDeleteOptions as nt, DocumentFieldMapper as o, TExistingTableOption as ot, AggregateResult as p, TMetadataOverrides as pt, TDbReferentialAction as q, resolveDesignType as r, TEnsureTableOptions as rt, FieldMappingStrategy as s, TFieldMeta as st, AtscriptDbReadable as t, TDbWriteGuardContext as tt, AggregateExpr$1 as u, TIdDescriptor as ut, DbRow as v, TSyncColumnResult as vt, OwnPropsOf$1 as w, TWriteTableResolver as wt, FlatOf$1 as x, TTouchManyOptions as xt, FieldOpsFor as y, TTableOptionDiff as yt, TDbFieldMeta as z };
@@ -1,6 +1,6 @@
1
- import { C as NullableOptional, Ct as TWriteTableResolver, E as TCascadeResolver, G as TDbInsertResult, Nt as TGenericLogger, Ot as TableMetadata, Q as TDbUpdateResult, R as TDbDeleteResult, St as TWriteOptions, W as TDbInsertManyResult, a as TDbEncryptionOptions, bt as TTableResolver, c as BaseDbAdapter, ct as TFkLookupResolver, g as DbPatch, ht as TReferencingForeignKey, i as DbEncryption, nt as TDeleteOptions, t as AtscriptDbReadable, v as DbRow } from "./db-readable-BTl4NzNN.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 { C as NullableOptional, Ct as TWriteOptions, E as TCascadeResolver, G as TDbInsertResult, Pt as TGenericLogger, Q as TDbUpdateResult, R as TDbDeleteResult, W as TDbInsertManyResult, a as TDbEncryptionOptions, bt as TTableResolver, c as BaseDbAdapter, ct as TFkLookupResolver, g as DbPatch, ht as TReferencingForeignKey, i as DbEncryption, kt as TableMetadata, nt as TDeleteOptions, t as AtscriptDbReadable, v as DbRow, wt as TWriteTableResolver, xt as TTouchManyOptions } from "./db-readable-B7eYWS5q.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
  /**
@@ -103,6 +103,32 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
103
103
  * present, `$cas` removed) — see {@link TWriteOptions}.
104
104
  */
105
105
  bulkUpdate(payloads: Array<DbPatch<DataType>>, opts?: TWriteOptions<DataType>): Promise<TDbUpdateResult>;
106
+ /**
107
+ * Batch versioned touch (since 0.1.129): bumps the version of every listed
108
+ * row by exactly one, each row guarded by its own expected version. This is
109
+ * the batch fence `updateMany(orFilter, {})` used to be before 0.1.128 (an
110
+ * empty patch is a no-op since then and takes no lock).
111
+ *
112
+ * Each key carries the primary key field(s) (composite supported) plus the
113
+ * version column and NOTHING else — a touch has no payload. Unique indexes
114
+ * do not identify a touch key. `undefined`-valued properties are ignored,
115
+ * like in every write payload. Empty `keys` → `{ 0, 0 }` without a statement.
116
+ *
117
+ * `require: 'all'` (default): one count over the whole key set runs FIRST;
118
+ * a stale or missing row throws {@link CasMismatchError} before any write.
119
+ * The bumps then run as `updateMany(orFilter, {})` chunks of at most
120
+ * {@link TOUCH_MANY_CHUNK} keys inside one adapter transaction; a summed
121
+ * `matchedCount` short of `keys.length` (a row moved between the count and
122
+ * the bump) throws the same error — SQL engines roll every bump back. The
123
+ * pre-count is therefore a deliberate double check on SQL: it is what makes
124
+ * the guarantee hold on adapters whose `withTransaction` is a passthrough
125
+ * (the memory adapter, a Mongo standalone topology) — there it covers the
126
+ * common stale case and the residual race window is accepted.
127
+ * `require: 'any'`: no pre-count, the honest summed result is returned.
128
+ *
129
+ * No `guard`, no `onWrite`; not exposed over HTTP.
130
+ */
131
+ touchMany(keys: Array<DbPatch<DataType>>, opts?: TTouchManyOptions): Promise<TDbUpdateResult>;
106
132
  /**
107
133
  * Deletes a single record by any type-compatible identifier — primary key
108
134
  * or single-field unique index. Uses the same resolution logic as `findById`.
@@ -1,6 +1,6 @@
1
- import { C as NullableOptional, Ct as TWriteTableResolver, E as TCascadeResolver, G as TDbInsertResult, Nt as TGenericLogger, Ot as TableMetadata, Q as TDbUpdateResult, R as TDbDeleteResult, St as TWriteOptions, W as TDbInsertManyResult, a as TDbEncryptionOptions, bt as TTableResolver, c as BaseDbAdapter, ct as TFkLookupResolver, g as DbPatch, ht as TReferencingForeignKey, i as DbEncryption, nt as TDeleteOptions, t as AtscriptDbReadable, v as DbRow } from "./db-readable-CkbZn_z-.cjs";
2
- import { FilterExpr } from "@uniqu/core";
1
+ import { C as NullableOptional, Ct as TWriteOptions, E as TCascadeResolver, G as TDbInsertResult, Pt as TGenericLogger, Q as TDbUpdateResult, R as TDbDeleteResult, W as TDbInsertManyResult, a as TDbEncryptionOptions, bt as TTableResolver, c as BaseDbAdapter, ct as TFkLookupResolver, g as DbPatch, ht as TReferencingForeignKey, i as DbEncryption, kt as TableMetadata, nt as TDeleteOptions, t as AtscriptDbReadable, v as DbRow, wt as TWriteTableResolver, xt as TTouchManyOptions } from "./db-readable-Bn1bV_eC.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
  /**
@@ -103,6 +103,32 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
103
103
  * present, `$cas` removed) — see {@link TWriteOptions}.
104
104
  */
105
105
  bulkUpdate(payloads: Array<DbPatch<DataType>>, opts?: TWriteOptions<DataType>): Promise<TDbUpdateResult>;
106
+ /**
107
+ * Batch versioned touch (since 0.1.129): bumps the version of every listed
108
+ * row by exactly one, each row guarded by its own expected version. This is
109
+ * the batch fence `updateMany(orFilter, {})` used to be before 0.1.128 (an
110
+ * empty patch is a no-op since then and takes no lock).
111
+ *
112
+ * Each key carries the primary key field(s) (composite supported) plus the
113
+ * version column and NOTHING else — a touch has no payload. Unique indexes
114
+ * do not identify a touch key. `undefined`-valued properties are ignored,
115
+ * like in every write payload. Empty `keys` → `{ 0, 0 }` without a statement.
116
+ *
117
+ * `require: 'all'` (default): one count over the whole key set runs FIRST;
118
+ * a stale or missing row throws {@link CasMismatchError} before any write.
119
+ * The bumps then run as `updateMany(orFilter, {})` chunks of at most
120
+ * {@link TOUCH_MANY_CHUNK} keys inside one adapter transaction; a summed
121
+ * `matchedCount` short of `keys.length` (a row moved between the count and
122
+ * the bump) throws the same error — SQL engines roll every bump back. The
123
+ * pre-count is therefore a deliberate double check on SQL: it is what makes
124
+ * the guarantee hold on adapters whose `withTransaction` is a passthrough
125
+ * (the memory adapter, a Mongo standalone topology) — there it covers the
126
+ * common stale case and the residual race window is accepted.
127
+ * `require: 'any'`: no pre-count, the honest summed result is returned.
128
+ *
129
+ * No `guard`, no `onWrite`; not exposed over HTTP.
130
+ */
131
+ touchMany(keys: Array<DbPatch<DataType>>, opts?: TTouchManyOptions): Promise<TDbUpdateResult>;
106
132
  /**
107
133
  * Deletes a single record by any type-compatible identifier — primary key
108
134
  * or single-field unique index. Uses the same resolution logic as `findById`.
@@ -1,7 +1,7 @@
1
- import { n as DbError } from "./db-error-BHPXOKzc.mjs";
2
- import { a as forceNavNonOptional, c as getKeyProps, i as dbPlugin, l as isEmptyObject, n as buildPatchPartial, t as buildDbValidator, u as isPlainObject } from "./validator-eKYWf3xf.mjs";
1
+ import { n as CasMismatchError, r as DbError } from "./db-error-COrO58t5.mjs";
2
+ import { a as forceNavNonOptional, c as getKeyProps, i as dbPlugin, l as isEmptyObject, n as buildPatchPartial, t as buildDbValidator, u as isPlainObject } from "./validator-CeD_fqyW.mjs";
3
3
  import { resolveAlias } from "./agg.mjs";
4
- import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, g as findRemoteFK, h as findFKForRelation, 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-DI-HeTky.mjs";
4
+ import { a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, g as findRemoteFK, h as findFKForRelation, 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-CkDo-ZfH.mjs";
5
5
  import { separateCas, separateFieldOps } from "./ops.mjs";
6
6
  import { flattenAnnotatedType, isAnnotatedType } from "@atscript/typescript/utils";
7
7
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -1027,13 +1027,11 @@ var RelationalFieldMapper = class extends FieldMappingStrategy {
1027
1027
  return {
1028
1028
  filter,
1029
1029
  controls: {
1030
+ ...controls,
1030
1031
  $groupBy: groupBy,
1031
1032
  $select: select ? new UniquSelect(select, meta.allPhysicalFields) : void 0,
1032
1033
  $sort: sort,
1033
- $having: having,
1034
- $skip: controls.$skip,
1035
- $limit: controls.$limit,
1036
- $count: controls.$count
1034
+ $having: having
1037
1035
  },
1038
1036
  insights: query.insights
1039
1037
  };
@@ -2047,6 +2045,11 @@ var AtscriptDbReadable = class {
2047
2045
  }]);
2048
2046
  }
2049
2047
  }
2048
+ const searchTerm = query.controls.$search;
2049
+ if (typeof searchTerm === "string" && searchTerm) {
2050
+ this._ensureSearchable();
2051
+ if (!searchTerm.trim()) return query.controls.$count ? [{ count: 0 }] : [];
2052
+ }
2050
2053
  guardAggregate(this._meta, this.adapter, query);
2051
2054
  const dbQuery = this._fieldMapper.translateAggregateQuery(query, this._meta);
2052
2055
  return (await this.adapter.aggregate(dbQuery)).map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
@@ -2345,7 +2348,7 @@ var AtscriptDbReadable = class {
2345
2348
  * Public entry point for relation loading. Used by adapters for nested $with delegation.
2346
2349
  */
2347
2350
  async loadRelations(rows, withRelations) {
2348
- const { loadRelationsImpl } = await import("./relation-loader-BmeOMj0b.mjs").then((n) => n.n);
2351
+ const { loadRelationsImpl } = await import("./relation-loader-CUGcxJ18.mjs").then((n) => n.n);
2349
2352
  return loadRelationsImpl(rows, withRelations, this);
2350
2353
  }
2351
2354
  /**
@@ -3504,6 +3507,15 @@ function _translateOpsKeys(ops, meta) {
3504
3507
  mul: ops.mul ? _translateOpsRecord(ops.mul, meta) : void 0
3505
3508
  };
3506
3509
  }
3510
+ /** Upper bound of keys per `touchMany` UPDATE statement (parameter-count safety). */
3511
+ const TOUCH_MANY_CHUNK = 500;
3512
+ /** `touchMany` input rejection — always `INVALID_QUERY`, path names the key. */
3513
+ function invalidTouchKey(path, message) {
3514
+ return new DbError("INVALID_QUERY", [{
3515
+ path,
3516
+ message
3517
+ }]);
3518
+ }
3507
3519
  var AtscriptDbTable = class extends AtscriptDbReadable {
3508
3520
  _cascadeResolver;
3509
3521
  _fkLookupResolver;
@@ -3783,6 +3795,84 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
3783
3795
  }));
3784
3796
  }
3785
3797
  /**
3798
+ * Batch versioned touch (since 0.1.129): bumps the version of every listed
3799
+ * row by exactly one, each row guarded by its own expected version. This is
3800
+ * the batch fence `updateMany(orFilter, {})` used to be before 0.1.128 (an
3801
+ * empty patch is a no-op since then and takes no lock).
3802
+ *
3803
+ * Each key carries the primary key field(s) (composite supported) plus the
3804
+ * version column and NOTHING else — a touch has no payload. Unique indexes
3805
+ * do not identify a touch key. `undefined`-valued properties are ignored,
3806
+ * like in every write payload. Empty `keys` → `{ 0, 0 }` without a statement.
3807
+ *
3808
+ * `require: 'all'` (default): one count over the whole key set runs FIRST;
3809
+ * a stale or missing row throws {@link CasMismatchError} before any write.
3810
+ * The bumps then run as `updateMany(orFilter, {})` chunks of at most
3811
+ * {@link TOUCH_MANY_CHUNK} keys inside one adapter transaction; a summed
3812
+ * `matchedCount` short of `keys.length` (a row moved between the count and
3813
+ * the bump) throws the same error — SQL engines roll every bump back. The
3814
+ * pre-count is therefore a deliberate double check on SQL: it is what makes
3815
+ * the guarantee hold on adapters whose `withTransaction` is a passthrough
3816
+ * (the memory adapter, a Mongo standalone topology) — there it covers the
3817
+ * common stale case and the residual race window is accepted.
3818
+ * `require: 'any'`: no pre-count, the honest summed result is returned.
3819
+ *
3820
+ * No `guard`, no `onWrite`; not exposed over HTTP.
3821
+ */
3822
+ async touchMany(keys, opts) {
3823
+ this._ensureBuilt();
3824
+ const versionField = this._meta.versionField;
3825
+ if (versionField === void 0) throw invalidTouchKey("", "touchMany requires @db.column.version");
3826
+ if (keys.length === 0) return {
3827
+ matchedCount: 0,
3828
+ modifiedCount: 0
3829
+ };
3830
+ const pkFields = this.primaryKeys;
3831
+ const seen = /* @__PURE__ */ new Set();
3832
+ const pairs = [];
3833
+ for (const [i, key] of keys.entries()) {
3834
+ const pair = {};
3835
+ for (const pk of pkFields) {
3836
+ if (key[pk] === void 0) throw invalidTouchKey(`[${i}].${pk}`, `touchMany: each key must carry its "${pk}"`);
3837
+ pair[pk] = key[pk];
3838
+ }
3839
+ const version = key[versionField];
3840
+ if (typeof version !== "number" || !Number.isFinite(version)) throw invalidTouchKey(`[${i}].${versionField}`, `touchMany: each key must carry its expected "${versionField}" (number)`);
3841
+ for (const [prop, value] of Object.entries(key)) if (value !== void 0 && prop !== versionField && !(prop in pair)) throw invalidTouchKey(`[${i}].${prop}`, `touchMany: a touch carries no payload — keys hold the primary key and "${versionField}" only, got "${prop}"`);
3842
+ const identity = JSON.stringify(pair);
3843
+ if (seen.has(identity)) throw invalidTouchKey(`[${i}]`, `touchMany: duplicate key ${identity}`);
3844
+ seen.add(identity);
3845
+ pair[versionField] = version;
3846
+ pairs.push(pair);
3847
+ }
3848
+ const orFilter = { $or: pairs };
3849
+ this._guardMutationFilter(orFilter);
3850
+ const translated = this._fieldMapper.translateFilter(orFilter, this._meta).$or;
3851
+ const requireAll = (opts?.require ?? "all") === "all";
3852
+ if (requireAll) {
3853
+ const matched = await this.adapter.count({
3854
+ filter: { $or: translated },
3855
+ controls: {}
3856
+ });
3857
+ if (matched < keys.length) throw new CasMismatchError(matched, keys.length);
3858
+ }
3859
+ return this.adapter.withTransaction(async () => {
3860
+ let matchedCount = 0;
3861
+ let modifiedCount = 0;
3862
+ for (let start = 0; start < translated.length; start += TOUCH_MANY_CHUNK) {
3863
+ const chunk = translated.slice(start, start + TOUCH_MANY_CHUNK);
3864
+ const result = await this.adapter.updateMany({ $or: chunk }, {}, void 0);
3865
+ matchedCount += result.matchedCount;
3866
+ modifiedCount += result.modifiedCount;
3867
+ }
3868
+ if (requireAll && matchedCount !== keys.length) throw new CasMismatchError(matchedCount, keys.length);
3869
+ return {
3870
+ matchedCount,
3871
+ modifiedCount
3872
+ };
3873
+ });
3874
+ }
3875
+ /**
3786
3876
  * Deletes a single record by any type-compatible identifier — primary key
3787
3877
  * or single-field unique index. Uses the same resolution logic as `findById`.
3788
3878
  *