@omgbase/oqx 0.13.0 → 0.14.1

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/CHANGELOG.md +94 -0
  2. package/README.md +184 -17
  3. package/dist/adapters/indexed.d.ts +13 -1
  4. package/dist/adapters/indexed.d.ts.map +1 -1
  5. package/dist/adapters/indexed.js +55 -24
  6. package/dist/adapters/indexed.js.map +1 -1
  7. package/dist/ast.d.ts +5 -1
  8. package/dist/ast.d.ts.map +1 -1
  9. package/dist/context.d.ts +24 -0
  10. package/dist/context.d.ts.map +1 -1
  11. package/dist/context.js.map +1 -1
  12. package/dist/engine.d.ts +55 -2
  13. package/dist/engine.d.ts.map +1 -1
  14. package/dist/engine.js +295 -37
  15. package/dist/engine.js.map +1 -1
  16. package/dist/index.d.ts +4 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/optimize/analysis.d.ts +45 -0
  21. package/dist/optimize/analysis.d.ts.map +1 -0
  22. package/dist/optimize/analysis.js +194 -0
  23. package/dist/optimize/analysis.js.map +1 -0
  24. package/dist/optimize/driver.d.ts +8 -0
  25. package/dist/optimize/driver.d.ts.map +1 -0
  26. package/dist/optimize/driver.js +35 -0
  27. package/dist/optimize/driver.js.map +1 -0
  28. package/dist/optimize/hash-index.d.ts +34 -0
  29. package/dist/optimize/hash-index.d.ts.map +1 -0
  30. package/dist/optimize/hash-index.js +79 -0
  31. package/dist/optimize/hash-index.js.map +1 -0
  32. package/dist/optimize/index.d.ts +8 -0
  33. package/dist/optimize/index.d.ts.map +1 -0
  34. package/dist/optimize/index.js +11 -0
  35. package/dist/optimize/index.js.map +1 -0
  36. package/dist/optimize/ir.d.ts +84 -0
  37. package/dist/optimize/ir.d.ts.map +1 -0
  38. package/dist/optimize/ir.js +60 -0
  39. package/dist/optimize/ir.js.map +1 -0
  40. package/dist/optimize/rules.d.ts +8 -0
  41. package/dist/optimize/rules.d.ts.map +1 -0
  42. package/dist/optimize/rules.js +136 -0
  43. package/dist/optimize/rules.js.map +1 -0
  44. package/dist/parser.d.ts.map +1 -1
  45. package/dist/parser.js +59 -5
  46. package/dist/parser.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/adapters/indexed.ts +60 -24
  49. package/src/ast.ts +6 -1
  50. package/src/context.ts +24 -0
  51. package/src/engine.ts +329 -35
  52. package/src/index.ts +7 -2
  53. package/src/optimize/analysis.ts +183 -0
  54. package/src/optimize/driver.ts +37 -0
  55. package/src/optimize/hash-index.ts +91 -0
  56. package/src/optimize/index.ts +16 -0
  57. package/src/optimize/ir.ts +128 -0
  58. package/src/optimize/rules.ts +136 -0
  59. package/src/parser.ts +54 -6
package/CHANGELOG.md CHANGED
@@ -4,6 +4,100 @@ All notable changes to `@omgbase/oqx` are recorded here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [Semantic Versioning](https://semver.org/) (pre-1.0: a minor bump may break).
6
6
 
7
+ ## [0.14.1] - 2026-10-09
8
+
9
+ ### Patch
10
+ - Relational patterns in nested blocks stop being quadratic. The in-memory engine
11
+ now runs every nested block through an optimizer (`optimize/`: a logical
12
+ `BlockPlan` per block, a `Rule` interface, a fixpoint driver) with four rules,
13
+ each proven unobservable — same rows, lifts and errors as the scan:
14
+
15
+ - **Correlated equality → hash probe.** `^customers first { where id ==
16
+ ^customer_id }`, `^orders collect { … where customer_id == ^id }`,
17
+ `^customers exists { where id == ^customer_id }`, `${customers} first { … }`:
18
+ a top-level `&&` conjunct `local == outer` (either side) is answered from a
19
+ hash index on the receiver built once per run (per collection and path),
20
+ with the residual conjuncts evaluated over the bucket in receiver order.
21
+ Multiple equalities intersect smallest-first. The index reproduces §5
22
+ equality exactly (absent ≡ null, `-0` ≡ `0`, `NaN` matches nothing, objects
23
+ by reference). The sound rule: every conjunct left of the equality must be
24
+ raise-free (no call, no `single`, no lift, no bound, no out-of-range binding)
25
+ and the outer side must be raise-free and read nothing from the block's row;
26
+ otherwise the scan runs. A receiver is indexed when it is statically stable
27
+ (reads only the root/bindings) or when the same collection object is probed
28
+ twice; a receiver that yields a fresh value per enclosing row is scanned.
29
+ 5k × 5k zip: ~9 ms (the scan: seconds).
30
+ - **Invariant block hoisting.** A block that reads nothing from any enclosing
31
+ row and lifts nothing is evaluated once per run and its value reused.
32
+ - **Semi/anti-join short-circuit** is kept over the probe's bucket (`exists`
33
+ stops at the first bucket row that passes).
34
+ - **Cardinality.** `exists`/`none`/`count` with no residual predicate are
35
+ answered from the bucket (or receiver) size without entering a row.
36
+
37
+ `DataContext.indexFor(collection, path)` (TypeScript) / `index_for` (Rust) is
38
+ a new optional seam for pre-built indexes; `IndexedCollection` keys under §5
39
+ equality (it kept `null` and `undefined` apart and matched `NaN` to itself)
40
+ and exposes `context()` (TypeScript) so a residual's nested blocks see the
41
+ root and reuse its indexes. `InMemoryEngine` takes `{ rules, trace }`
42
+ (TypeScript) / `with_rules` (Rust); `rules: []` is the naive scan, and the
43
+ conformance suites prove optimized ≡ naive — result or error, stage and
44
+ message — over every `spec/oqx` fixture. No language change.
45
+
46
+ A `RowIndex` may also implement `lookupRows(value)` (TypeScript) /
47
+ `lookup_rows` (Rust): the matching rows themselves, in receiver order. For a
48
+ statically stable receiver the engine asks the context's index **before**
49
+ reading the collection and, when it answers rows, never materializes the
50
+ receiver (one correlation is probed — the first whose outer side is a `^`
51
+ reference rather than a literal — and the other conjuncts, remaining equalities
52
+ included, stay residual in their original order; a `TraceEvent` of kind
53
+ `lookup` reports it). A statically stable receiver is now also read once per
54
+ run when nothing is correlated, instead of once per enclosing row. Rust:
55
+ `index_for` returns `Option<Rc<dyn RowIndex>>` and `lookup` returns
56
+ `Vec<usize>`, so a context can create indexes on demand.
57
+
58
+ `DataContext::materialize(value)` (Rust) / `materialize?(value)` (TypeScript)
59
+ is a new optional seam, default identity: a context that hands out a stand-in
60
+ for a collection it has not read yet (a lazy table handle) resolves it here.
61
+ The engine calls it on every value it is about to observe as a value — an
62
+ operand, an argument, a projected item, an `order by`/`distinct` key, a
63
+ `where` scalar, a lift — and never in row position (the source, a receiver, a
64
+ `from`, a `follow` destination), where the value reaches `to_rows`/`index_for`
65
+ as handed out. The Rust engine's execution path was folded to mirror the
66
+ TypeScript one: one `access` for every consumer (the `follow` seeds included),
67
+ `scan`/`materialize`, a `memoized` helper for invariant plans, `outer_value`,
68
+ and `lookup_order`/`residual_without` in `optimize`; optimized ≡ naive over
69
+ every fixture, same perf.
70
+
71
+ ## [0.14.0] - 2026-10-06
72
+
73
+ ### Minor
74
+ - OQX language **0.14** (`spec/oqx/VERSION`): `follow` gains correlated successor
75
+ predicates and destination lists.
76
+
77
+ - **`^` in the follow `where` is the frontier row.** `follow R { where P }` now
78
+ reads `P` in the candidate's scope whose parent is the row being expanded, so
79
+ `follow doc.in { where before.contains(^$path) }` steps only into rows that
80
+ name the row they were reached from; `^^name` is the walk's enclosing scope.
81
+ Before, `^` skipped the frontier row and read the enclosing scope (the root
82
+ for a top-level walk), so a correlated predicate silently never held.
83
+ `frontier` and `by` are unchanged (`^` there is the enclosing scope).
84
+ - **Destination lists.** `follow a, b, …` walks the union of several
85
+ destinations; each is a relation of the current row or a **destination
86
+ block** — a select-position `collect`/`first`/`single` block re-evaluated per
87
+ frontier row with `^` bound to it (`follow ^people collect { where manager ==
88
+ ^id }`, `follow before, ^docs collect { where after.contains(^$path) }`).
89
+ Within one expansion step the successors are unioned by identity — the
90
+ walk's identity, so `by E` governs it: a relation holding a node twice now
91
+ yields one successor, and under `by type` same-type siblings reached in one
92
+ step are one node (the surface case `query-follow::by-rekeys-identity` goes
93
+ from 8 to 5 hits). `frontier`, `depth`, `where`
94
+ and `by` apply to the whole walk; `$ordinal` stays `(depth, path)`.
95
+ - AST: `Follow.receiver` becomes `Follow.destinations` (TypeScript
96
+ `(Expr | OpNode)[]`; Rust `Vec<FollowDestination>` with `Relation(Expr)` /
97
+ `Block(OpNode)`). omgbase's query rewriters and planners read the new shape;
98
+ `@omgbase/oqx-syntax` pins `LANGUAGE_VERSION` to `0.14`.
99
+ - 27 new fixtures in `spec/oqx/cases/follow.json` (877 cases in 29 files).
100
+
7
101
  ## [0.13.0] - 2026-09-26
8
102
 
9
103
  Language `0.13` (`spec/oqx/VERSION`; `LANGUAGE_VERSION` is `"0.13"`). One
package/README.md CHANGED
@@ -16,7 +16,7 @@ JavaScript tagged template.
16
16
  > OQX is a language with more than one implementation; the specification is
17
17
  > [`spec/oqx`](https://github.com/omgbase/omgbase/tree/main/spec/oqx). The
18
18
  > language version is the package version's `major.minor` (`LANGUAGE_VERSION`,
19
- > `"0.13"`); the patch digit is this implementation's own.
19
+ > `"0.14"`); the patch digit is this implementation's own.
20
20
  > Requirements: Node ≥ 22.13 for `@omgbase/oqx/sqlite`; Node ≥ 22.18 to run the
21
21
  > test suite (see [Requirements](#requirements)).
22
22
 
@@ -57,7 +57,7 @@ once, and every one is optional except `from` at the top level:
57
57
  [ [select] projection ] name, id, title: label (or: select distinct …, … values)
58
58
  from <collection> from ${people}
59
59
  [ where <predicate> ] where age >= 18 && jobs exists { where !end }
60
- [ follow <relation> … ] follow children { depth 4 }
60
+ [ follow <dest>, … ] follow children { depth 4 } (or: follow before, after; follow ^people collect { … })
61
61
  [ order by <expr> … ] order by age desc, name
62
62
  [ limit N ] limit 10
63
63
  [ offset N ] offset 20
@@ -555,10 +555,10 @@ A storage adapter that pushes the whole query may translate them to SQL
555
555
 
556
556
  ### 9. Recursion: `follow`
557
557
 
558
- `follow <relation>` turns a query into a bounded recursive traversal: the `where`
559
- selects the seed rows, and `follow` walks a relation from each reached row. It's
560
- fully duck-typed — the relation is any expression yielding successors; a row that
561
- lacks it is simply a leaf.
558
+ `follow <destination>, …` turns a query into a bounded recursive traversal: the
559
+ `where` selects the seed rows, and `follow` walks one or more **destinations**
560
+ from each reached row. It's fully duck-typed — a relation is any expression
561
+ yielding successors; a row that lacks it is simply a leaf.
562
562
 
563
563
  ```js
564
564
  const tree = [{ id: "root", children: [
@@ -599,11 +599,43 @@ occurrences, and revisiting an identity already on the current path is admitted
599
599
  runaway. `follow distinct` collapses occurrences to reached nodes (the minimal
600
600
  `(depth, path)` per identity).
601
601
 
602
+ **Destinations** (language 0.14) are comma-separated; the walk is their union.
603
+ A **plain destination** is a relation of the current row (`children`, `doc.out`
604
+ — an outer reference `^rel` or a literal is a parse error). A **destination
605
+ block** is a receiver followed by `collect`, `first` or `single` (optionally
606
+ `distinct`) and a block — the same directive you would write in `select`,
607
+ re-evaluated per frontier row with `^` bound to that row. Its receiver *may* be
608
+ an outer reference, so a walk can compute successors from a named root:
609
+
610
+ ```js
611
+ const people = [{ id: 1, manager: null }, { id: 2, manager: 1 }, { id: 3, manager: 1 }, { id: 4, manager: 2 }];
612
+ oqx`id values from ${people} where id == 1 follow ^people collect { where manager == ^id } order by $ordinal`;
613
+ // [1, 2, 3, 4] — the reporting tree under person 1 (`^people` names the root from a top-level walk)
614
+
615
+ const chain = [{ id: "a" }, { id: "b", prev: "a" }, { id: "c", prev: "b" }];
616
+ oqx`id values from ${chain} where id == "b" follow distinct ^chain collect { where id == ^prev }, ^chain collect { where prev == ^id } order by $ordinal`;
617
+ // ["b", "a", "c"] — both directions of a chain; a block may order, bound and `distinct` its rows
618
+ ```
619
+
620
+ A row's successors are, per destination in order, that destination's rows,
621
+ filtered by the follow `where`, then **unioned by identity within the step**: a
622
+ node reached by two destinations (or twice by one relation) in the same step is
623
+ a single successor. `exists`/`none`/`count` are not destinations (they are
624
+ where-position tests).
625
+
602
626
  The block accepts: `where <succ>` (which successors keep participating),
603
- `frontier <pred>` (cut a relation that could continue), `depth <n>` (1–8), and
604
- `by <expr>` (the identity used for cycle detection + `distinct` — default `.id`
605
- or the object reference). Give `follow` a stable identity (`.id` or `by`) when
606
- your relation returns fresh objects rather than shared references.
627
+ `frontier <pred>` (cut a relation that could continue, whichever destination
628
+ reached it), `depth <n>` (1–8), and `by <expr>` (the identity used for cycle
629
+ detection + `distinct` — default `.id` or the object reference). The follow
630
+ `where` is **correlated**: it reads the candidate successor's scope whose parent
631
+ is the frontier row's — a bare name is the candidate's own property, `^name` is
632
+ the row being expanded, `^^name` the walk's enclosing scope (language 0.14;
633
+ before, `^` skipped the frontier row). So `follow doc.in { where
634
+ before.contains(^$path) }` steps only into rows that name the row they were
635
+ reached from. `frontier` and `by` are not correlated: they read the occurrence
636
+ with `^` the enclosing scope, as the body does. Give `follow` a stable identity
637
+ (`.id` or `by`) when your relation returns fresh objects rather than shared
638
+ references.
607
639
 
608
640
  ### Cheat-sheet
609
641
 
@@ -626,7 +658,9 @@ from entries(obj) … $key / $value a record's properties as a co
626
658
  entries(rel) exists { … } a free-function call may be a receiver
627
659
  order by expr desc, expr2 ordering
628
660
  limit n / offset n bound the row set (after where/order/distinct, before the consumer)
629
- follow rel { where … frontier … depth n by … } recursion ($depth/$stop/$leaf/$frontier)
661
+ follow rel { where … frontier … depth n by … } recursion ($depth/$stop/$leaf/$frontier); `^name` in the where is the frontier row
662
+ follow before, after several destinations: the union of their rows (per step, by identity)
663
+ follow ^people collect { where manager == ^id } a destination block: successors computed per frontier row (`^` = that row)
630
664
  ${source} <collect|exists|none|count|first|single> { … } whole-query consumer
631
665
  ```
632
666
 
@@ -747,6 +781,117 @@ blocks/nodes, the relations table, `$` intrinsics, `WITH RECURSIVE` for `follow`
747
781
  becomes a `QueryPlanner`, while oqx contributes the parser, IR, semantics
748
782
  contract, and residual executor.
749
783
 
784
+ ## Performance: relational patterns
785
+
786
+ A nested block runs once per enclosing row, so the natural way to join two
787
+ collections —
788
+
789
+ ```js
790
+ oqx`select id, total, customer: ${customers} first { where id == ^customer_id } from ${orders}`;
791
+ oqx`select name, orders: ${orders} collect { id, total where customer_id == ^id } from ${customers}`;
792
+ oqx`select id from ${orders} where ${customers} exists { where id == ^customer_id }`;
793
+ ```
794
+
795
+ — would be quadratic if the block scanned its receiver each time. It does not:
796
+ the in-memory engine runs every nested block through an **optimizer**
797
+ (`src/optimize/`), a small logical plan per block plus rules that rewrite it,
798
+ each with a proof that the rewrite is unobservable — same rows in the same
799
+ order, same lifts, same error (same message) at the same point. The planned
800
+ engine and the naive engine (`new InMemoryEngine(ctx, { rules: [] })`) are run
801
+ over every `spec/oqx` fixture by `test/conformance.test.ts` and must agree on
802
+ results and on errors.
803
+
804
+ **What is optimized**
805
+
806
+ - **Correlated equality → hash probe.** A top-level `&&` conjunct of the block's
807
+ `where` of the form `local == outer` (either side) — `local` a bare identifier
808
+ or member chain on the block's row (`customer_id`, `meta.id`, `$value`),
809
+ `outer` anything that reads nothing from that row (`^customer_id`, `^^x.id`,
810
+ a `${…}` binding, a literal, arithmetic over them) — is answered from a hash
811
+ index on the receiver built **once per run** per collection and path. The
812
+ remaining conjuncts run over the bucket only, in receiver order; several
813
+ equalities intersect their buckets smallest first. The index keys are exactly
814
+ `==` (SEMANTICS §5): absent ≡ `null`, `-0` ≡ `0`, `NaN` matches nothing,
815
+ objects and arrays by reference. 5 000 orders × 5 000 customers zip in a few
816
+ milliseconds; the scan takes seconds.
817
+ - **Invariant blocks run once.** A block that reads nothing from any enclosing
818
+ row (`^customers collect { name values }` from a top-level row) and lifts
819
+ nothing is evaluated the first time it is reached and its value reused for
820
+ every other row (the same array/record by reference).
821
+ - **Semi/anti-join short-circuit.** `exists` and `none` stop at the first bucket
822
+ row that passes the residual, as they stop at the first matching row of a scan.
823
+ - **Cardinality only.** `exists` / `none` / `count` with nothing left to check
824
+ per row (no `where`, or every conjunct correlated; no `distinct`, no `order
825
+ by`) are answered from the bucket's size after the bound without entering a
826
+ row.
827
+
828
+ **The sound rule.** `&&` is strictly left to right and short-circuiting (§13),
829
+ so a probe may skip a row only if everything the scan would have evaluated for
830
+ it could neither raise nor bind: an equality is hoisted only when **every
831
+ conjunct to its left is raise-free** — no function or method call, no `single`,
832
+ no lift, no `limit`/`offset`, no out-of-range `${…}`, only `exists`/`none`/
833
+ `count` blocks that are themselves raise-free — and its outer side is raise-free
834
+ too. A conjunct that could raise *right* of the equality is simply evaluated
835
+ over the bucket, where the scan would have evaluated it on the same rows. A
836
+ block whose `where` lifts anywhere is never probed (a lift could change what
837
+ the outer side reads between rows). Otherwise the block scans as before, so
838
+ `where nope(x) && id == ^id` still raises `unknown function`.
839
+
840
+ Property reads are treated as total (§23: evaluation is otherwise total). A
841
+ `DataContext.get` that throws is caught where the engine relies on reads it
842
+ would not otherwise perform — building an index, evaluating the probe value —
843
+ and the block falls back to the scan, which raises or not in its own order.
844
+
845
+ **Which receivers are indexed.** A receiver that reads only the root scope and
846
+ bindings (`^customers`, `${customers}`, `^^config.items`) is evaluated once per
847
+ run — its rows are read once and reused across the enclosing rows, with or
848
+ without a correlation — and indexed once per path even if the context hands out
849
+ a fresh array per read. Any other receiver is indexed the second time the
850
+ **same object** is probed, so a per-row relation (`lines collect { … where cid
851
+ == ^id }` with a different `lines` array per row) is scanned, not indexed. A
852
+ scalar receiver is one row.
853
+
854
+ **Pre-built indexes.** `DataContext.indexFor(collection, path)` is an optional
855
+ seam: return a `RowIndex` (`lookup(value) → ascending positions into
856
+ toRows(collection)`) for a collection you have already indexed, or `undefined`
857
+ to let the engine build one. A `RowIndex` may also implement
858
+ `lookupRows(value) → the matching rows, in receiver order`: for a stable
859
+ receiver the engine then probes it **before** reading the collection, so a
860
+ context backed by a store (a lazy handle for a table, an index on a column)
861
+ answers the probe with one indexed lookup and the table is never materialized.
862
+ One correlation is probed this way — the first whose outer side is a `^`
863
+ reference rather than a literal — and the other conjuncts, remaining
864
+ equalities included, are evaluated per selected row in their original order.
865
+
866
+ **Lazy handles.** A context that hands out a stand-in for a collection it has
867
+ not read yet may also implement `materialize(value)`: the engine calls it on
868
+ every value it is about to observe *as a value* (an operand of `==`/`in`/
869
+ arithmetic, a function or method argument, a projected item, an `order by` or
870
+ `distinct` key, a `where` scalar, a lift) and never on a value in *row
871
+ position* (the source, a block receiver, a body-level `from`, a `follow`
872
+ destination), which reaches `toRows` / `indexFor` as handed out. So the probe
873
+ above never reads the table, and the language never sees the stand-in. A
874
+ handle that already behaves as the collection (a `Proxy` over an array, as
875
+ omgbase's does) needs no `materialize`; the Rust crate's `DataContext::
876
+ materialize` is the same seam for a `Value` that cannot be lazy on its own.
877
+ `IndexedCollection` does the positional form for its fields:
878
+
879
+ ```js
880
+ import { parse, run, IndexedCollection } from "@omgbase/oqx";
881
+ const orders = new IndexedCollection("orders", orderRows, ["customer_id"]);
882
+ run(parse("select name, o: ^orders collect { id values where customer_id == ^id } from customers"),
883
+ { context: orders.context({ customers }) }); // probes the pre-built index
884
+ ```
885
+
886
+ **Still a scan.** A block with a `from` re-projection or a `follow`; a `where`
887
+ whose equality sits right of a conjunct that could raise; inequalities,
888
+ `in`, `contains()` and other non-equality correlations; `order by … limit N`
889
+ (sorted in full, then bounded); `distinct` (hashes projections, not an index);
890
+ top-level queries (a `QueryPlanner` is the seam for those). `trace` in
891
+ `InMemoryEngine`'s options receives one event per index built, probe, direct
892
+ lookup, memo hit, cardinality answer or fallback, if you want to see what a
893
+ query did.
894
+
750
895
  ## Exports
751
896
 
752
897
  Everything below is exported from `@omgbase/oqx` (`src/index.ts`); the SQLite
@@ -793,10 +938,18 @@ adapter lives on the `@omgbase/oqx/sqlite` subpath.
793
938
  **Engines and contexts**
794
939
 
795
940
  - `Engine` — `{ run(query, bindings): OqxResult }`; what `run({ engine })` accepts.
796
- - `InMemoryEngine` — `new InMemoryEngine(context?)`; the reference engine over a
797
- `DataContext` (tier 1/2).
941
+ - `InMemoryEngine` — `new InMemoryEngine(context?, options?)`; the reference
942
+ engine over a `DataContext` (tier 1/2). `InMemoryEngineOptions`: `rules`
943
+ (the optimizer rules for nested blocks, default `DEFAULT_RULES`; `[]` is the
944
+ naive scan) and `trace` (a `(event: TraceEvent) => void` sink for index /
945
+ probe / memo / cardinality / fallback events). See [Performance](#performance-relational-patterns).
798
946
  - `DataContext` — the tier-2 interface: `root`, `get`, `toRows`, `identity`,
799
- plus optional `callFunction` / `callMethod` (above) and an optional
947
+ plus optional `callFunction` / `callMethod` (above), an optional
948
+ `indexFor(collection, path)` returning a `RowIndex` for a pre-built equality
949
+ index (`lookup` → positions; optionally `lookupRows` → rows, probed without
950
+ materializing the collection), an optional `materialize(value)` resolving a
951
+ lazy handle the engine is about to observe as a value (see
952
+ [Performance](#performance-relational-patterns)), and an optional
800
953
  `regexDialect` (`"oqx"` default | `"native"`). The engine dispatches
801
954
  `matches` through `callMethod`, so a custom context that wants the native
802
955
  dialect answers `matches` itself with `semantics.regexMatches(recv, args,
@@ -839,17 +992,31 @@ adapter lives on the `@omgbase/oqx/sqlite` subpath.
839
992
  its native query using `constValue` for parameters, then returns
840
993
  `{ rows, residual: residualQuery(query, residual) }`.
841
994
  - `IndexedCollection` — `new IndexedCollection(rootName, rows, indexFields)`; a
842
- `QueryPlanner` that hash-indexes `rows` on `indexFields` and answers
843
- `field == value` predicates on them from the index, leaving the rest residual:
995
+ `QueryPlanner` that hash-indexes `rows` on `indexFields` (under `==`: absent ≡
996
+ null, `NaN` matches nothing) and answers `field == value` predicates on them
997
+ from the index, leaving the rest residual. `indexFor(collection, path)` serves
998
+ the same indexes to the engine's correlated probes and `context(extraRoots?)`
999
+ is a `DataContext` that resolves the root and wires `indexFor` up; the plans
1000
+ it returns carry that context, so a residual's nested blocks see the root.
844
1001
 
845
1002
  ```js
846
1003
  import { parse, run, IndexedCollection, PlannedEngine } from "@omgbase/oqx";
847
1004
  const planner = new IndexedCollection("people", people, ["city", "title"]);
848
- const engine = new PlannedEngine(planner);
1005
+ const engine = new PlannedEngine(planner, planner.context());
849
1006
  run(parse('name from people where city == "NYC" && age > 30'), { engine });
850
1007
  // city probe from the index; `age > 30` finished in-memory over the candidates
851
1008
  ```
852
1009
 
1010
+ **Optimizer (nested blocks)**
1011
+
1012
+ - `DEFAULT_RULES`, `correlatedEqualityProbe`, `stableReceiver`, `invariantBlock`,
1013
+ `cardinalityOnly` — the shipped `Rule`s (`(plan: BlockPlan, ctx: RuleContext)
1014
+ => BlockPlan | null`); `logicalBlock(op, depth)` is the unoptimized plan of a
1015
+ block and `optimizeBlock(op, depth, ctx, rules)` runs rules to a fixpoint.
1016
+ `BlockPlan` / `Correlation` / `RuleContext` are the types; `HashIndex` is the
1017
+ engine's `RowIndex` implementation (`add(value, position)`, `lookup(value)`),
1018
+ reusable by a context implementing `indexFor`.
1019
+
853
1020
  - `SqliteTable` (from `@omgbase/oqx/sqlite`) —
854
1021
  `new SqliteTable(db, tableName, options)` over a `node:sqlite` `DatabaseSync`.
855
1022
  `SqliteTableOptions`:
@@ -1,11 +1,23 @@
1
1
  import type { Query } from "../ast.js";
2
+ import type { DataContext } from "../context.js";
2
3
  import type { Plan, QueryPlanner } from "../planner.js";
4
+ import type { RowIndex } from "../optimize/hash-index.js";
3
5
  export declare class IndexedCollection implements QueryPlanner {
4
6
  private name;
5
7
  private rows;
6
8
  private indexes;
7
- /** Index `rows` (exposed as root `name`) on each field in `indexFields`. */
9
+ /** Index `rows` (exposed as root `name`) on each field in `indexFields`. A
10
+ * field is read as the engine reads a bare identifier (own property; array
11
+ * element for an integer-spelled name); a row without it keys as absent. */
8
12
  constructor(name: string, rows: readonly unknown[], indexFields: readonly string[]);
13
+ /** The pre-built index for `collection` on `path` when `collection` is this
14
+ * collection's rows and `path` is one indexed field — the `DataContext.indexFor`
15
+ * seam, which `context()` wires up. */
16
+ indexFor(collection: unknown, path: readonly string[]): RowIndex | undefined;
17
+ /** A `DataContext` over plain objects that serves this collection as the
18
+ * root `name` (plus `extraRoots`) and exposes the indexes through
19
+ * `indexFor`, so the engine's correlated probes reuse them. */
20
+ context(extraRoots?: Record<string, unknown>): DataContext;
9
21
  plan(query: Query, params: readonly unknown[]): Plan | null;
10
22
  }
11
23
  //# sourceMappingURL=indexed.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexed.d.ts","sourceRoot":"","sources":["../../src/adapters/indexed.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxD,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,OAAO,CAA8C;IAE7D,4EAA4E;gBAChE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE;IAelF,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;CAoB5D"}
1
+ {"version":3,"file":"indexed.d.ts","sourceRoot":"","sources":["../../src/adapters/indexed.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAExD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAG1D,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,OAAO,CAAgC;IAE/C;;gFAE4E;gBAChE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE;IAWlF;;2CAEuC;IACvC,QAAQ,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,GAAG,SAAS;IAK5E;;mEAE+D;IAC/D,OAAO,CAAC,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,WAAW;IAI9D,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;CAoB5D"}
@@ -1,33 +1,57 @@
1
1
  // An in-memory optimizing planner: it hash-indexes a named root collection on
2
- // chosen fields and, for a query whose where-clause contains equality predicates
3
- // on those fields, answers from the index (candidate intersection) instead of a
4
- // full scan. Everything it can't turn into an index probe is left as a residual
5
- // query the in-memory engine finishes over the candidate rows.
2
+ // chosen fields and, for a query whose where-clause contains equality
3
+ // predicates on those fields, answers from the index (candidate intersection)
4
+ // instead of a full scan. Everything it can't turn into an index probe is left
5
+ // as a residual query the in-memory engine finishes over the candidate rows.
6
6
  //
7
7
  // This is the smallest honest demonstration of plan optimization: same answers
8
8
  // as a naive scan, far fewer rows examined, and partial-pushdown correctness via
9
9
  // the residual.
10
- import { partitionPushable, residualQuery, asEquality, constValue } from "../plan.js";
10
+ //
11
+ // The same indexes serve the engine's correlated probes: `context()` is a
12
+ // `DataContext` that resolves the root and answers `indexFor(collection, path)`
13
+ // from them, so a nested `^emp first { where id == ^manager_id }` probes the
14
+ // pre-built index instead of building one per run. The planner's own plans
15
+ // carry that context too, so a residual's nested blocks still see every root.
16
+ //
17
+ // Buckets are keyed under OQX equality (`HashIndex`): absent ≡ null, `-0` ≡ `0`,
18
+ // `NaN` matches nothing, objects by reference — exactly what `==` compares, so
19
+ // a probe returns the rows the scan would keep (a plain `Map` would keep `null`
20
+ // and `undefined` apart and match `NaN` to itself).
21
+ import { DefaultContext } from "../context.js";
22
+ import { partitionPushable, residualQuery, asEquality, constValue, ROWS_ROOT } from "../plan.js";
23
+ import { HashIndex, intersectPositions } from "../optimize/hash-index.js";
11
24
  export class IndexedCollection {
12
25
  name;
13
26
  rows;
14
27
  indexes = new Map();
15
- /** Index `rows` (exposed as root `name`) on each field in `indexFields`. */
28
+ /** Index `rows` (exposed as root `name`) on each field in `indexFields`. A
29
+ * field is read as the engine reads a bare identifier (own property; array
30
+ * element for an integer-spelled name); a row without it keys as absent. */
16
31
  constructor(name, rows, indexFields) {
17
32
  this.name = name;
18
33
  this.rows = rows;
34
+ const read = new DefaultContext();
19
35
  for (const field of indexFields) {
20
- const idx = new Map();
21
- for (const row of rows) {
22
- const key = row?.[field];
23
- let bucket = idx.get(key);
24
- if (!bucket)
25
- idx.set(key, (bucket = []));
26
- bucket.push(row);
27
- }
36
+ const idx = new HashIndex();
37
+ rows.forEach((row, i) => idx.add(read.get(row, field), i));
28
38
  this.indexes.set(field, idx);
29
39
  }
30
40
  }
41
+ /** The pre-built index for `collection` on `path` when `collection` is this
42
+ * collection's rows and `path` is one indexed field — the `DataContext.indexFor`
43
+ * seam, which `context()` wires up. */
44
+ indexFor(collection, path) {
45
+ if (collection !== this.rows || path.length !== 1)
46
+ return undefined;
47
+ return this.indexes.get(path[0]);
48
+ }
49
+ /** A `DataContext` over plain objects that serves this collection as the
50
+ * root `name` (plus `extraRoots`) and exposes the indexes through
51
+ * `indexFor`, so the engine's correlated probes reuse them. */
52
+ context(extraRoots = {}) {
53
+ return new IndexedContext(this, { ...extraRoots, [this.name]: this.rows });
54
+ }
31
55
  plan(query, params) {
32
56
  if (query.source.kind !== "ident" || query.source.name !== this.name)
33
57
  return null;
@@ -40,18 +64,25 @@ export class IndexedCollection {
40
64
  if (pushed.length === 0)
41
65
  return null; // no index probe available — let the scan handle it
42
66
  // Intersect the candidate sets from each indexed equality (smallest first).
43
- let candidate = null;
44
- for (const e of pushed) {
67
+ const buckets = pushed.map((e) => {
45
68
  const eq = asEquality(e);
46
- const bucket = this.indexes.get(eq.field).get(constValue(eq.value, params)) ?? [];
47
- candidate = candidate === null ? bucket.slice() : intersect(candidate, bucket);
48
- }
49
- const rows = candidate ?? [];
50
- return { rows: () => rows, residual: residualQuery(query, residual) };
69
+ return this.indexes.get(eq.field).lookup(constValue(eq.value, params));
70
+ }).sort((a, b) => a.length - b.length);
71
+ let candidate = buckets[0];
72
+ for (let i = 1; i < buckets.length && candidate.length > 0; i++)
73
+ candidate = intersectPositions(candidate, buckets[i]);
74
+ const rows = candidate.map((i) => this.rows[i]);
75
+ return { rows: () => rows, residual: residualQuery(query, residual), context: this.context({ [ROWS_ROOT]: rows }) };
51
76
  }
52
77
  }
53
- function intersect(a, b) {
54
- const set = new Set(b);
55
- return a.filter((x) => set.has(x));
78
+ class IndexedContext extends DefaultContext {
79
+ owner;
80
+ constructor(owner, roots) {
81
+ super(roots);
82
+ this.owner = owner;
83
+ }
84
+ indexFor(collection, path) {
85
+ return this.owner.indexFor(collection, path);
86
+ }
56
87
  }
57
88
  //# sourceMappingURL=indexed.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexed.js","sourceRoot":"","sources":["../../src/adapters/indexed.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"indexed.js","sourceRoot":"","sources":["../../src/adapters/indexed.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,+EAA+E;AAC/E,6EAA6E;AAC7E,EAAE;AACF,+EAA+E;AAC/E,iFAAiF;AACjF,gBAAgB;AAChB,EAAE;AACF,0EAA0E;AAC1E,gFAAgF;AAChF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,EAAE;AACF,iFAAiF;AACjF,+EAA+E;AAC/E,gFAAgF;AAChF,oDAAoD;AAIpD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEjG,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE1E,MAAM,OAAO,iBAAiB;IACpB,IAAI,CAAS;IACb,IAAI,CAAqB;IACzB,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;IAE/C;;gFAE4E;IAC5E,YAAY,IAAY,EAAE,IAAwB,EAAE,WAA8B;QAChF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,IAAI,GAAG,IAAI,cAAc,EAAE,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED;;2CAEuC;IACvC,QAAQ,CAAC,UAAmB,EAAE,IAAuB;QACnD,IAAI,UAAU,KAAK,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACpE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;IACpC,CAAC;IAED;;mEAE+D;IAC/D,OAAO,CAAC,aAAsC,EAAE;QAC9C,OAAO,IAAI,cAAc,CAAC,IAAI,EAAE,EAAE,GAAG,UAAU,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,CAAC,KAAY,EAAE,MAA0B;QAC3C,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAClF,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEvD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;YAChE,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YACzB,OAAO,EAAE,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,oDAAoD;QAE1F,4EAA4E;QAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC/B,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,SAAS,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE;YAAE,SAAS,GAAG,kBAAkB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;QACxH,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACtH,CAAC;CACF;AAED,MAAM,cAAe,SAAQ,cAAc;IACjC,KAAK,CAAoB;IAEjC,YAAY,KAAwB,EAAE,KAA8B;QAClE,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,QAAQ,CAAC,UAAmB,EAAE,IAAuB;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;CACF"}
package/dist/ast.d.ts CHANGED
@@ -87,9 +87,13 @@ export interface OpNode {
87
87
  * row identity. Spellable as `<op> distinct { … }` or `{ select distinct … }`. */
88
88
  distinct?: boolean;
89
89
  }
90
+ /** One `follow` destination: a relation of the current row (an `Expr` receiver), or a
91
+ * destination block — a select-position directive (`collect`/`first`/`single`) re-evaluated
92
+ * per frontier row, inside which `^` is that row. Discriminate on `kind === "op"`. */
93
+ export type FollowDestination = Expr | OpNode;
90
94
  /** The recursive `follow` clause. */
91
95
  export interface Follow {
92
- receiver: Expr;
96
+ destinations: FollowDestination[];
93
97
  distinct: boolean;
94
98
  where: Expr | null;
95
99
  frontier: Expr | null;
package/dist/ast.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
package/dist/context.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { RegexDialect } from "./regex.js";
2
+ import type { RowIndex } from "./optimize/hash-index.js";
2
3
  /** Result of a context-provided function/method call: `handled: false` tells the
3
4
  * engine to fall back to the builtin table (or error if none). */
4
5
  export interface CallResult {
@@ -17,6 +18,18 @@ export interface DataContext {
17
18
  get(row: unknown, key: string): unknown;
18
19
  /** Coerce a relation/source value into rows (may be lazy). */
19
20
  toRows(value: unknown): Iterable<unknown>;
21
+ /** Optional: a value this context handed out as a stand-in for a collection
22
+ * it has not read yet (a lazy table handle), resolved to what it stands for.
23
+ * The engine calls it on every value it is about to observe AS A VALUE — an
24
+ * operand of `==`/`in`/arithmetic, a function or method argument, a projected
25
+ * item, an `order by` or `distinct` key, a `where` scalar, a lift — and never
26
+ * on a value it reads in ROW POSITION (the query source, a block receiver, a
27
+ * body-level `from`, a `follow` destination), which reaches `toRows` and
28
+ * `indexFor` as handed out, so a store-backed context can answer a probe on
29
+ * the handle without reading the table and still never lets the stand-in be
30
+ * seen by the language. Absent = identity; a context whose handle already
31
+ * behaves as the collection (a `Proxy` over an array) needs none. */
32
+ materialize?(value: unknown): unknown;
20
33
  /** Identity of a row for `follow` cycle detection / dedup. */
21
34
  identity(row: unknown): unknown;
22
35
  /** Optional custom free function; return `{ handled: false }` to defer. */
@@ -31,6 +44,17 @@ export interface DataContext {
31
44
  * `regexMatches(recv, args, dialect)` is the helper). `BUILTIN_METHODS.matches`
32
45
  * is always the baseline. */
33
46
  readonly regexDialect?: RegexDialect;
47
+ /** Optional: a pre-built equality index over `collection` (a value this
48
+ * context served as a root or relation) on the property path `path`
49
+ * (`["customer_id"]`, `["meta", "id"]`; `[]` keys by the row itself). The
50
+ * engine asks before building its own hash index for a correlated equality
51
+ * in a nested block (`where id == ^customer_id`); return `undefined` to let
52
+ * it build one. `lookup(value)` must return the ascending positions, into
53
+ * `toRows(collection)` in order, of the rows whose value at `path` equals
54
+ * `value` under OQX equality (SEMANTICS §5: absent ≡ null, `-0` ≡ `0`, `NaN`
55
+ * matches nothing, objects by reference). `IndexedCollection.context()`
56
+ * implements it over its indexes. */
57
+ indexFor?(collection: unknown, path: readonly string[]): RowIndex | undefined;
34
58
  }
35
59
  export interface DefaultContextOptions {
36
60
  /** See `DataContext.regexDialect`. Default `"oqx"`. */
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ/C;kEACkE;AAClE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B;;;;;kFAK8E;IAC9E,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,8DAA8D;IAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC1C,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,2EAA2E;IAC3E,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IACzD,oEAAoE;IACpE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IACtE;;;;;;iCAM6B;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;CACtC;AAED,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;0EAG0E;AAC1E,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,KAAK,CAA0B;IACvC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;gBAExB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAAE,OAAO,GAAE,qBAA0B;IAKpF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI3B,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAMvC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IAIzC,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO;IAK/B,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU;IAKvD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU;CAKrE"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAQzD;kEACkE;AAClE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B;;;;;kFAK8E;IAC9E,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,8DAA8D;IAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC1C;;;;;;;;;;yEAUqE;IACrE,WAAW,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,2EAA2E;IAC3E,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IACzD,oEAAoE;IACpE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IACtE;;;;;;iCAM6B;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC;;;;;;;;;yCASqC;IACrC,QAAQ,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC/E;AAED,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;0EAG0E;AAC1E,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,KAAK,CAA0B;IACvC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;gBAExB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAAE,OAAO,GAAE,qBAA0B;IAKpF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI3B,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAMvC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IAIzC,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO;IAK/B,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU;IAKvD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU;CAKrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}