@atscript/db 0.1.146 → 0.1.148

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 (59) hide show
  1. package/dist/{agg-CV7y8nC6.d.cts → agg-42CSpGDR.d.cts} +2 -2
  2. package/dist/{agg-D5DHsAby.d.mts → agg-Wo-smrYV.d.mts} +3 -3
  3. package/dist/agg.cjs +1 -1
  4. package/dist/agg.d.cts +2 -2
  5. package/dist/agg.d.mts +2 -2
  6. package/dist/agg.mjs +1 -1
  7. package/dist/{aggregate-fns-CGBv3E8S.cjs → aggregate-fns-C-UJRobm.cjs} +52 -5
  8. package/dist/{aggregate-fns-CfsveE1w.mjs → aggregate-fns-CyaZyb9I.mjs} +35 -6
  9. package/dist/aggregate-rules-D_bsCpUI.cjs +26 -0
  10. package/dist/aggregate-rules-jdPrxqWa.mjs +15 -0
  11. package/dist/{buckets-DRycmhOW.d.mts → buckets-CNdTOnei.d.mts} +832 -33
  12. package/dist/{buckets-DYFu0eZ8.d.cts → buckets-DJiYlMXc.d.cts} +832 -33
  13. package/dist/{column-diff-D_Kyuh0S.cjs → column-diff-CUU4GvYg.cjs} +1546 -193
  14. package/dist/{column-diff-e2oHc71_.mjs → column-diff-Cp6ZoyRE.mjs} +1529 -200
  15. package/dist/{db-error-D5uilS_A.mjs → db-error-Az85UhTX.mjs} +32 -1
  16. package/dist/{db-error-DTkkeu5b.cjs → db-error-DRQH4sLY.cjs} +55 -0
  17. package/dist/{column-diff-Q9UmWn5x.d.mts → fk-diff-BQ4krij8.d.cts} +48 -2
  18. package/dist/{column-diff-w-Mym_3w.d.cts → fk-diff-DsaIijVX.d.mts} +48 -2
  19. package/dist/index.cjs +95 -35
  20. package/dist/index.d.cts +92 -22
  21. package/dist/index.d.mts +92 -22
  22. package/dist/index.mjs +65 -35
  23. package/dist/{nested-writer-xfQwxplL.cjs → nested-writer-BUQvk6QT.cjs} +734 -2
  24. package/dist/{nested-writer-CnOOAehr.mjs → nested-writer-CUBoq1ZO.mjs} +598 -4
  25. package/dist/numeric-operand-B1jKH7x5.mjs +21 -0
  26. package/dist/numeric-operand-DKfiRLYp.cjs +26 -0
  27. package/dist/ops.cjs +1 -1
  28. package/dist/ops.mjs +1 -1
  29. package/dist/plugin.cjs +293 -5
  30. package/dist/plugin.mjs +294 -6
  31. package/dist/rel.cjs +2 -2
  32. package/dist/rel.d.cts +2 -2
  33. package/dist/rel.d.mts +2 -2
  34. package/dist/rel.mjs +2 -2
  35. package/dist/{relation-helpers-B-0NRKat.d.mts → relation-helpers-Ba0v49sn.d.mts} +1 -1
  36. package/dist/{relation-helpers-BOMm_HUI.d.cts → relation-helpers-kX7jjgME.d.cts} +1 -1
  37. package/dist/relation-loader-ByY1Byrl.mjs +370 -0
  38. package/dist/relation-loader-D8OrdH-r.cjs +369 -0
  39. package/dist/shared.cjs +4 -1
  40. package/dist/shared.d.cts +16 -4
  41. package/dist/shared.d.mts +16 -4
  42. package/dist/shared.mjs +3 -2
  43. package/dist/sync.cjs +11 -7
  44. package/dist/sync.d.cts +2 -25
  45. package/dist/sync.d.mts +2 -25
  46. package/dist/sync.mjs +11 -7
  47. package/dist/{validation-utils-DOsB4e6G.cjs → validation-utils-Da2GjobR.cjs} +35 -24
  48. package/dist/{validation-utils-CMR4fe2M.mjs → validation-utils-Dq0uZ7ef.mjs} +35 -24
  49. package/dist/{validator-Clu2q_7z.mjs → validator-BTiIOTKP.mjs} +9 -3
  50. package/dist/{validator-CVS-onRg.cjs → validator-UcuJxHNT.cjs} +8 -2
  51. package/dist/{validator-Bw6ks9Hy.d.mts → validator-tBNvM1qc.d.cts} +31 -7
  52. package/dist/{validator-Bw6ks9Hy.d.cts → validator-tBNvM1qc.d.mts} +31 -7
  53. package/dist/validator.cjs +2 -2
  54. package/dist/validator.d.cts +1 -1
  55. package/dist/validator.d.mts +1 -1
  56. package/dist/validator.mjs +2 -2
  57. package/package.json +8 -8
  58. package/dist/relation-loader-CBPY6kM7.cjs +0 -461
  59. package/dist/relation-loader-D9XuXaMv.mjs +0 -462
@@ -1,8 +1,41 @@
1
1
  import { f as TFieldOps } from "./ops-AqhV7s9o.mjs";
2
- import { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, FlatOf as FlatOf$1, NavPropsOf, NavPropsOf as NavPropsOf$1, OwnPropsOf, OwnPropsOf as OwnPropsOf$1, PrimaryKeyOf, PrimaryKeyOf as PrimaryKeyOf$1, TAtscriptAnnotatedType, TAtscriptDataType, TAtscriptTypeObject, TMetadataMap, TSerializedAnnotatedType, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
3
- import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, AggregateFn, AggregateFn as AggregateFn$1, AggregateQuery, AggregateQuery as AggregateQuery$1, AggregateResult, BucketUnit, FieldOpsFor, FilterExpr, FilterExpr as FilterExpr$1, ResolvedBucket, TypedWithRelation, Uniquery, Uniquery as Uniquery$1, UniqueryControls, UniqueryControls as UniqueryControls$1, UniqueryInsights, WithRelation, WithRelation as WithRelation$1 } from "@uniqu/core";
2
+ import { AtscriptExprNode, AtscriptExprNode as AtscriptExprNode$1, AtscriptOrderItem, AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryFieldRef as AtscriptQueryFieldRef$1, AtscriptQueryNode, AtscriptQueryNode as AtscriptQueryNode$1, AtscriptRef, FlatOf, FlatOf as FlatOf$1, NavPropsOf, NavPropsOf as NavPropsOf$1, OwnPropsOf, OwnPropsOf as OwnPropsOf$1, PrimaryKeyOf, PrimaryKeyOf as PrimaryKeyOf$1, TAtscriptAnnotatedType, TAtscriptDataType, TAtscriptTypeObject, TMetadataMap, TSerializedAnnotatedType, TValidatorOptions, TValidatorPlugin, Validator } from "@atscript/typescript/utils";
3
+ import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, AggregateFn, AggregateFn as AggregateFn$1, AggregateQuery, AggregateQuery as AggregateQuery$1, AggregateResult, BucketUnit, FieldOpsFor, FilterExpr, FilterExpr as FilterExpr$1, RelationOp, ResolvedBucket, ResolvedRowOrderKey, ResolvedSelectExpr, TypedWithRelation, Uniquery, Uniquery as Uniquery$1, UniqueryControls, UniqueryControls as UniqueryControls$1, UniqueryInsights, WithRelation, WithRelation as WithRelation$1 } from "@uniqu/core";
4
4
 
5
5
  //#region src/query/uniqu-select.d.ts
6
+ /** A row-level expression aggregate: `fn(<expr>)` over each row (physical field leaves). */
7
+ interface TExprAggregate {
8
+ fn: "sum" | "avg" | "min" | "max";
9
+ alias: string;
10
+ expr: AtscriptExprNode;
11
+ /** The distinct physical columns the expression reads. */
12
+ names: readonly string[];
13
+ }
14
+ /** A group-level expression: leaves name aliases of other entries or grouped columns. */
15
+ interface TSelectExpr {
16
+ alias: string;
17
+ expr: AtscriptExprNode;
18
+ /** The distinct names the expression reads (aliases of other entries, or grouped columns). */
19
+ names: readonly string[];
20
+ }
21
+ /** A `first` / `last` entry: the value of PHYSICAL `column` on the group's representative row. */
22
+ interface TFirstLast {
23
+ fn: "first" | "last";
24
+ column: string;
25
+ alias: string;
26
+ }
27
+ /** One key of the representative-row order (PHYSICAL column). */
28
+ interface TRowOrderKey {
29
+ column: string;
30
+ desc: boolean;
31
+ }
32
+ /** The arithmetic and representative-row parts of an aggregate `$select`, as the field mapper resolves them. */
33
+ interface TUniquComputed {
34
+ exprAggregates?: readonly TExprAggregate[];
35
+ exprs?: readonly TSelectExpr[];
36
+ rowOrder?: readonly TRowOrderKey[];
37
+ sources?: ReadonlyMap<string, TDbFieldMeta>;
38
+ }
6
39
  /**
7
40
  * Wraps a raw `$select` value and provides lazy-cached conversions
8
41
  * to the forms different adapters need.
@@ -13,8 +46,9 @@ import { AggregateControls, AggregateExpr, AggregateExpr as AggregateExpr$1, Agg
13
46
  * For exclusion → inclusion inversion, pass `allFields` (physical field names).
14
47
  *
15
48
  * An array `$select` holds plain field names and computed entries —
16
- * aggregates (`{ $fn, $field }`, {@link aggregates}) and calendar buckets
17
- * (`{ $bucket, $field }`, {@link buckets}). Entries arrive normalized
49
+ * aggregates (`{ $fn, $field }`, {@link aggregates}), `first` / `last`
50
+ * ({@link firstLast}), arithmetic ({@link exprAggregates}, {@link exprs}) and
51
+ * calendar buckets (`{ $bucket, $field }`, {@link buckets}). Entries arrive normalized
18
52
  * (`normalizeComputedSelect` rejects any other shape before translation).
19
53
  */
20
54
  declare class UniquSelect {
@@ -23,7 +57,6 @@ declare class UniquSelect {
23
57
  private _allFields?;
24
58
  private _array;
25
59
  private _projection;
26
- private _aggregates;
27
60
  /**
28
61
  * The calendar buckets of an aggregate `$select`, normalized (canonical
29
62
  * zone, week start, alias) with the PHYSICAL source `field` and its
@@ -32,14 +65,56 @@ declare class UniquSelect {
32
65
  * columns). Since 0.1.132.
33
66
  */
34
67
  readonly buckets: readonly TResolvedBucket[] | undefined;
68
+ /**
69
+ * The plain aggregates (`{ $fn, $field }`) of an array-form `$select`;
70
+ * `undefined` when there are none or the `$select` is object form.
71
+ * `first` / `last` are listed separately ({@link firstLast}), so an adapter
72
+ * written before them never meets one here.
73
+ */
74
+ readonly aggregates: AggregateExpr[] | undefined;
75
+ /** The `first` / `last` entries of an array-form `$select` (since 0.1.148); `undefined` when none. */
76
+ readonly firstLast: readonly TFirstLast[] | undefined;
77
+ /**
78
+ * Row-level expression aggregates (`sum(price*qty)`) — leaves are physical
79
+ * columns; render `fn(<expr>)` per row. `undefined` when there are none.
80
+ * Only reaches adapters whose `supportsAggregateExpressions()` is true.
81
+ * Since 0.1.148.
82
+ */
83
+ readonly exprAggregates: readonly TExprAggregate[] | undefined;
84
+ /**
85
+ * Group-level expressions in dependency order: each leaf names an alias
86
+ * defined by an aggregate / `first` / `last` / expression entry or a
87
+ * grouped column; evaluate after grouping. `undefined` when there are none.
88
+ * Since 0.1.148.
89
+ */
90
+ readonly exprs: readonly TSelectExpr[] | undefined;
91
+ /**
92
+ * The order of the rows inside each group that `first` / `last` read, as
93
+ * physical columns, with the primary key appended as the final ascending
94
+ * tie-break. `undefined` without `first` / `last`. Since 0.1.148.
95
+ */
96
+ readonly rowOrder: readonly TRowOrderKey[] | undefined;
97
+ /**
98
+ * The descriptors of the PHYSICAL columns a `min` / `max` / `first` / `last`
99
+ * reads — for an engine that cannot aggregate a type directly (PostgreSQL
100
+ * has no `MIN(boolean)`). Since 0.1.148.
101
+ */
102
+ readonly sources: ReadonlyMap<string, TDbFieldMeta>;
103
+ /**
104
+ * The output alias of every computed entry but the calendar buckets, in the
105
+ * order adapters emit them: {@link aggregates}, {@link exprAggregates},
106
+ * {@link firstLast}, then {@link exprs} (which read the others). Since 0.1.148.
107
+ */
108
+ readonly computedAliases: readonly string[];
35
109
  /**
36
110
  * @param raw - the `$select` value (field paths already physical).
37
111
  * @param allFields - physical field names, for exclusion-form inversion.
38
112
  * @param buckets - the resolved calendar buckets of the raw `$select`'s
39
113
  * `{ $bucket }` entries (the field mappers supply them — physical `field`,
40
114
  * source `fd`).
115
+ * @param computed - the resolved arithmetic and `$rowOrder` parts (physical).
41
116
  */
42
- constructor(raw: UniqueryControls["$select"], allFields?: string[], buckets?: readonly TResolvedBucket[]);
117
+ constructor(raw: UniqueryControls["$select"], allFields?: string[], buckets?: readonly TResolvedBucket[], computed?: TUniquComputed);
43
118
  /**
44
119
  * Resolved inclusion array of plain field names (strings only).
45
120
  * Computed entries (aggregates, calendar buckets) are filtered out.
@@ -53,11 +128,6 @@ declare class UniquSelect {
53
128
  * AggregateExpr objects in array form are ignored.
54
129
  */
55
130
  get asProjection(): Record<string, 0 | 1> | undefined;
56
- /**
57
- * Extracts AggregateExpr entries from array-form $select.
58
- * Returns undefined if no aggregates present or if $select is object form.
59
- */
60
- get aggregates(): AggregateExpr[] | undefined;
61
131
  /** Whether the $select contains any AggregateExpr entries. */
62
132
  get hasAggregates(): boolean;
63
133
  /** The calendar bucket whose alias is `key`, if any — how adapters resolve a `$groupBy` / `$having` key. */
@@ -129,11 +199,28 @@ declare abstract class FieldMappingStrategy {
129
199
  * `buckets` are the query's calendar buckets as the core's normalizer
130
200
  * resolved them (`normalizeComputedSelect` — `AtscriptDbReadable.aggregate`
131
201
  * runs it before the guards); they reach adapters with `field` made
132
- * physical and the source descriptor as `fd`.
202
+ * physical and the source descriptor as `fd`. `exprs` / `rowOrder` are the
203
+ * arithmetic entries and `$rowOrder` keys of the same normalizer
204
+ * (`resolveComputedSelect`): they reach adapters as `$select.exprAggregates`
205
+ * / `.exprs` / `.rowOrder` with physical names (the primary key appended to
206
+ * the order); `$rowOrder` itself is not forwarded (since 0.1.148).
207
+ */
208
+ translateAggregateQuery(query: AggregateQuery, meta: TableMetadata, buckets: readonly ResolvedBucket[], exprs?: readonly ResolvedSelectExpr[], rowOrder?: readonly ResolvedRowOrderKey[]): DbQuery;
209
+ /**
210
+ * `$having` with physical keys — except the computed output `aliases`,
211
+ * which stay as written (an alias equal to a renamed field's name,
212
+ * `first(raisedAt):raisedAt`, is the alias, exactly as in `$sort`).
133
213
  */
134
- translateAggregateQuery(query: AggregateQuery, meta: TableMetadata, buckets: readonly ResolvedBucket[]): DbQuery;
135
- /** Output aliases of the computed `$select` entries (aggregates and calendar buckets). */
214
+ private translateHaving;
215
+ /** Output aliases of the computed `$select` entries (aggregates, expressions and calendar buckets). */
136
216
  private computedAliasSet;
217
+ /**
218
+ * The arithmetic and `$rowOrder` parts of a grouped query with PHYSICAL
219
+ * names: a row-level operand is a column; a group-level operand stays an
220
+ * alias, or becomes the physical name of a `$groupBy` field. The primary
221
+ * key is appended to the order as the final ascending tie-break.
222
+ */
223
+ private physicalComputed;
137
224
  /**
138
225
  * `$select` with its field paths made physical: array-form names and
139
226
  * computed `$field`s (`'*'` kept), or the keys of the object
@@ -146,14 +233,30 @@ declare abstract class FieldMappingStrategy {
146
233
  /** `$sort` with physical keys; computed `aliases` (grouped queries) pass through. */
147
234
  protected physicalSort(sort: NonNullable<DbControls["$sort"]>, meta: TableMetadata, aliases?: ReadonlySet<string>): DbControls["$sort"];
148
235
  /**
149
- * Recursively walks a filter expression, applying `@db.column` key renames
150
- * (document paths — {@link TableMetadata.documentPath}) and adapter-specific
151
- * value formatting via `formatFilterValue`.
236
+ * Translates a logical filter for the adapter: relational predicates are
237
+ * resolved first (`resolveRelationFilterTree`), then every key and value
238
+ * goes through {@link translateResolvedFilter}. `depth` is the predicate
239
+ * level of `filter` itself (0 for a query's own filter; the related tables
240
+ * translate predicate operands at deeper levels).
241
+ */
242
+ translateFilter(filter: FilterExpr, meta: TableMetadata, depth?: number): FilterExpr;
243
+ /**
244
+ * `out` — the translation of the caller's `filter` — with its pre-scan
245
+ * result (`has`) cached for the adapter's repeated `containsRelationFilter`
246
+ * checks; only when the core built it (never the caller's own object).
247
+ */
248
+ protected noteTranslated(filter: unknown, out: FilterExpr, has: boolean): FilterExpr;
249
+ /**
250
+ * Recursively walks a filter expression (predicates already resolved),
251
+ * applying `@db.column` key renames (document paths —
252
+ * {@link TableMetadata.documentPath}) and adapter-specific value formatting
253
+ * via `formatFilterValue`. A resolved predicate passes through under its
254
+ * navigation-field key.
152
255
  *
153
256
  * The relational mapper overrides this to use `leafByLogical` for deeper
154
257
  * key resolution (flattened nested paths).
155
258
  */
156
- translateFilter(filter: FilterExpr, meta: TableMetadata): FilterExpr;
259
+ protected translateResolvedFilter(filter: FilterExpr, meta: TableMetadata): FilterExpr;
157
260
  abstract prepareForWrite(payload: Record<string, unknown>, meta: TableMetadata, adapter: BaseDbAdapter): Record<string, unknown>;
158
261
  abstract translatePatchKeys(update: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
159
262
  /** `$inc` / `$mul` field-op keys to physical names ({@link physicalPath}). */
@@ -289,6 +392,167 @@ declare class DbEncryption {
289
392
  private _getKey;
290
393
  }
291
394
  //#endregion
395
+ //#region src/query/relation-filter.d.ts
396
+ /**
397
+ * Relational filter predicates (`{ nav: { $some | $none: <filter on the
398
+ * related table> } }`) — resolution against the related tables.
399
+ *
400
+ * Semantics: `rel(r, nav)` is exactly the set of related rows `$with=nav`
401
+ * loads for row `r` (same foreign-key pairing, the relation's
402
+ * `@db.rel.filter` included). `$some: F` holds when one of them matches `F`,
403
+ * `$none: F` when none does; a NULL foreign-key component means "no related
404
+ * row". The core resolves each predicate into a {@link ResolvedRelationFilter}
405
+ * (physical names on every side, the inner filter translated by the related
406
+ * table's own field mapper) before the adapter sees the filter — adapters
407
+ * only render it.
408
+ *
409
+ * @since 0.1.147
410
+ */
411
+ /**
412
+ * Maximum nesting of relational predicates in one filter, server-added ones
413
+ * included (a predicate inside a predicate's operand counts one level). The
414
+ * core backstop for every caller; higher than moost-db's per-client limit
415
+ * (`REL_FILTER_CLIENT_MAX_DEPTH`) so server overlays (row scopes,
416
+ * `transformRelationFilter`) have headroom above what a client may send.
417
+ */
418
+ declare const REL_FILTER_MAX_DEPTH = 4;
419
+ /** Maximum number of relational predicates in one filter (nested and server-added ones included). See {@link REL_FILTER_MAX_DEPTH}. */
420
+ declare const REL_FILTER_MAX_NODES = 16;
421
+ /** A table taking part in a resolved predicate. */
422
+ interface TRelationFilterTable {
423
+ /** The adapter's `resolveTableName()` — schema-qualified when the table has a `@db.schema`. */
424
+ table: string;
425
+ /** The adapter's `resolveTableName(false)` — the bare table / collection name. */
426
+ name: string;
427
+ /** The adapter instance bound to this table. */
428
+ adapter: BaseDbAdapter;
429
+ }
430
+ /** The junction side of a resolved `via` predicate. */
431
+ interface TRelationFilterJunction extends TRelationFilterTable {
432
+ /** Junction column → the SOURCE column it references (physical names). */
433
+ toSource: Array<{
434
+ junction: string;
435
+ source: string;
436
+ }>;
437
+ /** Junction column → the TARGET column it references (physical names). */
438
+ toTarget: Array<{
439
+ junction: string;
440
+ target: string;
441
+ }>;
442
+ /** The junction part of the relation's `@db.rel.filter` (junction physical names), if any. */
443
+ filter?: FilterExpr;
444
+ }
445
+ /**
446
+ * Cross-realm brand of {@link ResolvedRelationFilter}: `instanceof` fails when
447
+ * two copies of `@atscript/db` are loaded (ESM + CJS, or nested installs) and
448
+ * an adapter from one sees nodes built by the other.
449
+ */
450
+ declare const RESOLVED_BRAND: unique symbol;
451
+ /**
452
+ * A relational predicate as adapters receive it — the operand of
453
+ * `FilterVisitor.relation(field, op, operand)` once the core translated the
454
+ * filter. Every name is physical:
455
+ *
456
+ * - `to` / `from`: `pairs` correlate a SOURCE column with a TARGET column
457
+ * (`target.<pair.target> = source.<pair.source>`, one pair per composite
458
+ * key part);
459
+ * - `via`: `pairs` is empty — `junction.toSource` correlates the junction
460
+ * with the source row, `junction.toTarget` with the target row.
461
+ *
462
+ * `filter` is the inner filter on the TARGET (already translated by the
463
+ * target's field mapper: renames, flattening, value formatters; nested
464
+ * predicates resolved the same way), conjoined with the target part of the
465
+ * relation's `@db.rel.filter`. `{}` matches every related row.
466
+ *
467
+ * @since 0.1.147
468
+ */
469
+ declare class ResolvedRelationFilter {
470
+ /** @internal cross-realm brand (see {@link isResolvedRelationFilter}). */
471
+ readonly [RESOLVED_BRAND]: true;
472
+ readonly kind: "to" | "from" | "via";
473
+ /** Logical navigation field name on the source table. */
474
+ readonly nav: string;
475
+ readonly source: TRelationFilterTable;
476
+ readonly target: TRelationFilterTable;
477
+ readonly pairs: ReadonlyArray<{
478
+ source: string;
479
+ target: string;
480
+ }>;
481
+ readonly junction?: TRelationFilterJunction;
482
+ readonly filter: FilterExpr;
483
+ constructor(init: {
484
+ kind: "to" | "from" | "via";
485
+ nav: string;
486
+ source: TRelationFilterTable;
487
+ target: TRelationFilterTable;
488
+ pairs: Array<{
489
+ source: string;
490
+ target: string;
491
+ }>;
492
+ junction?: TRelationFilterJunction;
493
+ filter: FilterExpr;
494
+ });
495
+ }
496
+ /** `true` for a {@link ResolvedRelationFilter} (the resolved operand of a predicate). */
497
+ declare function isResolvedRelationFilter(value: unknown): value is ResolvedRelationFilter;
498
+ /** `true` when `value` (a filter entry's value) is an operator map with a `$some` / `$none` key. */
499
+ declare function hasRelationOp(value: unknown): value is Record<string, unknown>;
500
+ /**
501
+ * `true` when `filter` holds a relational predicate anywhere outside
502
+ * predicate operands (through `$and` / `$or` / `$not`) — the cheap pre-scan
503
+ * the field mappers and renderers use to keep predicate-free filters on
504
+ * their fast paths. Results for filters the core translated are cached.
505
+ */
506
+ declare function containsRelationFilter(filter: unknown): boolean;
507
+ /**
508
+ * Calls `visit` for every resolved predicate of a TRANSLATED filter,
509
+ * depth-first: the top-level ones, then (with `nested`) those inside each
510
+ * operand — target filters and junction filters alike. Adapters use it to
511
+ * prepare per-predicate data (memory snapshots, self-referencing checks).
512
+ */
513
+ declare function forEachResolvedRelation(filter: unknown, visit: (node: ResolvedRelationFilter, op: RelationOp) => void, nested?: boolean): void;
514
+ /** Shared state of one guarded filter: predicate depth / count and the read/write mode. */
515
+ interface TRelGuardState {
516
+ /** Nesting level of the filter being guarded (0 = the query's own filter). */
517
+ depth: number;
518
+ /** Predicates seen so far in this query (shared by every level). */
519
+ counter: {
520
+ nodes: number;
521
+ };
522
+ /** Mutation filter (`supportsRelationFilters('write')`) vs read filter. */
523
+ write: boolean;
524
+ /** Dotted navigation chain of the filter being guarded (`""` at the root). */
525
+ path: string;
526
+ }
527
+ /** Installed on `TableMetadata.relationFilters` by the owning readable. */
528
+ interface TRelationFilterHost {
529
+ /** Validates one predicate's operand against the related table (recursively). */
530
+ guard(nav: string, op: RelationOp, inner: FilterExpr, state: TRelGuardState): void;
531
+ /** Resolves one predicate into its adapter-facing form; `depth` is this predicate's level (≥ 1). */
532
+ resolve(nav: string, op: RelationOp, inner: FilterExpr, depth: number): ResolvedRelationFilter;
533
+ }
534
+ /** The present, non-empty `parts` ANDed (`{}` when none, a single one as is). */
535
+ declare function andFilters(...parts: Array<FilterExpr | null | undefined>): FilterExpr;
536
+ /** A relation's `@db.rel.filter`, split by the table each condition reads (LOGICAL names). */
537
+ interface TRelationStaticFilter {
538
+ /** Conditions on the related (target) table. */
539
+ target?: FilterExpr;
540
+ /** Conditions on the junction table (`@db.rel.via` only). */
541
+ junction?: FilterExpr;
542
+ }
543
+ /**
544
+ * A relation's `@db.rel.filter` as logical filters per side — what `$with`
545
+ * loading and relational predicates both AND into the related rows (the
546
+ * filter is part of the relation's meaning). Top-level `and` conditions are
547
+ * split by side: an unqualified field and the related type's fields go to
548
+ * `target`, the `@db.rel.via` junction's to `junction`. A single condition
549
+ * that reads both sides (e.g. an `or` across them) or compares two fields is
550
+ * rejected with `INVALID_QUERY` — `name` is the navigation field (error path).
551
+ *
552
+ * @since 0.1.147
553
+ */
554
+ declare function relationStaticFilter(relation: TDbRelation, name?: string): TRelationStaticFilter;
555
+ //#endregion
292
556
  //#region src/table/db-readable.d.ts
293
557
  /**
294
558
  * Extracts nav prop names from a query's `$with` array.
@@ -358,6 +622,8 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
358
622
  * configured with an `encryption` options block.
359
623
  */
360
624
  setEncryption(encryption: DbEncryption | undefined): void;
625
+ /** @internal Set by the owning `DbSpace` when it closes. */
626
+ _spaceClosed: boolean;
361
627
  /** Ensures metadata is built. Called before any metadata access. */
362
628
  protected _ensureBuilt(): void;
363
629
  /**
@@ -369,6 +635,40 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
369
635
  protected _ensureSearchable(): void;
370
636
  /** Engine-agnostic query-time guards (encrypted-field refs, $geoWithin shape). */
371
637
  protected _guardQuery(query: Uniquery | undefined): void;
638
+ /**
639
+ * Guards a relational predicate operand against THIS table — the filter
640
+ * guard and the path guard with the predicate's shared `state` (depth,
641
+ * count, read/write mode). Called by the source table's relation host.
642
+ *
643
+ * @internal Core wiring for relational predicates; not consumer API.
644
+ */
645
+ _guardRelationOperand(filter: FilterExpr, state: TRelGuardState): void;
646
+ /**
647
+ * Translates a relational predicate operand for THIS table's adapter (its
648
+ * own field mapper; nested predicates resolved at `depth + 1`).
649
+ *
650
+ * @internal Core wiring for relational predicates; not consumer API.
651
+ */
652
+ _resolveRelationOperand(filter: FilterExpr, depth: number): FilterExpr;
653
+ /**
654
+ * Translates a logical query (filter + controls) for THIS table's adapter
655
+ * after the read guards — exactly what `findMany` hands the adapter.
656
+ * For adapters that load `$with` relations natively and must address the
657
+ * related table's physical names.
658
+ *
659
+ * @internal Adapter-facing surface; not part of the consumer API.
660
+ * @since 0.1.147
661
+ */
662
+ _translateForAdapter(query: Uniquery): ReturnType<FieldMappingStrategy["translateQuery"]>;
663
+ /**
664
+ * Physical rows of THIS table → logical rows (field mapping, value
665
+ * formatters, decryption) — what every read does before `$with` loading.
666
+ * `controls` are the logical read controls the rows were read with.
667
+ *
668
+ * @internal Adapter-facing surface; not part of the consumer API.
669
+ * @since 0.1.147
670
+ */
671
+ _rowsFromAdapter(rows: Record<string, unknown>[], controls?: TReadControls): Promise<Record<string, unknown>[]>;
372
672
  private _encryptedPathsCache?;
373
673
  /** Pre-split `encryptedFields` paths — computed once, reused on every read/write. */
374
674
  protected get _encryptedPaths(): Array<{
@@ -401,6 +701,12 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
401
701
  get indexes(): Map<string, TDbIndex>;
402
702
  /** Primary key field names from `@meta.id`. */
403
703
  get primaryKeys(): readonly string[];
704
+ /**
705
+ * Physical column lists that must be unique: the primary key (when declared)
706
+ * followed by every unique index and every adapter-contributed unique field. Used by conflict-ignoring inserts.
707
+ * @since 0.1.148
708
+ */
709
+ get uniqueKeySets(): string[][];
404
710
  /** Preferred row identifier field names. Defaults to primary keys. */
405
711
  get preferredId(): readonly string[];
406
712
  /** Legitimate row-identifier shapes (primary key + every unique index). */
@@ -495,6 +801,12 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
495
801
  setVerbose(enabled: boolean): void;
496
802
  /** Precomputed logical dot-path → physical column name map. */
497
803
  get pathToPhysical(): ReadonlyMap<string, string>;
804
+ /**
805
+ * Physical column (or document path) of a logical field path —
806
+ * `@db.column` renames and flattening applied.
807
+ * @since 0.1.147
808
+ */
809
+ physicalPath(logical: string): string;
498
810
  /** Precomputed physical column name → logical dot-path map (inverse). */
499
811
  get physicalToPath(): ReadonlyMap<string, string>;
500
812
  /** Descriptor for the primary ID field(s). */
@@ -514,7 +826,10 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
514
826
  * its key was not selected.
515
827
  */
516
828
  private _translateRead;
517
- /** Reconstructs + decrypts a read's rows, loads its `$with` relations and strips widened keys. */
829
+ /**
830
+ * Reconstructs + decrypts a read's rows, keeps the ones `pick` selects (all
831
+ * by default), loads their `$with` relations and strips widened keys.
832
+ */
518
833
  private _finishRead;
519
834
  /** `$select` plus the join keys of `withRelations` it leaves out — `undefined` when none is missing. */
520
835
  private _widenSelectForWith;
@@ -564,11 +879,12 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
564
879
  * table. Defense-in-depth for query-path validation: `flattenAnnotatedType`
565
880
  * still truncates real self-referential cycles, so paths like
566
881
  * `parent.parent.name` on a self-ref schema would miss `flatMap.has` but
567
- * remain valid field references on the target.
882
+ * remain valid field references on the target — a path may cross the same
883
+ * relation any number of times (callers cap the depth).
568
884
  *
569
- * Cycle-safe via a visited set keyed on `<tableName>:<navField>`.
885
+ * Terminates on cyclic schemas: every hop consumes one path segment.
570
886
  */
571
- isValidFieldPath(path: string, _visited?: Set<string>): boolean;
887
+ isValidFieldPath(path: string): boolean;
572
888
  /**
573
889
  * Creates a new validator with custom options.
574
890
  */
@@ -585,6 +901,19 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
585
901
  * explicitly requested via `$with`.
586
902
  */
587
903
  findMany<Q extends Uniquery<OwnProps, NavType>>(query: Q): Promise<Array<DbResponse<DataType, NavType, Q>>>;
904
+ /**
905
+ * `findMany` for the generic `$with` loader. With `partitionBy` (logical
906
+ * fields), `$skip` / `$limit` apply per group of rows sharing those fields'
907
+ * values (`BaseDbAdapter.findManyPerPartition`); `pick` chooses which of the
908
+ * read rows to keep before their own `$with` relations load.
909
+ *
910
+ * @internal Relation-loader surface; not part of the consumer API.
911
+ * @since 0.1.147
912
+ */
913
+ _findManyForRelation(query: Uniquery, opts: {
914
+ partitionBy?: readonly string[];
915
+ pick?: (rows: Record<string, unknown>[]) => Record<string, unknown>[];
916
+ }): Promise<Record<string, unknown>[]>;
588
917
  /**
589
918
  * Counts records matching the query.
590
919
  */
@@ -616,12 +945,20 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
616
945
  * then reverse-maps and applies fromStorage formatters on results.
617
946
  */
618
947
  aggregate(query: AggregateQuery): Promise<Array<Record<string, unknown>>>;
948
+ /**
949
+ * The computed aliases of one source field that is a boolean or a decimal —
950
+ * `min` / `max` / `first` / `last` — with that field's descriptor: the
951
+ * aggregate row's value is coerced like the column's own on read.
952
+ */
953
+ private _aliasFields;
619
954
  /** Whether the underlying adapter supports text search. */
620
955
  isSearchable(): boolean;
621
956
  /** Whether the adapter can filter on a given field (proxies adapter capability). */
622
957
  canFilterField(fd: TDbFieldMeta): boolean;
623
958
  /** Calendar-bucket units the adapter can group by (proxies adapter capability; empty = none). */
624
959
  calendarBucketUnits(): ReadonlySet<BucketUnit>;
960
+ /** Whether the adapter renders aggregate arithmetic (proxies adapter capability). @since 0.1.148 */
961
+ supportsAggregateExpressions(): boolean;
625
962
  /** Aggregate functions the adapter renders (proxies adapter capability). @since 0.1.136 */
626
963
  aggregateFns(): ReadonlySet<AggregateFn>;
627
964
  /** Whether the adapter can sort by a given field (proxies adapter capability). */
@@ -836,6 +1173,18 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
836
1173
  }
837
1174
  //#endregion
838
1175
  //#region src/strategies/integrity.d.ts
1176
+ /**
1177
+ * Result of {@link IntegrityStrategy.cascadeBeforeDelete}:
1178
+ * - `undefined` — no cascade ran, or the filter holds no relational
1179
+ * predicate; delete with the caller's own filter;
1180
+ * - an array — the rows the cascade ran for, pinned by primary key as
1181
+ * ADAPTER-READY (physical, already translated) filters, in batches. The
1182
+ * caller must delete exactly these rows instead of evaluating its filter
1183
+ * again: the cascade changed the data the filter may read (a relational
1184
+ * predicate on a child relation no longer matches once the children are
1185
+ * gone). An empty array means no row matched.
1186
+ */
1187
+ type TCascadePin = FilterExpr[] | undefined;
839
1188
  /**
840
1189
  * Strategy for referential integrity enforcement.
841
1190
  * Two implementations: {@link NativeIntegrity} (DB handles FK constraints)
@@ -843,7 +1192,7 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
843
1192
  */
844
1193
  declare abstract class IntegrityStrategy {
845
1194
  abstract validateForeignKeys(items: Array<Record<string, unknown>>, meta: TableMetadata, fkLookupResolver: TFkLookupResolver | undefined, writeTableResolver: TWriteTableResolver | undefined, partial?: boolean, excludeTargetTable?: string): Promise<void>;
846
- abstract cascadeBeforeDelete(filter: FilterExpr, tableName: string, meta: TableMetadata, cascadeResolver: TCascadeResolver, translateFilter: (f: FilterExpr) => FilterExpr, adapter: BaseDbAdapter): Promise<void>;
1195
+ abstract cascadeBeforeDelete(filter: FilterExpr, tableName: string, meta: TableMetadata, cascadeResolver: TCascadeResolver, translateFilter: (f: FilterExpr) => FilterExpr, adapter: BaseDbAdapter): Promise<TCascadePin>;
847
1196
  abstract needsCascade(cascadeResolver: TCascadeResolver | undefined): boolean;
848
1197
  }
849
1198
  /**
@@ -852,7 +1201,7 @@ declare abstract class IntegrityStrategy {
852
1201
  */
853
1202
  declare class NativeIntegrity extends IntegrityStrategy {
854
1203
  validateForeignKeys(): Promise<void>;
855
- cascadeBeforeDelete(): Promise<void>;
1204
+ cascadeBeforeDelete(): Promise<TCascadePin>;
856
1205
  needsCascade(): boolean;
857
1206
  }
858
1207
  //#endregion
@@ -886,7 +1235,10 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
886
1235
  * Inserts a single record. Delegates to {@link insertMany} for unified
887
1236
  * nested creation support.
888
1237
  */
889
- insertOne(payload: DbPatch<DataType>, opts?: TWriteOptions<DataType>): Promise<TDbInsertResult>;
1238
+ insertOne(payload: DbPatch<DataType>, opts: TInsertOptions<DataType> & {
1239
+ onConflict: "ignore";
1240
+ }): Promise<TDbInsertIgnoreResult>;
1241
+ insertOne(payload: DbPatch<DataType>, opts?: TInsertOptions<DataType>): Promise<TDbInsertResult>;
890
1242
  /**
891
1243
  * Inserts multiple records with batch-optimized nested creation.
892
1244
  *
@@ -903,7 +1255,11 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
903
1255
  * 0.1.143) runs once after every phase with the inserted rows' primary-key
904
1256
  * filters — see {@link TWriteOptions}.
905
1257
  */
906
- insertMany(payloads: Array<DbPatch<DataType>>, opts?: TWriteOptions<DataType>): Promise<TDbInsertManyResult>;
1258
+ insertMany(payloads: Array<DbPatch<DataType>>, opts: TInsertOptions<DataType> & {
1259
+ onConflict: "ignore";
1260
+ }): Promise<TDbInsertManyIgnoreResult>;
1261
+ insertMany(payloads: Array<DbPatch<DataType>>, opts?: TInsertOptions<DataType>): Promise<TDbInsertManyResult>;
1262
+ private _insertMany;
907
1263
  /**
908
1264
  * Replaces a single record identified by primary key(s).
909
1265
  * Delegates to {@link bulkReplace} for unified nested relation support.
@@ -1057,6 +1413,24 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
1057
1413
  private _writtenPkFilters;
1058
1414
  /** Invokes a {@link TWriteOptions.check} with de-duplicated PK filters (inside the transaction). */
1059
1415
  private _runWriteCheck;
1416
+ /**
1417
+ * Ignore mode never writes a related parent — it would be orphaned when the
1418
+ * row is skipped. A TO object that names only the target's key fields
1419
+ * (`{ org: { id: 9 } }`) creates nothing: it is a reference to an existing
1420
+ * parent, so it becomes the row's foreign key (the existence check follows
1421
+ * with the other FK validation). Runs before validation (which would demand
1422
+ * the parent's required fields); any other TO object is left for
1423
+ * {@link _rejectNestedToInIgnoreMode}.
1424
+ */
1425
+ private _linkToByKeyInIgnoreMode;
1426
+ /** Ignore mode: whatever TO object is left would create a related parent (see {@link _linkToByKeyInIgnoreMode}). */
1427
+ private _rejectNestedToInIgnoreMode;
1428
+ /**
1429
+ * Conflict-ignoring main insert: marks rows repeating an earlier row's
1430
+ * primary / unique key tuple (NULL components never collide), sends the rest
1431
+ * to the adapter and assembles one slot per input row.
1432
+ */
1433
+ private _insertIgnoring;
1060
1434
  /**
1061
1435
  * The exact primary-key filter of each inserted row: the logical key from
1062
1436
  * the row (SDK defaults applied), else the stored key the adapter wrote into
@@ -1173,6 +1547,22 @@ interface TViewJoin {
1173
1547
  * @since 0.1.136
1174
1548
  */
1175
1549
  kind: "inner" | "left";
1550
+ /**
1551
+ * Set for a first-row join (the `@db.view.joins` 4th argument): of the
1552
+ * target rows matching {@link condition} only the first by `order` joins.
1553
+ * `order` refs are qualified with the target (`ref.type`), the target's
1554
+ * primary key appended as the final ascending key unless already a key;
1555
+ * `key` is that primary key's logical path — the anchor of the join's
1556
+ * correlated subquery. NULL is the smallest value (first in `asc`).
1557
+ * @since 0.1.147
1558
+ */
1559
+ first?: {
1560
+ order: Array<{
1561
+ ref: AtscriptQueryFieldRef;
1562
+ desc: boolean;
1563
+ }>;
1564
+ key: string;
1565
+ };
1176
1566
  }
1177
1567
  /** Resolved view query plan produced by AtscriptDbView. */
1178
1568
  interface TViewPlan {
@@ -1189,6 +1579,14 @@ interface TViewPlan {
1189
1579
  * via the provided resolver function.
1190
1580
  */
1191
1581
  declare function translateQueryTree(node: AtscriptQueryNode, resolveField: (ref: AtscriptQueryFieldRef) => string): FilterExpr;
1582
+ /**
1583
+ * Evaluates a computed expression in process (the memory adapter, a test, a
1584
+ * third-party adapter) with the semantics every adapter shares: IEEE double,
1585
+ * a leaf is `Number()`-ed, NULL / undefined propagates as `null`, `/` by zero
1586
+ * is `null`, `coalesce` returns its first non-null value.
1587
+ * @since 0.1.148
1588
+ */
1589
+ declare function evaluateExpr(expr: AtscriptExprNode, leaf: (field: string) => unknown): number | null;
1192
1590
  //#endregion
1193
1591
  //#region src/table/view-source.d.ts
1194
1592
  /**
@@ -1272,6 +1670,15 @@ interface TViewColumnMapping {
1272
1670
  * `@db.view.filter` (entry table + joins). @since 0.1.136
1273
1671
  */
1274
1672
  aggFilter?: AtscriptQueryNode;
1673
+ /**
1674
+ * A computed column (`@db.compute`): the arithmetic expression, its leaves
1675
+ * naming the view's own fields by {@link viewPath}. Such a mapping reads no
1676
+ * source column — `sourceTable` is the entry table and `sourceColumn` is
1677
+ * `""` (unused). Renderers evaluate it in IEEE double with division by zero
1678
+ * → NULL; `nullable` is set when the expression may be NULL.
1679
+ * @since 0.1.147
1680
+ */
1681
+ expr?: AtscriptExprNode;
1275
1682
  }
1276
1683
  /**
1277
1684
  * Whether `type` declares a view (managed `@db.view.for` or external
@@ -1367,7 +1774,22 @@ declare class AtscriptDbView<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
1367
1774
  * aggregate other than `count` over `'*'`.
1368
1775
  */
1369
1776
  getViewColumnMappings(): TViewColumnMapping[];
1777
+ /**
1778
+ * Why the bound adapter cannot render this managed view: one message per
1779
+ * computed column / first-row join whose feature its `viewCapabilities()`
1780
+ * does not list. Empty for an external view or when every feature is
1781
+ * rendered. Schema sync refuses such a view; the adapter's `ensureTable()`
1782
+ * throws for it (fail-closed for adapters that predate the features).
1783
+ * @since 0.1.147
1784
+ */
1785
+ viewCapabilityProblems(): string[];
1370
1786
  private _buildColumnMappings;
1787
+ /**
1788
+ * The runtime twin of the compile-time `@db.compute` rules the renderers
1789
+ * rely on: every leaf names a (non-ignored) view column, computed columns
1790
+ * form no cycle; sets each computed mapping's `nullable`.
1791
+ */
1792
+ private _checkComputed;
1371
1793
  /** One view column over one physical source (a column or a JSON leaf). */
1372
1794
  private _leafMapping;
1373
1795
  }
@@ -1394,6 +1816,14 @@ interface TDbSpaceOptions {
1394
1816
  logger?: TGenericLogger;
1395
1817
  /** Field-level encryption configuration for `@db.encrypted` fields. */
1396
1818
  encryption?: TDbEncryptionOptions;
1819
+ /**
1820
+ * Runs once from {@link DbSpace.close}, after every adapter was disposed.
1821
+ * Pass the resource the space does not own by itself, e.g.
1822
+ * `() => driver.close()`. The `createAdapter` helpers of the adapter
1823
+ * packages set it to close the driver they built.
1824
+ * @since 0.1.148
1825
+ */
1826
+ onClose?: () => void | Promise<void>;
1397
1827
  }
1398
1828
  /**
1399
1829
  * A database space — a registry of tables and views sharing the same adapter type and driver.
@@ -1413,13 +1843,21 @@ interface TDbSpaceOptions {
1413
1843
  * const activeUsers = db.getView(ActiveUsersType)
1414
1844
  * ```
1415
1845
  */
1416
- declare class DbSpace {
1846
+ declare class DbSpace implements AsyncDisposable {
1417
1847
  protected readonly adapterFactory: TAdapterFactory;
1848
+ /** `await using` support — an alias for {@link close}, installed below where the runtime has the symbol. */
1849
+ [Symbol.asyncDispose]: () => Promise<void>;
1418
1850
  private _readables;
1419
1851
  /** All tables created in this space — used for reverse FK lookup during cascade. */
1420
1852
  private _allTables;
1853
+ /** Every table / view handle the space created — flagged closed on {@link close}. */
1854
+ private _handles;
1421
1855
  /** Lazily created adapter for administrative ops (drop table/view) that don't need a registered readable. */
1422
1856
  private _adminAdapter?;
1857
+ /** Every adapter this space created (tables, views, admin) — disposed on close. */
1858
+ private _adapters;
1859
+ private _onClose?;
1860
+ private _closing?;
1423
1861
  protected readonly logger: TGenericLogger;
1424
1862
  /** Encryption service for `@db.encrypted` fields — validated eagerly at construction. */
1425
1863
  protected readonly _encryption?: DbEncryption;
@@ -1484,6 +1922,23 @@ declare class DbSpace {
1484
1922
  * Optional call: an adapter built against an older `@atscript/db` copy lacks it.
1485
1923
  */
1486
1924
  private _createAdapter;
1925
+ /** `true` once {@link close} was called. @since 0.1.148 */
1926
+ get closed(): boolean;
1927
+ /** Throws `SPACE_CLOSED` once the space is closed. */
1928
+ private _assertOpen;
1929
+ /**
1930
+ * Closes the space: marks it closed, disposes every adapter it created, then
1931
+ * runs the `onClose` hook (the `createAdapter` helpers close their driver
1932
+ * there). Idempotent — every call returns the first call's promise. Every
1933
+ * step is attempted; failures are collected into an `AggregateError`.
1934
+ *
1935
+ * It does not cancel or drain in-flight queries: call it after the server
1936
+ * stopped accepting requests. After close, `get*` and operations on existing
1937
+ * table/view handles throw `DbError("SPACE_CLOSED")`.
1938
+ * @since 0.1.148
1939
+ */
1940
+ close(): Promise<void>;
1941
+ private _doClose;
1487
1942
  /**
1488
1943
  * Finds all child tables with FKs pointing to the given parent table name.
1489
1944
  * Accesses `table.foreignKeys` which triggers `_flatten()` if needed.
@@ -1497,8 +1952,19 @@ declare class DbSpace {
1497
1952
  }
1498
1953
  //#endregion
1499
1954
  //#region src/base-adapter.d.ts
1500
- /** Every calendar-bucket unit — what an adapter that renders them all returns from `calendarBucketUnits()`. */
1955
+ /**
1956
+ * Every calendar-bucket unit — what an adapter that renders them all returns
1957
+ * from `calendarBucketUnits()`. Includes `'hour'` since 0.1.147.
1958
+ */
1501
1959
  declare const ALL_BUCKET_UNITS: ReadonlySet<BucketUnit>;
1960
+ /**
1961
+ * A managed-view feature an adapter may render (`viewCapabilities()`):
1962
+ * `compute` — computed columns (`@db.compute`); `firstJoin` — first-row joins.
1963
+ * @since 0.1.147
1964
+ */
1965
+ type TViewCapability = "compute" | "firstJoin";
1966
+ /** Every view capability — what the bundled adapters return from `viewCapabilities()`. @since 0.1.147 */
1967
+ declare const ALL_VIEW_CAPABILITIES: ReadonlySet<TViewCapability>;
1502
1968
  /**
1503
1969
  * Abstract base class for database adapters.
1504
1970
  *
@@ -1548,6 +2014,15 @@ declare abstract class BaseDbAdapter {
1548
2014
  * index sync, etc.
1549
2015
  */
1550
2016
  registerReadable(readable: AtscriptDbReadable<any, any, any, any, any, any, any>, logger?: TGenericLogger): void;
2017
+ /**
2018
+ * Makes `ensureTable()` of a managed view fail closed (since 0.1.147): it
2019
+ * throws before the adapter renders a computed column / first-row join its
2020
+ * {@link viewCapabilities} does not list — schema sync refuses such a view
2021
+ * up front, a direct `ensureTable()` call must not render it as a plain
2022
+ * (row-multiplying) join either. Wraps the subclass's own implementation, so
2023
+ * third-party adapters get the guard without code changes.
2024
+ */
2025
+ private _guardViewCapabilities;
1551
2026
  /**
1552
2027
  * Called by {@link DbSpace} right after its factory builds this adapter —
1553
2028
  * the administrative one included — before {@link registerReadable}. No-op
@@ -1557,6 +2032,13 @@ declare abstract class BaseDbAdapter {
1557
2032
  * @since 0.1.137
1558
2033
  */
1559
2034
  registerSpace(_space: DbSpace): void;
2035
+ /**
2036
+ * Releases timers, caches or change streams the adapter holds. Called once
2037
+ * by {@link DbSpace.close}; the driver itself is closed by the space's
2038
+ * `onClose` hook, never here. No-op by default.
2039
+ * @since 0.1.148
2040
+ */
2041
+ dispose?(): void | Promise<void>;
1560
2042
  /**
1561
2043
  * Enables or disables verbose (debug-level) logging for this adapter.
1562
2044
  * When disabled, no log strings are constructed — zero overhead.
@@ -1679,8 +2161,10 @@ declare abstract class BaseDbAdapter {
1679
2161
  * be adopted adapter by adapter. An adapter that returns a unit must group
1680
2162
  * by the bucket alias in `$groupBy` — see `controls.$select.buckets`
1681
2163
  * (`TResolvedBucket`: physical `field`, source `fd`) — and return the
1682
- * `YYYY-MM-DD` label of the bucket's first local day (null for a null or
1683
- * out-of-range source, uniqu's `bucketLabel` semantics). Since 0.1.132.
2164
+ * `YYYY-MM-DD` label of the bucket's first local day, or for `'hour'` the
2165
+ * local wall-clock hour `YYYY-MM-DDTHH:00` (null for a null or out-of-range
2166
+ * source, uniqu's `bucketLabel` semantics). Since 0.1.132; `'hour'` since
2167
+ * 0.1.147 — an adapter returning {@link ALL_BUCKET_UNITS} must render it.
1684
2168
  */
1685
2169
  calendarBucketUnits(): ReadonlySet<BucketUnit>;
1686
2170
  /**
@@ -1695,6 +2179,20 @@ declare abstract class BaseDbAdapter {
1695
2179
  * @since 0.1.136
1696
2180
  */
1697
2181
  aggregateFns(): ReadonlySet<AggregateFn>;
2182
+ /**
2183
+ * Whether this adapter renders arithmetic in an aggregate `$select`
2184
+ * (`{ $expr }`, `{ $fn, $expr }`) — IEEE double, NULL propagating, `/` by
2185
+ * zero NULL, the same semantics as `@db.compute`. Default `false`
2186
+ * (fail-closed): the core rejects such a query with `AGG_EXPR_NOT_SUPPORTED`
2187
+ * before dispatch. An adapter returning `true` also receives
2188
+ * `controls.$select.exprAggregates` / `.exprs` (see `UniquSelect`), and
2189
+ * `first` / `last` (`aggregateFns()`) with `.firstLast` and `.rowOrder`;
2190
+ * the shared `evaluateExpr` evaluates an expression tree in process.
2191
+ * moost-db advertises it as `/meta.aggregateExpressions`.
2192
+ *
2193
+ * @since 0.1.148
2194
+ */
2195
+ supportsAggregateExpressions(): boolean;
1698
2196
  /**
1699
2197
  * Revision of how this adapter renders a managed view (its SQL / pipeline)
1700
2198
  * from an unchanged view definition. Stored in each managed view's sync
@@ -1706,6 +2204,16 @@ declare abstract class BaseDbAdapter {
1706
2204
  * @since 0.1.137
1707
2205
  */
1708
2206
  viewRenderRevision(): string | undefined;
2207
+ /**
2208
+ * The managed-view features this adapter renders: `compute` — computed
2209
+ * columns (`@db.compute`); `firstJoin` — first-row joins (the ordered 4th
2210
+ * argument of `@db.view.joins`). Schema sync refuses a view using a feature
2211
+ * not listed. The default is EMPTY (fail-closed): a third-party adapter
2212
+ * opts in once it renders them.
2213
+ *
2214
+ * @since 0.1.147
2215
+ */
2216
+ viewCapabilities(): ReadonlySet<TViewCapability>;
1709
2217
  /**
1710
2218
  * Whether this adapter enforces foreign key constraints natively.
1711
2219
  * When `true`, the generic layer skips application-level cascade/setNull
@@ -1752,6 +2260,35 @@ declare abstract class BaseDbAdapter {
1752
2260
  * Default: `false` — the table layer uses application-level batch loading.
1753
2261
  */
1754
2262
  supportsNativeRelations(): boolean;
2263
+ /**
2264
+ * Whether this adapter renders relational filter predicates
2265
+ * (`{ nav: { $some | $none: … } }`) in `mode` — `read` for find / count /
2266
+ * search / aggregate filters, `write` for mutation filters
2267
+ * (`updateMany`, `deleteMany`, …). Default `false`: the core rejects such
2268
+ * filters with `REL_FILTER_NOT_SUPPORTED` before they reach the adapter.
2269
+ *
2270
+ * An adapter returning `true` receives each predicate already resolved by
2271
+ * the core: the filter visitor's `relation(field, op, operand)` callback
2272
+ * gets a `ResolvedRelationFilter` operand (`kind`, physical
2273
+ * correlation `pairs`, `target` / `junction` tables with their adapters,
2274
+ * and the inner `filter` already translated to the target's physical
2275
+ * names, nested predicates resolved too). Keep `relation` on every
2276
+ * `walkFilter` visitor that may meet such a filter.
2277
+ *
2278
+ * @since 0.1.147
2279
+ */
2280
+ supportsRelationFilters(_mode: "read" | "write"): boolean;
2281
+ /**
2282
+ * Whether `other` serves a table of the SAME store as this adapter, so one
2283
+ * statement / pipeline can correlate both (a relational predicate renders
2284
+ * the related table inside this table's query). Default: same adapter class
2285
+ * and same {@link _transactionOwner} (the driver / pool / client the
2286
+ * adapter was built with). Override when the owner is shared across
2287
+ * separate databases (e.g. one Mongo client over several databases).
2288
+ *
2289
+ * @since 0.1.147
2290
+ */
2291
+ sharesStoreWith(other: BaseDbAdapter): boolean;
1755
2292
  /**
1756
2293
  * Loads relations onto result rows using adapter-native operations.
1757
2294
  * Only called when {@link supportsNativeRelations} returns `true`.
@@ -1987,6 +2524,22 @@ declare abstract class BaseDbAdapter {
1987
2524
  data: Array<Record<string, unknown>>;
1988
2525
  count: number;
1989
2526
  }>;
2527
+ /**
2528
+ * Reads like {@link findMany}, except that `$skip` / `$limit` apply to each
2529
+ * partition — the rows sharing the values of the `partitionBy` columns
2530
+ * (physical names) — instead of to the whole result. The generic `$with`
2531
+ * loader reads the related rows of many parent rows at once this way, so a
2532
+ * relation's `$skip` / `$limit` page each parent row's related rows.
2533
+ * `$sort` orders the rows within a partition; how partitions interleave is
2534
+ * unspecified.
2535
+ *
2536
+ * Default: one {@link findMany} without `$skip` / `$limit`, paged per
2537
+ * partition in memory. The SQL adapters override it with a `ROW_NUMBER()`
2538
+ * window, so only the kept rows are read.
2539
+ *
2540
+ * @since 0.1.147
2541
+ */
2542
+ findManyPerPartition(query: DbQuery, partitionBy: readonly string[]): Promise<Array<Record<string, unknown>>>;
1990
2543
  /**
1991
2544
  * Executes an aggregate query (GROUP BY + aggregate functions).
1992
2545
  * Default throws — override in adapters that support aggregation.
@@ -1994,6 +2547,21 @@ declare abstract class BaseDbAdapter {
1994
2547
  aggregate(_query: DbQuery): Promise<Array<Record<string, unknown>>>;
1995
2548
  abstract insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
1996
2549
  abstract insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
2550
+ /**
2551
+ * Conflict-ignoring batch insert (`insertMany(rows, { onConflict: 'ignore' })`).
2552
+ * Returns ONE SLOT PER INPUT ROW, in order: `{ insertedId }` for an inserted
2553
+ * row, `null` for a row skipped because it collided with a STORED row on the
2554
+ * primary key or a unique index. (The core already removed duplicates inside
2555
+ * the batch.) Only uniqueness collisions are skipped — NOT NULL, FK, check
2556
+ * and every other error must throw — and a skipped row must never abort the
2557
+ * surrounding transaction. Fail-closed by default: throws
2558
+ * `DbError("ON_CONFLICT_NOT_SUPPORTED")`; also override
2559
+ * {@link supportsInsertIgnore}.
2560
+ * @since 0.1.148
2561
+ */
2562
+ insertManyIgnore(_data: Array<Record<string, unknown>>): Promise<TDbInsertIgnoreSlot[]>;
2563
+ /** Whether {@link insertManyIgnore} is implemented (drives `crud.insert: ["onConflict"]` in `/meta`). @since 0.1.148 */
2564
+ supportsInsertIgnore(): boolean;
1997
2565
  abstract replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
1998
2566
  abstract updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
1999
2567
  abstract deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
@@ -2350,6 +2918,14 @@ declare class TableMetadata {
2350
2918
  jsonValueParents: ReadonlySet<string>;
2351
2919
  /** Every field descriptor's `physicalName` — reserved names a bucket alias may not take. */
2352
2920
  physicalNames: ReadonlySet<string>;
2921
+ /**
2922
+ * Resolves / guards relational filter predicates (`{ nav: { $some: … } }`)
2923
+ * against the related tables — installed by the owning readable when the
2924
+ * table has navigation fields and a table resolver (a `DbSpace`). The field
2925
+ * mappers and the path guard reach the related tables through it.
2926
+ * @since 0.1.147
2927
+ */
2928
+ relationFilters?: TRelationFilterHost;
2353
2929
  private _built;
2354
2930
  private _identifications?;
2355
2931
  private _alwaysAddressable?;
@@ -2442,6 +3018,14 @@ declare class TableMetadata {
2442
3018
  * (flatMap, indexes, columnMap, etc.) is already populated.
2443
3019
  */
2444
3020
  private _buildFieldDescriptors;
3021
+ /**
3022
+ * Fills `physicalFields` / `physicalTargetFields` on every FK: the local
3023
+ * side from this table's path maps, the target side from the referenced
3024
+ * type's own `@db.column` renames (same storage rules as this table — a
3025
+ * dotted target path is a flattened column on relational storage, a
3026
+ * renamed top-level key on document storage).
3027
+ */
3028
+ private _resolveFkPhysicalFields;
2445
3029
  /**
2446
3030
  * Resolves `fkTargetField` for FK fields in field descriptors.
2447
3031
  */
@@ -2476,7 +3060,7 @@ declare class TableMetadata {
2476
3060
  //#endregion
2477
3061
  //#region src/types.d.ts
2478
3062
  /** Controls with resolved projection. Used in the adapter interface. */
2479
- interface DbControls extends Omit<UniqueryControls, "$select"> {
3063
+ interface DbControls extends Omit<UniqueryControls, "$select" | "$rowOrder"> {
2480
3064
  $select?: UniquSelect;
2481
3065
  }
2482
3066
  /** Query object with resolved projection. Passed to adapter methods. */
@@ -2513,6 +3097,11 @@ interface TRelationInfo {
2513
3097
  name: string;
2514
3098
  direction: "to" | "from" | "via";
2515
3099
  isArray: boolean;
3100
+ /**
3101
+ * Present (true) when the relation is `@db.rel.filterable`: clients may
3102
+ * filter by related rows (`nav=$some(…)` / `nav=$none(…)`). @since 0.1.147
3103
+ */
3104
+ filterable?: true;
2516
3105
  }
2517
3106
  /** Per-field capability flags in a meta response. */
2518
3107
  interface TFieldMeta {
@@ -2549,6 +3138,13 @@ interface TFieldMeta {
2549
3138
  * (`bucketUnits`). Since 0.1.132.
2550
3139
  */
2551
3140
  bucketable?: true;
3141
+ /**
3142
+ * Present (true) exactly when the field may be an operand of query-time
3143
+ * arithmetic (`sum(price*qty)`, `expr(a/b)`) — a plain `number` (not a
3144
+ * decimal or timestamp) that is visible and physically aggregatable — on an
3145
+ * adapter with `aggregateExpressions`. Since 0.1.148.
3146
+ */
3147
+ numeric?: true;
2552
3148
  /**
2553
3149
  * Present (true) when the field is a `@db.column.derived` column: its value
2554
3150
  * is computed from a `@db.json` field of the same row and is never written
@@ -2556,6 +3152,27 @@ interface TFieldMeta {
2556
3152
  * render it read-only. Since 0.1.141.
2557
3153
  */
2558
3154
  derived?: true;
3155
+ /**
3156
+ * Present (true) when the field is a computed view column (`@db.compute`):
3157
+ * its value is arithmetic over other fields of the view, evaluated by the
3158
+ * database. Advisory — sorting / filtering follow `sortable` / `filterable`.
3159
+ * Since 0.1.147.
3160
+ */
3161
+ computed?: true;
3162
+ /**
3163
+ * Present (true) exactly when `$groupBy` on this field passes the gate:
3164
+ * physically filterable (adapter, not write-only, not encrypted) and, on a
3165
+ * table declaring dimensions or measures, a dimension. Since 0.1.148.
3166
+ */
3167
+ groupable?: true;
3168
+ /**
3169
+ * Present (true) for a declared display-only decoration (`@DbDecorations`):
3170
+ * a value the controller computes (`decorateRows`) and attaches to rows. Name
3171
+ * it in `$select` to receive it; it is never filterable, sortable or
3172
+ * groupable, and its column definition is in {@link TMetaResponse.decorations}
3173
+ * (not in `type`). Since 0.1.148.
3174
+ */
3175
+ decoration?: true;
2559
3176
  }
2560
3177
  /** Built-in CRUD operation names; map 1:1 to public method names. */
2561
3178
  type TCrudOp = "query" | "pages" | "one" | "geo" | "insert" | "update" | "replace" | "remove";
@@ -2578,6 +3195,14 @@ interface TMetaResponse {
2578
3195
  relations: TRelationInfo[];
2579
3196
  fields: Record<string, TFieldMeta>;
2580
3197
  type: TSerializedAnnotatedType;
3198
+ /**
3199
+ * The declared display-only fields (`@DbDecorations`): an object type whose
3200
+ * props are the decorations (with their `@meta.*`, `@expect.*` and `@ui.*`
3201
+ * annotations), each also listed in {@link fields} with `decoration: true`.
3202
+ * Absent when none is declared (or none is visible to the caller). Not part
3203
+ * of {@link type}, so forms and write validation never see it. Since 0.1.148.
3204
+ */
3205
+ decorations?: TSerializedAnnotatedType;
2581
3206
  actions: TDbActionInfo[];
2582
3207
  crud: TCrudPermissions;
2583
3208
  /**
@@ -2602,6 +3227,15 @@ interface TMetaResponse {
2602
3227
  * @since 0.1.136
2603
3228
  */
2604
3229
  aggregateFns?: AggregateFn[];
3230
+ /**
3231
+ * Whether the adapter renders arithmetic in an aggregate `$select`
3232
+ * (`{ $expr }`, URL `expr(a/b):alias`, `sum(price*qty):alias`) — see
3233
+ * `BaseDbAdapter.supportsAggregateExpressions()`. Fields that may be
3234
+ * operands are flagged `fields[P].numeric`.
3235
+ *
3236
+ * @since 0.1.148
3237
+ */
3238
+ aggregateExpressions?: boolean;
2605
3239
  }
2606
3240
  /** Where the action applies on the UI. */
2607
3241
  type TDbActionLevel = "table" | "row" | "rows";
@@ -2687,6 +3321,76 @@ interface TDbActionInfo {
2687
3321
  * @since 0.1.136
2688
3322
  */
2689
3323
  formUrl?: string;
3324
+ /**
3325
+ * Present on an action another controller owns and runs (a view
3326
+ * delegating its source table's row actions): that controller's
3327
+ * server-absolute base path. `value`, `formUrl` and the per-row
3328
+ * `GET {owner}/meta/actions/:id` live there; `disabled` is not sent (the
3329
+ * row's `$actions` verdict is authoritative).
3330
+ *
3331
+ * @since 0.1.147
3332
+ */
3333
+ owner?: string;
3334
+ /**
3335
+ * Delegated action only: the {@link owner}'s identification field → the
3336
+ * path in THIS controller's rows that carries its value. Clients build the
3337
+ * action's `ids` from a row through it. Absent when every pair is
3338
+ * identical and is exactly this controller's `preferredId`.
3339
+ *
3340
+ * @since 0.1.147
3341
+ */
3342
+ idMap?: Record<string, string>;
3343
+ /**
3344
+ * `'rows'` level only: the action also accepts a query target — "every row
3345
+ * matching this filter / search" instead of a list of identifiers — of at
3346
+ * most `maxRows` rows. `url` (server-absolute) is where such a request is
3347
+ * POSTed when it is not `value` (a delegated action: this controller
3348
+ * resolves the query and runs the owner's action in batches).
3349
+ *
3350
+ * @since 0.1.147
3351
+ */
3352
+ queryTarget?: {
3353
+ maxRows: number;
3354
+ url?: string;
3355
+ };
3356
+ }
3357
+ /**
3358
+ * Outcome of an action run over a target (a query target, or the
3359
+ * `@DbActionTarget` handler surface): how many rows the target matched, how
3360
+ * many the handler processed, and the rows left out — `skipped` by the gate
3361
+ * (disabled, out of scope, or `"stale"`: the row no longer matches the query
3362
+ * it was selected by) and `failed` as reported by the handler.
3363
+ *
3364
+ * @since 0.1.147
3365
+ */
3366
+ interface TDbActionTargetSummary {
3367
+ matched: number;
3368
+ processed: number;
3369
+ skipped: {
3370
+ id: Record<string, unknown>;
3371
+ reason?: string;
3372
+ }[];
3373
+ failed: {
3374
+ id: Record<string, unknown>;
3375
+ reason: string;
3376
+ }[];
3377
+ /**
3378
+ * The run stopped early: a batch failed after an earlier batch had run
3379
+ * (those stay applied). Its ids, and every id not reached, are in
3380
+ * `failed`. Absent when the run completed.
3381
+ */
3382
+ aborted?: {
3383
+ status: number;
3384
+ message: string;
3385
+ };
3386
+ /**
3387
+ * A delegated run (a view's query target onto its source's action): the
3388
+ * `message` each batch's source handler returned, in batch order. Absent
3389
+ * when none returned one.
3390
+ */
3391
+ messages?: string[];
3392
+ /** {@link messages}, the distinct ones joined by newlines — for a toast. */
3393
+ message?: string;
2690
3394
  }
2691
3395
  /**
2692
3396
  * `GET /meta/actions/:id` (and `/meta/actions?…`) response: the row-level
@@ -2709,6 +3413,35 @@ interface TDbInsertManyResult {
2709
3413
  insertedCount: number;
2710
3414
  insertedIds: unknown[];
2711
3415
  }
3416
+ /**
3417
+ * `insertOne` result in conflict-ignoring mode (`onConflict: 'ignore'`).
3418
+ * @since 0.1.148
3419
+ */
3420
+ interface TDbInsertIgnoreResult {
3421
+ /** Id of the inserted row; absent when the row was skipped. */
3422
+ insertedId?: unknown;
3423
+ /** `true` when the row collided on the primary key or a unique index and was skipped. */
3424
+ conflict: boolean;
3425
+ }
3426
+ /**
3427
+ * `insertMany` result in conflict-ignoring mode (`onConflict: 'ignore'`):
3428
+ * `insertedCount` / `insertedIds` cover the INSERTED rows only (dense, input
3429
+ * order); `inserted` and `conflicts` map back to input indices.
3430
+ * @since 0.1.148
3431
+ */
3432
+ interface TDbInsertManyIgnoreResult extends TDbInsertManyResult {
3433
+ /** Input index of each `insertedIds` entry. */
3434
+ inserted: number[];
3435
+ /** Input indices skipped because of a unique / primary-key conflict, ascending. */
3436
+ conflicts: number[];
3437
+ }
3438
+ /**
3439
+ * One slot per input row of `BaseDbAdapter.insertManyIgnore`: the inserted id,
3440
+ * or `null` for a skipped (conflicting) row.
3441
+ */
3442
+ type TDbInsertIgnoreSlot = {
3443
+ insertedId: unknown;
3444
+ } | null;
2712
3445
  interface TDbUpdateResult {
2713
3446
  matchedCount: number;
2714
3447
  modifiedCount: number;
@@ -2870,6 +3603,20 @@ interface TDbFieldMeta {
2870
3603
  * rejected.
2871
3604
  */
2872
3605
  derived?: TDerivedColumn;
3606
+ /**
3607
+ * A computed view column (`@db.compute`, since 0.1.147): `operands` are the
3608
+ * logical paths of the view fields its value is computed from — transitive
3609
+ * (a computed operand is replaced by its own operands), never computed
3610
+ * themselves. `via` lists the intermediate computed fields the value is
3611
+ * computed through (transitively; empty when every leaf is a plain field).
3612
+ * Read-only; a computed field must not be visible when one of its operands
3613
+ * or `via` fields is hidden (the same rule as the `@db.writeOnly` seal —
3614
+ * `priority = x * 100 + rank` would otherwise give back a hidden `rank`).
3615
+ */
3616
+ computed?: {
3617
+ operands: readonly string[];
3618
+ via: readonly string[];
3619
+ };
2873
3620
  }
2874
3621
  interface TValueFormatterPair {
2875
3622
  /** Converts a JS value to storage representation (write + filter paths). */
@@ -2885,6 +3632,24 @@ interface TDbForeignKey {
2885
3632
  targetTable: string;
2886
3633
  /** Target field names on the referenced table. */
2887
3634
  targetFields: string[];
3635
+ /**
3636
+ * Physical column names of {@link fields} (after `@db.column` renames and
3637
+ * flattening), in the same order. Use these for DDL, constraint sync, the
3638
+ * FK diff and the schema snapshot; `fields` stays logical (query / relation
3639
+ * pairing). Absent → same as `fields`.
3640
+ */
3641
+ physicalFields?: string[];
3642
+ /**
3643
+ * Physical column names of {@link targetFields} on the referenced table
3644
+ * (its `@db.column` renames), in the same order. Absent → same as `targetFields`.
3645
+ */
3646
+ physicalTargetFields?: string[];
3647
+ /**
3648
+ * `@db.schema` of the referenced table, when it declares one — SQL DDL
3649
+ * qualifies `REFERENCES` with it (a table in another schema). Not part of
3650
+ * the schema snapshot.
3651
+ */
3652
+ targetSchema?: string;
2888
3653
  /** Lazy reference to the target annotated type (for on-demand table resolution). */
2889
3654
  targetTypeRef?: () => TAtscriptAnnotatedType;
2890
3655
  /** Alias grouping FK fields (if any). */
@@ -3074,7 +3839,7 @@ interface AtscriptDbTableLike {
3074
3839
  relations: ReadonlyMap<string, TDbRelation>;
3075
3840
  foreignKeys: ReadonlyMap<string, TDbForeignKey>;
3076
3841
  getMetadata(): TableMetadata;
3077
- isValidFieldPath(path: string, visited?: Set<string>): boolean;
3842
+ isValidFieldPath(path: string): boolean;
3078
3843
  }
3079
3844
  /**
3080
3845
  * Nested FROM re-entry option (internal): pins every child's foreign key
@@ -3170,6 +3935,18 @@ interface TDbRelation {
3170
3935
  isArray: boolean;
3171
3936
  /** Junction type reference for 'via' (M:N) relations. */
3172
3937
  viaType?: () => TAtscriptAnnotatedType;
3938
+ /**
3939
+ * `@db.rel.filterable` — HTTP clients may filter the parent rows by this
3940
+ * relation (`{ nav: { $some | $none: … } }`). Server-side code may always.
3941
+ * @since 0.1.147
3942
+ */
3943
+ filterable?: boolean;
3944
+ /**
3945
+ * `@db.rel.filter` condition: part of the relation's meaning — applied when
3946
+ * the relation is loaded (`$with`) and inside relational predicates.
3947
+ * @since 0.1.147
3948
+ */
3949
+ filter?: AtscriptQueryNode;
3173
3950
  }
3174
3951
  /**
3175
3952
  * Write payload for insert / patch paths: every key optional, and optional
@@ -3295,6 +4072,21 @@ interface TWriteOptions<Row = Record<string, unknown>> extends TIdResolveOptions
3295
4072
  */
3296
4073
  check?: TDbWriteCheck;
3297
4074
  }
4075
+ /**
4076
+ * Options of `insertOne` / `insertMany`.
4077
+ * @since 0.1.148
4078
+ */
4079
+ interface TInsertOptions<Row = Record<string, unknown>> extends TWriteOptions<Row> {
4080
+ /**
4081
+ * `'error'` (default): a unique / primary-key violation throws `CONFLICT`.
4082
+ * `'ignore'`: rows that collide on the primary key or any unique index —
4083
+ * with a stored row or an earlier row of the same batch — are skipped and
4084
+ * reported by input index. Everything else (validation, NOT NULL, FK, check,
4085
+ * guard) still throws and rolls the call back. A row that creates a related
4086
+ * parent (`@db.rel.to` nested object) is rejected in this mode.
4087
+ */
4088
+ onConflict?: "error" | "ignore";
4089
+ }
3298
4090
  /**
3299
4091
  * Context handed to a write {@link TWriteOptions.check} (since 0.1.143) — and
3300
4092
  * through it to `AsDbController.checkWrite()`. Lets a permission layer verify
@@ -3433,7 +4225,14 @@ interface TBucketFieldSource {
3433
4225
  declare function normalizeComputedSelect(controls: {
3434
4226
  $select?: unknown;
3435
4227
  $groupBy?: unknown;
4228
+ $rowOrder?: unknown;
3436
4229
  } | undefined, fields: TBucketFieldSource, aggregate?: boolean): ResolvedBucket[];
4230
+ /** The normalized computed `$select` entries of a grouped query — see {@link resolveComputedSelect}. */
4231
+ interface TComputedSelect {
4232
+ buckets: ResolvedBucket[];
4233
+ exprs: ResolvedSelectExpr[];
4234
+ rowOrder?: ResolvedRowOrderKey[];
4235
+ }
3437
4236
  /**
3438
4237
  * @deprecated since 0.1.136 — renamed {@link normalizeComputedSelect} (it
3439
4238
  * normalizes every computed `$select` entry, aggregates included).
@@ -3464,4 +4263,4 @@ declare function isJsonValueField(fd: TDbFieldMeta): boolean;
3464
4263
  */
3465
4264
  declare function jsonValueAncestor(path: string, jsonValueParents: ReadonlySet<string>): string | undefined;
3466
4265
  //#endregion
3467
- export { TDbUpdateResult as $, AtscriptRef as $t, TCrudPermissions as A, TViewJsonType as At, TDbFieldMeta as B, ALL_BUCKET_UNITS as Bt, NullableOptional as C, TRowResolveOptions as Ct, TCascadeTarget as D, TTableResolver as Dt, TCascadeResolver as E, TTableOptionDiff as Et, TDbAvailableActions as F, UniqueryControls$1 as Ft, TDbInsertManyResult as G, AtscriptDbView as Gt, TDbIndex as H, DbSpace as Ht, TDbCollation as I, WithRelation$1 as It, TDbReferentialAction as J, isViewType as Jt, TDbInsertResult as K, TViewColumnMapping as Kt, TDbDefaultFn as L, TableMetadata as Lt, TDbActionIntent as M, TWriteTableResolver as Mt, TDbActionLevel as N, TypedWithRelation as Nt, TColumnDiff as O, TTouchManyOptions as Ot, TDbActionProcessor as P, Uniquery$1 as Pt, TDbStorageType as Q, AtscriptQueryNode$1 as Qt, TDbDefaultValue as R, isGeoIndexableType as Rt, NavPropsOf$1 as S, TRelationInfo as St, PrimaryKeyOf$1 as T, TSyncColumnResult as Tt, TDbIndexField as U, TAdapterFactory as Ut, TDbForeignKey as V, BaseDbAdapter as Vt, TDbIndexType as W, TDbSpaceOptions as Wt, TDbRemoveGuard as X, AtscriptQueryComparison as Xt, TDbRelation as Y, aliasTargetOf as Yt, TDbRemoveGuardContext as Z, AtscriptQueryFieldRef$1 as Zt, DbQuery as _, UniquSelect as _n, TIdentification as _t, jsonValueAncestor as a, IntegrityStrategy as an, TDeleteOptions as at, FilterExpr$1 as b, TPrimaryKeyChange as bt, AggregateControls as c, DbResponse as cn, TEnsureTableOptions as ct, AggregateQuery$1 as d, TDbEncryptionOptions as dn, TExistingTableOption as dt, TViewJoin as en, TDbWriteAction as et, AggregateResult as f, DocumentFieldMapper as fn, TFieldMeta as ft, DbPatch as g, TGenericLogger as gn, TIdResolveOptions as gt, DbControls as h, NoopLogger as hn, TIdDescriptor as ht, isJsonValueField as i, AtscriptDbTable as in, TDbWriteGuardContext as it, TDbActionInfo as j, TWriteOptions as jt, TCrudOp as k, TValueFormatterPair as kt, AggregateExpr$1 as l, resolveDesignType as ln, TExistingColumn as lt, AtscriptDbWritable as m, TReadControls as mn, TFkLookupTarget as mt, TResolvedBucket as n, isFieldRef as nn, TDbWriteCheckContext as nt, normalizeComputedSelect as o, NativeIntegrity as on, TDerivedChangeReason as ot, AtscriptDbTableLike as p, FieldMappingStrategy as pn, TFkLookupResolver as pt, TDbObjectKind as q, isAtscriptDbView as qt, isBucketableField as r, translateQueryTree as rn, TDbWriteGuard as rt, resolveCalendarBuckets as s, AtscriptDbReadable as sn, TDerivedColumn as st, TBucketFieldSource as t, TViewPlan as tn, TDbWriteCheck as tt, AggregateFn$1 as u, DbEncryption as un, TExistingForeignKey as ut, DbRow as v, TMetaResponse as vt, OwnPropsOf$1 as w, TSearchIndexInfo as wt, FlatOf$1 as x, TReferencingForeignKey as xt, FieldOpsFor as y, TMetadataOverrides as yt, TDbDeleteResult as z, isGeoPointType as zt };
4266
+ export { TDbReferentialAction as $, AtscriptDbView as $t, TCrudOp as A, containsRelationFilter as An, TSearchIndexInfo as At, TDbDefaultValue as B, NoopLogger as Bn, Uniquery$1 as Bt, NavPropsOf$1 as C, REL_FILTER_MAX_NODES as Cn, TInsertOptions as Ct, TCascadeResolver as D, TRelationFilterTable as Dn, TReferencingForeignKey as Dt, PrimaryKeyOf$1 as E, TRelationFilterJunction as En, TPrimaryKeyChange as Et, TDbActionProcessor as F, DbEncryption as Fn, TValueFormatterPair as Ft, TDbIndexField as G, UniquSelect as Gn, isGeoPointType as Gt, TDbFieldMeta as H, TExprAggregate as Hn, WithRelation$1 as Ht, TDbActionTargetSummary as I, TDbEncryptionOptions as In, TViewJsonType as It, TDbInsertIgnoreSlot as J, BaseDbAdapter as Jt, TDbIndexType as K, ALL_BUCKET_UNITS as Kt, TDbAvailableActions as L, DocumentFieldMapper as Ln, TWriteOptions as Lt, TDbActionInfo as M, hasRelationOp as Mn, TTableOptionDiff as Mt, TDbActionIntent as N, isResolvedRelationFilter as Nn, TTableResolver as Nt, TCascadeTarget as O, TRelationStaticFilter as On, TRelationInfo as Ot, TDbActionLevel as P, relationStaticFilter as Pn, TTouchManyOptions as Pt, TDbObjectKind as Q, TDbSpaceOptions as Qt, TDbCollation as R, FieldMappingStrategy as Rn, TWriteTableResolver as Rt, FlatOf$1 as S, REL_FILTER_MAX_DEPTH as Sn, TIdentification as St, OwnPropsOf$1 as T, TRelGuardState as Tn, TMetadataOverrides as Tt, TDbForeignKey as U, TFirstLast as Un, TableMetadata as Ut, TDbDeleteResult as V, TGenericLogger as Vn, UniqueryControls$1 as Vt, TDbIndex as W, TRowOrderKey as Wn, isGeoIndexableType as Wt, TDbInsertManyResult as X, DbSpace as Xt, TDbInsertManyIgnoreResult as Y, TViewCapability as Yt, TDbInsertResult as Z, TAdapterFactory as Zt, DbPatch as _, NativeIntegrity as _n, TFieldMeta as _t, isJsonValueField as a, AtscriptOrderItem as an, TDbWriteAction as at, FieldOpsFor as b, DbResponse as bn, TIdDescriptor as bt, resolveCalendarBuckets as c, AtscriptQueryNode$1 as cn, TDbWriteGuard as ct, AggregateFn$1 as d, TViewPlan as dn, TDerivedChangeReason as dt, TViewColumnMapping as en, TDbRelation as et, AggregateQuery$1 as f, evaluateExpr as fn, TDerivedColumn as ft, DbControls as g, IntegrityStrategy as gn, TExistingTableOption as gt, AtscriptDbWritable as h, AtscriptDbTable as hn, TExistingForeignKey as ht, isBucketableField as i, AtscriptExprNode$1 as in, TDbUpdateResult as it, TCrudPermissions as j, forEachResolvedRelation as jn, TSyncColumnResult as jt, TColumnDiff as k, andFilters as kn, TRowResolveOptions as kt, AggregateControls as l, AtscriptRef as ln, TDbWriteGuardContext as lt, AtscriptDbTableLike as m, translateQueryTree as mn, TExistingColumn as mt, TComputedSelect as n, isViewType as nn, TDbRemoveGuardContext as nt, jsonValueAncestor as o, AtscriptQueryComparison as on, TDbWriteCheck as ot, AggregateResult as p, isFieldRef as pn, TEnsureTableOptions as pt, TDbInsertIgnoreResult as q, ALL_VIEW_CAPABILITIES as qt, TResolvedBucket as r, aliasTargetOf as rn, TDbStorageType as rt, normalizeComputedSelect as s, AtscriptQueryFieldRef$1 as sn, TDbWriteCheckContext as st, TBucketFieldSource as t, isAtscriptDbView as tn, TDbRemoveGuard as tt, AggregateExpr$1 as u, TViewJoin as un, TDeleteOptions as ut, DbQuery as v, TCascadePin as vn, TFkLookupResolver as vt, NullableOptional as w, ResolvedRelationFilter as wn, TMetaResponse as wt, FilterExpr$1 as x, resolveDesignType as xn, TIdResolveOptions as xt, DbRow as y, AtscriptDbReadable as yn, TFkLookupTarget as yt, TDbDefaultFn as z, TReadControls as zn, TypedWithRelation as zt };