@atscript/db 0.1.142 → 0.1.144

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.d.cts +1 -1
  2. package/dist/agg.d.mts +1 -1
  3. package/dist/{buckets-Bv4pah66.d.cts → buckets-Ba5lP_o9.d.cts} +358 -17
  4. package/dist/{buckets-CjL7F-hp.d.mts → buckets-BlJ4cdlr.d.mts} +358 -17
  5. package/dist/{column-diff-DiBbXyLA.d.cts → column-diff-BlGxPobU.d.cts} +1 -1
  6. package/dist/{column-diff-CfPNcP6e.cjs → column-diff-D_Kyuh0S.cjs} +906 -209
  7. package/dist/{column-diff-n-k5KY0u.d.mts → column-diff-PqA_edXA.d.mts} +1 -1
  8. package/dist/{column-diff-CmFNXV8C.mjs → column-diff-e2oHc71_.mjs} +850 -177
  9. package/dist/index.cjs +16 -10
  10. package/dist/index.d.cts +30 -5
  11. package/dist/index.d.mts +30 -5
  12. package/dist/index.mjs +6 -5
  13. package/dist/nested-writer-CnOOAehr.mjs +880 -0
  14. package/dist/nested-writer-xfQwxplL.cjs +1029 -0
  15. package/dist/{object-DSN0h9lB.d.cts → object-CtPYTIvy.d.cts} +3 -1
  16. package/dist/{object-DSN0h9lB.d.mts → object-CtPYTIvy.d.mts} +3 -1
  17. package/dist/object-Djg28csK.cjs +101 -0
  18. package/dist/object-TkiJQ-Dp.mjs +66 -0
  19. package/dist/rel.cjs +5 -2
  20. package/dist/rel.d.cts +76 -10
  21. package/dist/rel.d.mts +76 -10
  22. package/dist/rel.mjs +3 -3
  23. package/dist/{relation-helpers-B59to_dG.d.mts → relation-helpers-JTEyZzVd.d.cts} +1 -1
  24. package/dist/{relation-helpers-DQ_nRsV9.d.cts → relation-helpers-MTwzaSGA.d.mts} +1 -1
  25. package/dist/{relation-loader-CgJ8bK6X.cjs → relation-loader-CBPY6kM7.cjs} +1 -1
  26. package/dist/{relation-loader-CuhEBzFU.mjs → relation-loader-D9XuXaMv.mjs} +1 -1
  27. package/dist/sync.cjs +1 -1
  28. package/dist/sync.d.cts +2 -2
  29. package/dist/sync.d.mts +2 -2
  30. package/dist/sync.mjs +1 -1
  31. package/dist/{validator-DASnXf1j.cjs → validator-CVS-onRg.cjs} +0 -101
  32. package/dist/{validator-D8bPsXPN.mjs → validator-Clu2q_7z.mjs} +1 -66
  33. package/dist/validator.cjs +4 -3
  34. package/dist/validator.d.cts +1 -1
  35. package/dist/validator.d.mts +1 -1
  36. package/dist/validator.mjs +2 -1
  37. package/package.json +6 -6
  38. package/dist/nested-writer-BO3vhbkP.mjs +0 -661
  39. package/dist/nested-writer-DYsRxZ5f.cjs +0 -768
package/dist/agg.d.cts CHANGED
@@ -1,3 +1,3 @@
1
- import { n as TResolvedBucket } from "./buckets-Bv4pah66.cjs";
1
+ import { n as TResolvedBucket } from "./buckets-Ba5lP_o9.cjs";
2
2
  import { _ as TDbAggregateFn, a as AggregateResult, c as CalendarBucketLabel, d as WeekStart, f as isAggregateExpr, h as resolveAlias, i as AggregateQuery, l as ComputedExpr, m as resolveAggregateSearch, n as AggregateExpr, o as BucketExpr, p as isBucketExpr, r as AggregateFn, s as BucketUnit, t as AggregateControls, u as ResolvedBucket, v as assertAggregateFn } from "./agg-CV7y8nC6.cjs";
3
3
  export { type AggregateControls, type AggregateExpr, type AggregateFn, type AggregateQuery, type AggregateResult, type BucketExpr, type BucketUnit, type CalendarBucketLabel, type ComputedExpr, type ResolvedBucket, type TDbAggregateFn, type TResolvedBucket, type WeekStart, assertAggregateFn, isAggregateExpr, isBucketExpr, resolveAggregateSearch, resolveAlias };
package/dist/agg.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { n as TResolvedBucket } from "./buckets-CjL7F-hp.mjs";
1
+ import { n as TResolvedBucket } from "./buckets-BlJ4cdlr.mjs";
2
2
  import { _ as TDbAggregateFn, a as AggregateResult, c as CalendarBucketLabel, d as WeekStart, f as isAggregateExpr, h as resolveAlias, i as AggregateQuery, l as ComputedExpr, m as resolveAggregateSearch, n as AggregateExpr, o as BucketExpr, p as isBucketExpr, r as AggregateFn, s as BucketUnit, t as AggregateControls, u as ResolvedBucket, v as assertAggregateFn } from "./agg-D5DHsAby.mjs";
3
3
  export { type AggregateControls, type AggregateExpr, type AggregateFn, type AggregateQuery, type AggregateResult, type BucketExpr, type BucketUnit, type CalendarBucketLabel, type ComputedExpr, type ResolvedBucket, type TDbAggregateFn, type TResolvedBucket, type WeekStart, assertAggregateFn, isAggregateExpr, isBucketExpr, resolveAggregateSearch, resolveAlias };
@@ -465,6 +465,27 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
465
465
  get foreignKeys(): ReadonlyMap<string, TDbForeignKey>;
466
466
  /** Navigational relation metadata from `@db.rel.to` / `@db.rel.from`. */
467
467
  get relations(): ReadonlyMap<string, TDbRelation>;
468
+ /**
469
+ * The `@db.rel.FK` entry a `@db.rel.to` relation is backed by — paired
470
+ * exactly like relation loading and nested writes pair them: by the
471
+ * relation's alias when it has one, else by the target table. `undefined`
472
+ * for an unknown name, a `@db.rel.from` / `@db.rel.via` relation (their key
473
+ * lives on the other table) or a TO relation without a matching FK.
474
+ *
475
+ * @since 0.1.143
476
+ */
477
+ foreignKeyOf(relationName: string): TDbForeignKey | undefined;
478
+ /**
479
+ * Logical paths stored as ONE JSON column (`storage: "json"` — `@db.json`
480
+ * fields and nested objects / arrays a relational adapter serializes): the
481
+ * engine cannot address a sub-path of such a column in a projection,
482
+ * filter or sort, so a permission layer treats it atomically (visible whole
483
+ * or not at all). Navigation fields excluded; empty on document adapters
484
+ * (they store nested values natively).
485
+ *
486
+ * @since 0.1.143
487
+ */
488
+ get jsonParents(): ReadonlySet<string>;
468
489
  /** The underlying database adapter instance. */
469
490
  get dbAdapter(): A;
470
491
  /**
@@ -484,6 +505,28 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
484
505
  * of them. Every read path funnels through here.
485
506
  */
486
507
  private _fromRead;
508
+ /**
509
+ * Translates a read query for the adapter. A `$select` that leaves out a
510
+ * key a `$with` relation joins on — a TO relation's foreign key, the key a
511
+ * FROM / VIA relation is looked up by — is widened with it for the read
512
+ * (since 0.1.143), and {@link _finishRead} strips it again once the
513
+ * relations are loaded: the joined object never reads `null` just because
514
+ * its key was not selected.
515
+ */
516
+ private _translateRead;
517
+ /** Reconstructs + decrypts a read's rows, loads its `$with` relations and strips widened keys. */
518
+ private _finishRead;
519
+ /** `$select` plus the join keys of `withRelations` it leaves out — `undefined` when none is missing. */
520
+ private _widenSelectForWith;
521
+ private _joinKeysCache?;
522
+ /**
523
+ * The keys of THIS table a `$with` relation joins on: a TO relation's
524
+ * foreign-key fields; the fields a FROM relation's (or a VIA junction's)
525
+ * foreign key references — typically the primary key. An adapter that
526
+ * loads relations natively re-reads the rows by primary key, so it is
527
+ * always included there. Empty for an unknown or nested (`a.b`) name.
528
+ */
529
+ private _joinKeysOf;
487
530
  /**
488
531
  * Pre-computed field metadata for adapter use.
489
532
  */
@@ -656,6 +699,10 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
656
699
  * or single-field unique index.
657
700
  * The return type excludes nav props unless `$with` is provided in controls.
658
701
  *
702
+ * The id addresses exactly ONE row, primary key first (since 0.1.143) —
703
+ * see {@link resolveRowFilter}: when a scalar id equals one row's primary
704
+ * key and another row's unique key, the primary-key row is returned.
705
+ *
659
706
  * ```typescript
660
707
  * // Without relations — nav props stripped from result
661
708
  * const user = await table.findById('123')
@@ -667,25 +714,100 @@ declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnn
667
714
  findById<Q extends {
668
715
  controls?: UniqueryControls<OwnProps, NavType>;
669
716
  } = Record<string, never>>(id: IdType, query?: Q): Promise<DbResponse<DataType, NavType, Q> | null>;
717
+ /**
718
+ * Reads the ONE row an id addresses — resolved exactly like
719
+ * {@link resolveRowFilter} (primary key first, `opts.scope` /
720
+ * `opts.isFieldVisible` as there) — with `opts.controls` applied, in one
721
+ * step: the identifications are probed in order with the caller's
722
+ * controls and the first row found wins, so no pin-then-reread. The row
723
+ * must also match `opts.scope` (an out-of-scope row answers `null` like a
724
+ * missing one).
725
+ *
726
+ * @since 0.1.143
727
+ */
728
+ findOneByRow<Q extends {
729
+ controls?: UniqueryControls<OwnProps, NavType>;
730
+ } = Record<string, never>>(id: unknown, opts?: TRowResolveOptions & Q): Promise<DbResponse<DataType, NavType, Q> | null>;
670
731
  /**
671
732
  * Resolve an id value (scalar or object) into a {@link FilterExpr} using the
672
- * same identification resolution as {@link findById}. Public so callers can
733
+ * same identifications as {@link findById}. Public so callers can
673
734
  * AND-combine the id-filter with a row-level read overlay before issuing
674
735
  * `findOne` (avoiding the existence leak that `findById` would cause).
675
736
  * `opts.isFieldVisible` (since 0.1.134) drops unique indexes over hidden
676
737
  * fields — see {@link identificationsVisibleTo}.
738
+ *
739
+ * The result is a plain `$or` over every identification the id is
740
+ * type-compatible with (a scalar can equal one row's primary key AND
741
+ * another row's unique key), so it may match more than one row. Code that
742
+ * must address exactly one row — writes, pre-images, `/one` reads — uses
743
+ * {@link resolveRowFilter} instead.
677
744
  */
678
745
  resolveIdFilter(id: unknown, opts?: TIdResolveOptions): FilterExpr | null;
679
746
  /**
680
- * Resolve an id value into a filter expression.
747
+ * Resolve an id value (scalar or object) into a filter that matches exactly
748
+ * ONE row, deterministically and primary key first (since 0.1.143):
749
+ *
750
+ * - an id that yields a single identification (e.g. a numeric PK with no
751
+ * type-compatible unique key) resolves to it without a read;
752
+ * - an object id carrying the complete primary key resolves by the primary
753
+ * key alone — exactly like a write payload is identified;
754
+ * - otherwise the identifications are tried in order (primary key first,
755
+ * then each unique index): the first one that matches a row wins and the
756
+ * result is that row's exact primary-key filter;
757
+ * - when none matches, the first identification is returned (it matches
758
+ * nothing, so callers answer "not found" as usual).
759
+ *
760
+ * `null` when the id resolves to no identification at all. Every write
761
+ * (`deleteOne`, the guards' `current()`) and `findById` go through this
762
+ * resolution; call it inside the write's transaction when the answer must
763
+ * stay pinned. `opts.isFieldVisible` as in {@link resolveIdFilter}.
764
+ *
765
+ * `opts.scope` (a row-level overlay) restricts which rows count as matches
766
+ * while the identifications are tried — a row outside it never shadows one
767
+ * inside it, so the answer is the same as if that row did not exist. AND
768
+ * the scope onto the result to exclude an out-of-scope row the id names
769
+ * unambiguously. See {@link TRowResolveOptions}.
770
+ */
771
+ resolveRowFilter(id: unknown, opts?: TRowResolveOptions): Promise<FilterExpr | null>;
772
+ /**
773
+ * Resolve an id value into a filter expression (the `$or` of
774
+ * {@link _idCandidates}; a single candidate is returned as-is).
775
+ */
776
+ protected _resolveIdFilter(id: unknown, opts?: TIdResolveOptions): FilterExpr | null;
777
+ /**
778
+ * The ordered identification filters an id value can resolve through —
779
+ * primary key first, then each unique index.
681
780
  *
682
781
  * When `preferredId` differs from the PK, scalar ids resolve only against
683
782
  * the preferred field (deterministic addressing). Otherwise scalars try PK
684
783
  * + every single-field unique index; objects try PK + compound unique
685
784
  * indexes. With `opts.isFieldVisible`, only the identifications
686
- * {@link identificationsVisibleTo} keeps are tried.
785
+ * {@link identificationsVisibleTo} keeps are tried. With `pkWins` (the
786
+ * default), an object id carrying the complete primary key yields the
787
+ * primary-key filter alone.
687
788
  */
688
- protected _resolveIdFilter(id: unknown, opts?: TIdResolveOptions): FilterExpr | null;
789
+ protected _idCandidates(id: unknown, opts?: TIdResolveOptions, pkWins?: boolean): FilterExpr[];
790
+ /**
791
+ * Picks the one row a list of identification candidates addresses — see
792
+ * {@link resolveRowFilter}. A lone candidate needs no read. With a
793
+ * non-empty `scope`, only rows matching it count as matches.
794
+ */
795
+ protected _pinIdCandidates(candidates: FilterExpr[], scope?: FilterExpr): Promise<FilterExpr | null>;
796
+ /** Sequential fallback of {@link _pinIdCandidates}: first candidate matching a row wins. */
797
+ private _probeIdCandidates;
798
+ /**
799
+ * The exact primary-key filter of `row` (values prepared like ids) — `null`
800
+ * when the table has no primary key or `row` lacks a key field.
801
+ */
802
+ protected _pkFilterFrom(row: Record<string, unknown>): FilterExpr | null;
803
+ /**
804
+ * Reads the row `filter` (AND `scope`) matches and returns its exact
805
+ * primary-key filter (`filter` itself on a table without one); `undefined`
806
+ * when no row matches.
807
+ */
808
+ protected _readPkFilter(filter: FilterExpr, scope?: FilterExpr): Promise<FilterExpr | undefined>;
809
+ /** `filter` AND a row scope — `filter` itself when the scope is absent or empty. */
810
+ protected _andScope(filter: FilterExpr, scope?: FilterExpr): FilterExpr;
689
811
  /** Build a single-key filter from `idObj` over `fields`, or null if any field is missing/incompatible. */
690
812
  private _tryCompoundFilter;
691
813
  /**
@@ -777,7 +899,9 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
777
899
  * Recursive up to `maxDepth` (default 3).
778
900
  *
779
901
  * `opts.guard` (since 0.1.128) runs once inside the transaction, after
780
- * defaults + validation, with the prepared rows — see {@link TWriteOptions}.
902
+ * defaults + validation, with the prepared rows; `opts.check` (since
903
+ * 0.1.143) runs once after every phase with the inserted rows' primary-key
904
+ * filters — see {@link TWriteOptions}.
781
905
  */
782
906
  insertMany(payloads: Array<DbPatch<DataType>>, opts?: TWriteOptions<DataType>): Promise<TDbInsertManyResult>;
783
907
  /**
@@ -794,7 +918,9 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
794
918
  * re-create junction rows. Fully recursive up to `maxDepth` (default 3).
795
919
  *
796
920
  * `opts.guard` (since 0.1.128) runs once inside the transaction, after
797
- * `$cas` extraction, defaults + validation — see {@link TWriteOptions}.
921
+ * `$cas` extraction, defaults + validation; `opts.check` (since 0.1.143)
922
+ * after every phase — see {@link TWriteOptions}. The nested phases run only
923
+ * for the rows the main replace matched.
798
924
  */
799
925
  bulkReplace(payloads: Array<DbRow<DataType>>, opts?: TWriteOptions<DataType>): Promise<TDbUpdateResult>;
800
926
  /**
@@ -811,7 +937,10 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
811
937
  *
812
938
  * `opts.guard` (since 0.1.128) runs once inside the transaction, after
813
939
  * `$cas` extraction and validation, with the patches (identifying fields
814
- * present, `$cas` removed) — see {@link TWriteOptions}.
940
+ * present, `$cas` removed); `opts.check` (since 0.1.143) after every
941
+ * phase — see {@link TWriteOptions}. The nested phases run only for the
942
+ * rows the main patch matched; a nested TO object patches the row the
943
+ * STORED foreign key references.
815
944
  */
816
945
  bulkUpdate(payloads: Array<DbPatch<DataType>>, opts?: TWriteOptions<DataType>): Promise<TDbUpdateResult>;
817
946
  /**
@@ -842,13 +971,22 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
842
971
  touchMany(keys: Array<DbPatch<DataType>>, opts?: TTouchManyOptions): Promise<TDbUpdateResult>;
843
972
  /**
844
973
  * Deletes a single record by any type-compatible identifier — primary key
845
- * or single-field unique index. Uses the same resolution logic as `findById`.
974
+ * or single-field unique index. Uses the same resolution logic as `findById`:
975
+ * the id addresses exactly ONE row, primary key first (since 0.1.143 — see
976
+ * {@link resolveRowFilter}); an id that could name several rows is pinned
977
+ * inside the transaction, so the guard, the cascade and the delete all see
978
+ * the same row.
846
979
  *
847
980
  * When the adapter does not support native foreign keys (e.g. MongoDB),
848
981
  * cascade and setNull actions are applied before the delete.
849
982
  *
850
983
  * `opts.guard` (since 0.1.128) runs inside the transaction once the id has
851
984
  * resolved to a filter, before cascade / delete — see {@link TDeleteOptions}.
985
+ * `opts.scope` (since 0.1.143) is a row scope: an ambiguous id is pinned
986
+ * among in-scope rows only (see {@link TRowResolveOptions}) and the delete
987
+ * — guard `current()` and cascade included — targets the row only while it
988
+ * matches the scope, so an out-of-scope row answers `{ deletedCount: 0 }`
989
+ * exactly like a missing one.
852
990
  * An id that resolves to no filter answers `{ deletedCount: 0 }` without
853
991
  * calling the guard.
854
992
  */
@@ -880,12 +1018,60 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
880
1018
  */
881
1019
  protected _encryptItems(items: Array<Record<string, unknown>>, mode: "write" | "patch"): Promise<void>;
882
1020
  /**
883
- * Lazy pre-image read for a guard's `current(i)`: `null` when the row has
884
- * no identifying key (e.g. an auto-increment insert) or the key cannot be
885
- * resolved — never throws for a missing key.
1021
+ * The record filter a guard's `current(i)` reads its pre-image by: `null`
1022
+ * when the row has no identifying key (e.g. an auto-increment insert) or
1023
+ * the key cannot be resolved — never throws for a missing key. Identified
1024
+ * exactly like the write itself (primary key first, then a unique index —
1025
+ * since 0.1.143), so it is always the row the write targets.
886
1026
  * @internal
887
1027
  */
888
- _readPreImage(row: unknown): Promise<DataType | null>;
1028
+ _recordFilterOrNull(row: unknown, opts?: TIdResolveOptions): FilterExpr | null;
1029
+ /**
1030
+ * Each item's record filter (see {@link _extractRecordFilter}). A nested
1031
+ * FROM re-entry (`_ownedBy`) also pins the child's foreign key to its
1032
+ * parent, so the write never touches a child of another parent.
1033
+ */
1034
+ private _rowFilters;
1035
+ /** Whether `filter` names every primary-key field (so it IS the row's exact key). */
1036
+ private _isPkFilter;
1037
+ /**
1038
+ * The TO relations with their single-field local foreign key (lazily
1039
+ * listed) — what a write pins per item for its nested TO phase.
1040
+ */
1041
+ private _toRelationsCache?;
1042
+ private _toRelations;
1043
+ /** Whether `item` carries a nested TO object (Phase 1 work). */
1044
+ private _carriesNestedTo;
1045
+ /**
1046
+ * Reads — once per write call, inside its transaction — the stored row
1047
+ * each item `need`s, by the item's record filter: its primary key, version
1048
+ * and single-field TO foreign keys, in ONE read for the batch. A pre-image
1049
+ * the guard's `current(i)` already read by the same filter is reused.
1050
+ * `null` = no such row; `undefined` = not needed.
1051
+ */
1052
+ private _pinTargets;
1053
+ /**
1054
+ * The exact primary-key filter of every row the write matched: the record
1055
+ * filter itself when it is the primary key, else the pinned row's key.
1056
+ */
1057
+ private _writtenPkFilters;
1058
+ /** Invokes a {@link TWriteOptions.check} with de-duplicated PK filters (inside the transaction). */
1059
+ private _runWriteCheck;
1060
+ /**
1061
+ * The exact primary-key filter of each inserted row: the logical key from
1062
+ * the row (SDK defaults applied), else the stored key the adapter wrote into
1063
+ * the prepared row (e.g. a driver-assigned `_id`), else — single-field keys
1064
+ * only — the adapter's `insertedIds` (auto-increment).
1065
+ */
1066
+ private _insertedPkFilters;
1067
+ /**
1068
+ * Drops the nested TO objects of every replace item whose main replace
1069
+ * cannot match (pinned row missing, or stale `$cas`) so Phase 1 never
1070
+ * writes a related row for a replace that does nothing. Returns, per item,
1071
+ * whether its TO objects stay (a later 0-match for such an item is a
1072
+ * concurrent change and rolls back).
1073
+ */
1074
+ private _gateNestedTo;
889
1075
  /**
890
1076
  * Applies `@db.default` values in place to a row's absent fields — the
891
1077
  * defaults pass every insert / replace path runs before validation.
@@ -905,6 +1091,16 @@ declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnota
905
1091
  * validator reports it against the field instead of a bare `SyntaxError`.
906
1092
  */
907
1093
  private _parseValueDefault;
1094
+ /**
1095
+ * The filter an `updateOne` / `replaceOne` of `payload` targets its row by
1096
+ * — the write's own resolution (primary key first, then a unique index;
1097
+ * see {@link _extractRecordFilter}), so a caller explaining a write's
1098
+ * outcome (e.g. a CAS mismatch) reads exactly the row the write addressed.
1099
+ * Throws `NOT_FOUND` when the payload carries no identifying fields.
1100
+ *
1101
+ * @since 0.1.143
1102
+ */
1103
+ recordFilter(payload: Record<string, unknown>, opts?: TIdResolveOptions): FilterExpr;
908
1104
  /**
909
1105
  * Extracts a record-identifying filter from a payload.
910
1106
  *
@@ -1077,6 +1273,12 @@ interface TViewColumnMapping {
1077
1273
  */
1078
1274
  aggFilter?: AtscriptQueryNode;
1079
1275
  }
1276
+ /**
1277
+ * Whether `type` declares a view (managed `@db.view.for` or external
1278
+ * `@db.view`) — how `DbSpace.get` tells views from tables.
1279
+ * @since 0.1.143
1280
+ */
1281
+ declare function isViewType(type: TAtscriptAnnotatedType): boolean;
1080
1282
  /**
1081
1283
  * Database view abstraction driven by Atscript `@db.view.*` annotations.
1082
1284
  *
@@ -1094,6 +1296,12 @@ declare class AtscriptDbView<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
1094
1296
  private _viewPlan?;
1095
1297
  private _columnMappings?;
1096
1298
  get isView(): boolean;
1299
+ /**
1300
+ * Builds the view's metadata — first stamping its fields with their
1301
+ * sources' read seals (`inheritViewFieldSeals`, once per view type), so
1302
+ * every metadata consumer sees the sealed type.
1303
+ */
1304
+ protected _ensureBuilt(): void;
1097
1305
  /**
1098
1306
  * Whether this is an external view — declared with `@db.view` only,
1099
1307
  * without `@db.view.for`. External views reference pre-existing DB views
@@ -1385,6 +1593,14 @@ declare abstract class BaseDbAdapter {
1385
1593
  * Adapters use this to retrieve DB-specific state (e.g., MongoDB `ClientSession`).
1386
1594
  */
1387
1595
  protected _getTransactionState(): unknown;
1596
+ /**
1597
+ * `true` when the current async context runs inside a REAL transaction of
1598
+ * this adapter (its owner) — one a throw rolls back (since 0.1.143).
1599
+ * `false` outside any transaction and inside a pass-through
1600
+ * `withTransaction` (adapters without transaction primitives such as the
1601
+ * in-memory one, a standalone MongoDB topology).
1602
+ */
1603
+ isInTransaction(): boolean;
1388
1604
  /**
1389
1605
  * Runs `fn` inside the transaction ALS context with the given state.
1390
1606
  * Adapters that override `withTransaction` (e.g., to use MongoDB's
@@ -1665,6 +1881,14 @@ declare abstract class BaseDbAdapter {
1665
1881
  * UI uses this to show index picker. Override in adapters that support search.
1666
1882
  */
1667
1883
  getSearchIndexes(): TSearchIndexInfo[];
1884
+ private _physicalToLogical?;
1885
+ /**
1886
+ * The LOGICAL field paths an index reads (its `fields` carry physical
1887
+ * names) — what adapters report as {@link TSearchIndexInfo.fields}. A
1888
+ * derived column never shadows the regular field sharing its physical name.
1889
+ * @since 0.1.143
1890
+ */
1891
+ protected _indexLogicalPaths(index: TDbIndex): string[];
1668
1892
  /**
1669
1893
  * Whether this adapter can run TEXT search — `search()`, `searchWithCount()`
1670
1894
  * and the grouped `$search` path all gate on it. Vector capability is a
@@ -2270,6 +2494,19 @@ interface TSearchIndexInfo {
2270
2494
  description?: string;
2271
2495
  /** Index type: text search or vector similarity search. */
2272
2496
  type?: "text" | "vector";
2497
+ /**
2498
+ * LOGICAL field paths the index reads. Absent when the adapter cannot tell
2499
+ * (e.g. a dynamic document search mapping) — treat it as "every field"
2500
+ * (fail closed) when gating access by field visibility.
2501
+ * @since 0.1.143
2502
+ */
2503
+ fields?: string[];
2504
+ /**
2505
+ * `true` on the index of its `type` that answers a request naming none
2506
+ * (at most one per type).
2507
+ * @since 0.1.143
2508
+ */
2509
+ isDefault?: boolean;
2273
2510
  }
2274
2511
  /** Relation summary in a meta response. */
2275
2512
  interface TRelationInfo {
@@ -2825,6 +3062,16 @@ interface AtscriptDbTableLike {
2825
3062
  getMetadata(): TableMetadata;
2826
3063
  isValidFieldPath(path: string, visited?: Set<string>): boolean;
2827
3064
  }
3065
+ /**
3066
+ * Nested FROM re-entry option (internal): pins every child's foreign key
3067
+ * `field` to its parent in the write's row filter; a `strict` item that
3068
+ * matches nothing → `CONFLICT`.
3069
+ * @internal
3070
+ */
3071
+ interface TNestedOwner {
3072
+ field: string;
3073
+ strict?: ReadonlyArray<boolean>;
3074
+ }
2828
3075
  /** Minimal writable table interface for nested creation/update. */
2829
3076
  interface AtscriptDbWritable {
2830
3077
  insertOne(payload: Record<string, unknown>, opts?: {
@@ -2840,6 +3087,7 @@ interface AtscriptDbWritable {
2840
3087
  bulkReplace(payloads: Array<Record<string, unknown>>, opts?: {
2841
3088
  maxDepth?: number;
2842
3089
  _depth?: number;
3090
+ _ownedBy?: TNestedOwner;
2843
3091
  }): Promise<TDbUpdateResult>;
2844
3092
  updateOne(payload: Record<string, unknown>, opts?: {
2845
3093
  maxDepth?: number;
@@ -2847,6 +3095,7 @@ interface AtscriptDbWritable {
2847
3095
  bulkUpdate(payloads: Array<Record<string, unknown>>, opts?: {
2848
3096
  maxDepth?: number;
2849
3097
  _depth?: number;
3098
+ _ownedBy?: TNestedOwner;
2850
3099
  }): Promise<TDbUpdateResult>;
2851
3100
  findOne(query: unknown): Promise<Record<string, unknown> | null>;
2852
3101
  count(query: {
@@ -2941,10 +3190,33 @@ interface TDbWriteGuardContext<Row = Record<string, unknown>> {
2941
3190
  readonly expectedVersions: ReadonlyArray<number | undefined>;
2942
3191
  /**
2943
3192
  * Lazy, memoised pre-image of `rows[i]` by its identifying filter, read
2944
- * inside the transaction. `null` when the row is missing OR when it carries
3193
+ * inside the transaction. Identified exactly like the write (primary key
3194
+ * first, then a unique index — since 0.1.143), so it is always the row the
3195
+ * write targets. `null` when the row is missing OR when it carries
2945
3196
  * no identifying key yet (e.g. auto-increment inserts) — never throws.
2946
3197
  */
2947
3198
  current(i: number): Promise<Row | null>;
3199
+ /**
3200
+ * Every row's pre-image in ONE read (since 0.1.143): parallel to `rows`,
3201
+ * each entry exactly what `current(i)` resolves to — a single `findMany`
3202
+ * by the rows' record filters inside the transaction, which fills the
3203
+ * same per-index memo (an index `current(i)` already read is reused, a
3204
+ * later `current(i)` reads nothing). Prefer it over a `current(i)` loop
3205
+ * for batches.
3206
+ */
3207
+ currentAll(): Promise<Array<Row | null>>;
3208
+ /**
3209
+ * The exact filter the write identifies `rows[i]` by (since 0.1.143) —
3210
+ * its primary key, else the unique index it carries (see `current(i)`),
3211
+ * each naming at most ONE row — or `null` when the row has no identifying
3212
+ * key yet (e.g. an auto-increment insert). The filter `current(i)` reads
3213
+ * by; memoised per index on first use (change a row's identifying fields
3214
+ * before asking, not after). Combine it with a policy filter to check
3215
+ * the batch in the database without reading it — every targeted row
3216
+ * matches `policy` iff `count({ $and: [{ $or: filters }, policy] })`
3217
+ * equals the number of DISTINCT filters (a missing row counts as a miss).
3218
+ */
3219
+ filterFor(i: number): FilterExpr | null;
2948
3220
  }
2949
3221
  /**
2950
3222
  * Context handed to a delete {@link TDeleteOptions.guard} (since 0.1.128) —
@@ -2955,7 +3227,10 @@ interface TDbWriteGuardContext<Row = Record<string, unknown>> {
2955
3227
  interface TDbRemoveGuardContext<Row = Record<string, unknown>> {
2956
3228
  /** The id `deleteOne` was called with. */
2957
3229
  readonly id: unknown;
2958
- /** `table.resolveIdFilter(id)` — never null here. */
3230
+ /**
3231
+ * The exact filter the delete targets — `table.resolveRowFilter(id)`, pinned
3232
+ * inside the transaction (primary key first, since 0.1.143). Never null here.
3233
+ */
2959
3234
  readonly filter: FilterExpr;
2960
3235
  /** Lazy, memoised pre-image of the row about to be deleted (`null` when missing). */
2961
3236
  current(): Promise<Row | null>;
@@ -2978,6 +3253,12 @@ interface TTouchManyOptions {
2978
3253
  * `isFieldVisible` (since 0.1.134, see {@link TIdResolveOptions}) applies to the
2979
3254
  * top-level rows only — a payload without its primary key identifies through
2980
3255
  * a unique index; nested-relation writes ignore it.
3256
+ *
3257
+ * The nested re-entries a deep write performs on related tables get neither
3258
+ * `guard`, `check` nor `isFieldVisible` — they run with the table's own
3259
+ * integrity rules only (a nested write touches only rows related to the
3260
+ * record being written). A permission layer that must authorize related
3261
+ * tables rejects nested payloads up front.
2981
3262
  */
2982
3263
  interface TWriteOptions<Row = Record<string, unknown>> extends TIdResolveOptions {
2983
3264
  /** Nested-relation write recursion limit (default 3). */
@@ -2991,9 +3272,51 @@ interface TWriteOptions<Row = Record<string, unknown>> extends TIdResolveOptions
2991
3272
  * for the nested re-entries a deep write performs on related tables.
2992
3273
  */
2993
3274
  guard?: TDbWriteGuard<Row>;
3275
+ /**
3276
+ * Post-write check (since 0.1.143): invoked exactly once per top-level call,
3277
+ * inside the table's transaction, AFTER the main write and every
3278
+ * nested-relation phase — see {@link TDbWriteCheckContext}. A throw rolls
3279
+ * the transaction back (when `ctx.transactional`) and propagates unchanged.
3280
+ * Never runs for the nested re-entries a deep write performs on related tables.
3281
+ */
3282
+ check?: TDbWriteCheck;
3283
+ }
3284
+ /**
3285
+ * Context handed to a write {@link TWriteOptions.check} (since 0.1.143) — and
3286
+ * through it to `AsDbController.checkWrite()`. Lets a permission layer verify
3287
+ * the POST-image of a write with the database's own filter semantics (a
3288
+ * row-level "WITH CHECK"): count the written rows that still match a policy
3289
+ * filter and throw when one does not.
3290
+ */
3291
+ interface TDbWriteCheckContext {
3292
+ /** The table method the check runs for (`insertOne` → `insert`, …). */
3293
+ readonly action: TDbWriteAction;
3294
+ /**
3295
+ * One exact primary-key filter per row the call wrote (inserted rows by
3296
+ * their resulting PK, updated / replaced rows by the PK of the row the
3297
+ * write actually targeted), de-duplicated. Rows the write matched nothing
3298
+ * for are absent.
3299
+ */
3300
+ readonly filters: ReadonlyArray<Record<string, unknown>>;
3301
+ /**
3302
+ * `true` when the check runs inside a real transaction, so a throw rolls
3303
+ * the write back. `false` on adapters whose transaction is a pass-through
3304
+ * (e.g. a standalone MongoDB) — the write is already durable, so a caller
3305
+ * that needs a hard guarantee must validate BEFORE the write instead.
3306
+ */
3307
+ readonly transactional: boolean;
3308
+ /** Counts rows matching `filter` inside the check's transaction. */
3309
+ count(filter: Record<string, unknown>): Promise<number>;
2994
3310
  }
2995
- /** Options of `deleteOne`. `isFieldVisible` since 0.1.134 — see {@link TIdResolveOptions}. */
2996
- interface TDeleteOptions<Row = Record<string, unknown>> extends TIdResolveOptions {
3311
+ /** A post-write check — see {@link TWriteOptions.check}. */
3312
+ type TDbWriteCheck = (ctx: TDbWriteCheckContext) => void | Promise<void>;
3313
+ /**
3314
+ * Options of `deleteOne`. `isFieldVisible` since 0.1.134 — see
3315
+ * {@link TIdResolveOptions}. `scope` since 0.1.143 — see
3316
+ * {@link TRowResolveOptions}; on `deleteOne` it also restricts the delete
3317
+ * itself (an out-of-scope row is not deleted, `{ deletedCount: 0 }`).
3318
+ */
3319
+ interface TDeleteOptions<Row = Record<string, unknown>> extends TRowResolveOptions {
2997
3320
  /**
2998
3321
  * Validated-stage guard (since 0.1.128): invoked inside the table's
2999
3322
  * transaction after the id resolved to a filter and before cascade /
@@ -3012,6 +3335,24 @@ interface TIdResolveOptions {
3012
3335
  */
3013
3336
  isFieldVisible?: (path: string) => boolean;
3014
3337
  }
3338
+ /**
3339
+ * Options of `resolveRowFilter` (and `deleteOne`) — see {@link TIdResolveOptions}.
3340
+ *
3341
+ * @since 0.1.143
3342
+ */
3343
+ interface TRowResolveOptions extends TIdResolveOptions {
3344
+ /**
3345
+ * Row scope (e.g. a per-request row-level read overlay). When an id could
3346
+ * name several rows (a scalar equal to one row's primary key and another
3347
+ * row's unique key), only rows matching `scope` count while the
3348
+ * identifications are tried primary key first — so a row outside the scope
3349
+ * can never shadow one inside it, and the outcome is exactly what it would
3350
+ * be if the out-of-scope row did not exist. It does not filter the result
3351
+ * itself: AND the scope onto the returned filter (or guard the write) to
3352
+ * exclude an out-of-scope row the id names unambiguously. Empty = no scope.
3353
+ */
3354
+ scope?: FilterExpr;
3355
+ }
3015
3356
  /**
3016
3357
  * Adds `null` to every optional property of `O`. Optional columns store SQL
3017
3358
  * NULL / Mongo null, and the runtime validator accepts `null` for optional
@@ -3109,4 +3450,4 @@ declare function isJsonValueField(fd: TDbFieldMeta): boolean;
3109
3450
  */
3110
3451
  declare function jsonValueAncestor(path: string, jsonValueParents: ReadonlySet<string>): string | undefined;
3111
3452
  //#endregion
3112
- export { TDbWriteGuard as $, IntegrityStrategy as $t, TDbActionInfo as A, UniqueryControls$1 as At, TDbIndex as B, AtscriptDbView as Bt, OwnPropsOf$1 as C, TTouchManyOptions as Ct, TColumnDiff as D, TWriteTableResolver as Dt, TCascadeTarget as E, TWriteOptions as Et, TDbDefaultFn as F, ALL_BUCKET_UNITS as Ft, TDbObjectKind as G, AtscriptQueryFieldRef$1 as Gt, TDbIndexType as H, isAtscriptDbView as Ht, TDbDefaultValue as I, BaseDbAdapter as It, TDbRemoveGuard as J, TViewJoin as Jt, TDbReferentialAction as K, AtscriptQueryNode$1 as Kt, TDbDeleteResult as L, DbSpace as Lt, TDbActionLevel as M, TableMetadata as Mt, TDbActionProcessor as N, isGeoIndexableType as Nt, TCrudOp as O, TypedWithRelation as Ot, TDbCollation as P, isGeoPointType as Pt, TDbWriteAction as Q, AtscriptDbTable as Qt, TDbFieldMeta as R, TAdapterFactory as Rt, NullableOptional as S, TTableResolver as St, TCascadeResolver as T, TViewJsonType as Tt, TDbInsertManyResult as U, aliasTargetOf as Ut, TDbIndexField as V, TViewColumnMapping as Vt, TDbInsertResult as W, AtscriptQueryComparison as Wt, TDbStorageType as X, isFieldRef as Xt, TDbRemoveGuardContext as Y, TViewPlan as Yt, TDbUpdateResult as Z, translateQueryTree as Zt, DbRow as _, TReferencingForeignKey as _t, jsonValueAncestor as a, TDbEncryptionOptions as an, TExistingColumn as at, FlatOf$1 as b, TSyncColumnResult as bt, AggregateControls as c, TReadControls as cn, TFieldMeta as ct, AggregateQuery$1 as d, UniquSelect as dn, TIdDescriptor as dt, NativeIntegrity as en, TDbWriteGuardContext as et, AggregateResult as f, TIdResolveOptions as ft, DbQuery as g, TPrimaryKeyChange as gt, DbPatch as h, TMetadataOverrides as ht, isJsonValueField as i, DbEncryption as in, TEnsureTableOptions as it, TDbActionIntent as j, WithRelation$1 as jt, TCrudPermissions as k, Uniquery$1 as kt, AggregateExpr$1 as l, NoopLogger as ln, TFkLookupResolver as lt, DbControls as m, TMetaResponse as mt, TResolvedBucket as n, DbResponse as nn, TDerivedChangeReason as nt, normalizeComputedSelect as o, DocumentFieldMapper as on, TExistingForeignKey as ot, AtscriptDbWritable as p, TIdentification as pt, TDbRelation as q, AtscriptRef as qt, isBucketableField as r, resolveDesignType as rn, TDerivedColumn as rt, resolveCalendarBuckets as s, FieldMappingStrategy as sn, TExistingTableOption as st, TBucketFieldSource as t, AtscriptDbReadable as tn, TDeleteOptions as tt, AggregateFn$1 as u, TGenericLogger as un, TFkLookupTarget as ut, FieldOpsFor as v, TRelationInfo as vt, PrimaryKeyOf$1 as w, TValueFormatterPair as wt, NavPropsOf$1 as x, TTableOptionDiff as xt, FilterExpr$1 as y, TSearchIndexInfo as yt, TDbForeignKey as z, TDbSpaceOptions as zt };
3453
+ export { TDbWriteAction as $, TViewJoin as $t, TCrudPermissions as A, TWriteOptions as At, TDbForeignKey as B, BaseDbAdapter as Bt, NullableOptional as C, TSearchIndexInfo as Ct, TCascadeTarget as D, TTouchManyOptions as Dt, TCascadeResolver as E, TTableResolver as Et, TDbCollation as F, WithRelation$1 as Ft, TDbInsertResult as G, TViewColumnMapping as Gt, TDbIndexField as H, TAdapterFactory as Ht, TDbDefaultFn as I, TableMetadata as It, TDbRelation as J, aliasTargetOf as Jt, TDbObjectKind as K, isAtscriptDbView as Kt, TDbDefaultValue as L, isGeoIndexableType as Lt, TDbActionIntent as M, TypedWithRelation as Mt, TDbActionLevel as N, Uniquery$1 as Nt, TColumnDiff as O, TValueFormatterPair as Ot, TDbActionProcessor as P, UniqueryControls$1 as Pt, TDbUpdateResult as Q, AtscriptRef as Qt, TDbDeleteResult as R, isGeoPointType as Rt, NavPropsOf$1 as S, TRowResolveOptions as St, PrimaryKeyOf$1 as T, TTableOptionDiff as Tt, TDbIndexType as U, TDbSpaceOptions as Ut, TDbIndex as V, DbSpace as Vt, TDbInsertManyResult as W, AtscriptDbView as Wt, TDbRemoveGuardContext as X, AtscriptQueryFieldRef$1 as Xt, TDbRemoveGuard as Y, AtscriptQueryComparison as Yt, TDbStorageType as Z, AtscriptQueryNode$1 as Zt, DbQuery as _, TMetaResponse as _t, jsonValueAncestor as a, NativeIntegrity as an, TDerivedChangeReason as at, FilterExpr$1 as b, TReferencingForeignKey as bt, AggregateControls as c, resolveDesignType as cn, TExistingColumn as ct, AggregateQuery$1 as d, DocumentFieldMapper as dn, TFieldMeta as dt, TViewPlan as en, TDbWriteCheck as et, AggregateResult as f, FieldMappingStrategy as fn, TFkLookupResolver as ft, DbPatch as g, UniquSelect as gn, TIdentification as gt, DbControls as h, TGenericLogger as hn, TIdResolveOptions as ht, isJsonValueField as i, IntegrityStrategy as in, TDeleteOptions as it, TDbActionInfo as j, TWriteTableResolver as jt, TCrudOp as k, TViewJsonType as kt, AggregateExpr$1 as l, DbEncryption as ln, TExistingForeignKey as lt, AtscriptDbWritable as m, NoopLogger as mn, TIdDescriptor as mt, TResolvedBucket as n, translateQueryTree as nn, TDbWriteGuard as nt, normalizeComputedSelect as o, AtscriptDbReadable as on, TDerivedColumn as ot, AtscriptDbTableLike as p, TReadControls as pn, TFkLookupTarget as pt, TDbReferentialAction as q, isViewType as qt, isBucketableField as r, AtscriptDbTable as rn, TDbWriteGuardContext as rt, resolveCalendarBuckets as s, DbResponse as sn, TEnsureTableOptions as st, TBucketFieldSource as t, isFieldRef as tn, TDbWriteCheckContext as tt, AggregateFn$1 as u, TDbEncryptionOptions as un, TExistingTableOption as ut, DbRow as v, TMetadataOverrides as vt, OwnPropsOf$1 as w, TSyncColumnResult as wt, FlatOf$1 as x, TRelationInfo as xt, FieldOpsFor as y, TPrimaryKeyChange as yt, TDbFieldMeta as z, ALL_BUCKET_UNITS as zt };