diff --git a/.changeset/opt-in-operators.md b/.changeset/opt-in-operators.md new file mode 100644 index 0000000000..507da726cc --- /dev/null +++ b/.changeset/opt-in-operators.md @@ -0,0 +1,18 @@ +--- +'@tanstack/db': minor +'@tanstack/react-db': patch +'@tanstack/preact-db': patch +'@tanstack/svelte-db': patch +'@tanstack/vue-db': patch +'@tanstack/solid-db': patch +'@tanstack/angular-db': patch +--- + +Breaking: grouping, includes, and pooling are now opt-in. An app bundles an operator's code only when it imports the value for that operator. Joins do not change. A typical app is about 8 kB smaller after gzip. This app has a query collection, a filtered live query, and React `useLiveQuery`. + +- An include needs `include()`, `toArray()`, or `materialize()`. Change `issues: q.from(...)` in `select()` to `issues: include(q.from(...))`. A subquery in `select()` without one of these throws `IncludeOperatorRequiredError`. +- Aggregates such as `count()` need no change. A `groupBy` with no aggregate takes `groupKeys()`: `.groupBy(groupKeys(({ todo }) => todo.status))`. Without it, the query throws `GroupingOperatorRequiredError`. +- Hooks do not pool live queries by default. To share one partition among many equality-filtered queries, pass `pool: pooledLiveQueries` in the hook's query config: `useLiveQuery({ query, pool: pooledLiveQueries })`. Results do not change. Angular's `{ params, query }` form takes `pool` the same way. Any other `pool` value, such as the old boolean, throws `InvalidPoolOptionError`, also for a disabled query. For adapter authors: the `pool` option of `resolveLiveQueryValue` is now `pooledLiveQueries` or `false`, not a boolean. An adapter that reads `pool` from its own options checks it with `configPool` first. +- Code that builds query IR by hand must supply the operator code. `IR.Aggregate` takes `groupingCompiler` as its third argument. `IR.IncludesSubquery` takes `includesRuntime` as its fifth argument, before `parentFilters`. Import both from `@tanstack/db`. IR without them throws `GroupingOperatorRequiredError` or `IncludeOperatorRequiredError` when it compiles, unless another aggregate or `groupKeys()` in the query supplies the grouping compiler. Queries that the query builder builds need no change. +- Operator code is part of query identity. A hook that re-renders a grouped query without `groupKeys()` now throws `GroupingOperatorRequiredError`. Before, React and Preact kept the grouped query. +- Vue: when a changed query fails to compile, `useLiveQuery` no longer shows the rows of the previous query. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6f3de15a97..422d067d3c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -26,6 +26,8 @@ jobs: uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 - name: Check lint run: pnpm run lint:check + - name: Typecheck doc and skill examples + run: pnpm run test:doc-snippets - name: Check oracle lint without inline directives run: pnpm run lint:oracles - name: Test oracle lint rules diff --git a/docs/contributing/oracle-coverage.md b/docs/contributing/oracle-coverage.md index 15a526e56d..8c7d880c48 100644 --- a/docs/contributing/oracle-coverage.md +++ b/docs/contributing/oracle-coverage.md @@ -631,7 +631,7 @@ generic visitor behavior outside the named collectors. | Correlated include path identity | `packages/db/tests/query/includes-context-transport-oracle.test.ts` | A flat parent field and nested parent field reach one-level and nested `toArray` results independently; dotted ancestor aliases remain distinct through a grandchild route. Initial results and parent updates have exact public-value checks. A conditional projection gives [`a.b`] and [`a`, `b`] different include results and checks parent-key changes plus later child inserts. Removing the compiler's unique route-key allocator loses a public child result; restoring dotted-string deduplication at either builder site loses a distinct parent value. These fixed witnesses do not establish arbitrary path segments, every recursive source form, or every materialization form. | | Alias scope identity | `packages/db/tests/query/includes-oracle.property.test.ts` (`includes alpha-renaming across lexical scopes`), `packages/db/tests/query/includes-scope-identity-oracle.ts`, `packages/db/tests/query/includes-alias-shadowing-oracle.test.ts`, `packages/db/tests/query/includes-outer-join-route-work-oracle.test.ts`, `packages/db/tests/query/validate-aliases.test.ts`, [issue #1975 review](oracle-reviews/issue-1975-scope-identity.md) | Four topologies place a source beside a same-named source: a `from()` subquery with an include, a joined `unionAll()` branch with an include, a collapsed top-level pure wrapper with a join subquery, and an include directly on a joined `unionAll()`, which may reuse a branch alias. The grammar crosses zero to two joins, LEFT or INNER, zero to two predicates, plain, ordered-and-limited, or DISTINCT bodies, include forms, and eager or on-demand finite providers, after preload and after source writes. Includes read their source directly, through a join, through a `unionAll()` with an anchor join, or through a `from()` subquery that reads the parent row. One in four scenarios draws any naming; same-scope and union-branch repeats are rejected while ancestor shadowing is admitted. Each checkpoint compares the canonical naming with plain recomputation, the generated naming with the canonical naming, and per-Collection `loadSubset` WHERE clauses. The exact-output companion independently recomputes parent-child rows for direct, QueryRef, union, and implicit-join forms across two equality operand orders, shadowed and renamed aliases, eager and on-demand children, initial publication and parent/child writes. The recursive forms keep a required outer lockId correlation and an independent captured lockName predicate; the union has a nonempty right branch and a later two-branch overlap. It checks enumerable user and virtual key names, user values, multiplicity, sibling helper reuse, a fresh parent/inner QueryRef declaration across a source update, direct, child union-branch, parent union-branch, parent QueryRef, and nested-include rejection of ancestor/descendant builder reuse, one nested grandparent capture, functional callback child input, grouping, captured whole-row and nested-path spreads across shadowed and renamed aliases, eager and on-demand children, and parent/child writes, and query identity. Focused follow-up cells check same-path group keys under shadowing and renaming through parent and child writes, alpha-renamed pure wrappers at public-row and optimized-identity cuts, and indexed lazy demand for direct or wrapped nested includes through anchor and user writes. Wrong-query controls that drop either inner predicate or substitute a parent-shaped callback value fail at the initial public-row comparison. A finite follow-up crosses direct includes and joined QueryRefs with shadowed or renamed aliases, eager or on-demand sources, and child IDs equal to or distinct from an outer filter literal; it checks public rows and on-demand child admission after preload. Another follow-up projects a captured parent rank through a child QueryRef and checks public rows after preload, a child-rank change, and a parent-rank change. The original compiler misrouted the outer filter in four cases, and the original optimizer lost the captured binding in two cases at their first public-row comparison. Every pinned witness and both campaigns fail on `18abceee4`; each of the four optimizer re-mint sites fails a witness when reverted. Arbitrary nested includes, include-inside-subquery forms beyond the checked LEFT joined QueryRef, other join-subquery topologies, `having`, outer joins beyond the bounded RIGHT/FULL matrix, publication events, unload, ordered windows, other lazy join targets, and temporal demand remain outside these owners; they need witnesses before an alias-scope closure claim covers them. Ancestor/descendant builder reuse is rejected before correlation extraction; one helper builder remains legal across a parent QueryRef, union branch, or joined QueryRef and a sibling include child. Six placement/source-mode cells check construction, exact rows after preload, and a source update; the joined placement includes an unmatched manager that would expose a missing helper join. The original source-tree guard rejects all six. A discarded enumeration of a captured proxy cannot change the selected query identity or rows before or after a child update; the retained-marker implementation fails that identity check. The [PR #2079 follow-up](oracle-reviews/issue-2072-alias-shadowing-followup.md) records its RED trace, the repaired spread and nested-QueryRef cells, and the remaining compiler and optimizer evidence gaps. The [ad5e0108 review](oracle-reviews/issue-2072-alias-shadowing-ad5e0108.md) adds a self-join with an inner alias matching the outer joined source, a joined child whose local source competes with inherited parent context in matched and unmatched LEFT joins, captured/local spreads, grandparent whole-row capture, and direct captured-alias selection. These finite cells compare public rows after preload and selected writes; an alias-only lazy-target mutant fails the eager self-join comparison. The reported on-demand failure did not reproduce with the controlled source. A further finite matrix crosses nested FROM or JOIN placement, field or whole-row capture of a shadowed outer joined alias, shadowed or renamed spelling, and eager or on-demand acquisition. It compares source-role rows after preload, child deletion and movement, joined-row update and movement, and parent update; on-demand cases compare nonempty request sets. Disabling projected binding lookup fails all eight cells at the initial public-row assertion; an alias-only lazy-target mutant survives this matrix, so it adds no broader lazy-target claim. A bounded outer-join extension crosses RIGHT and FULL joins, main- or joined-side correlation, direct and QueryRef joined sources, shadowed and renamed aliases, eager and on-demand sources, two parent routes, and seven initial or post-write cuts. Its independent source-role model checks exact public rows, including both absent sides; on-demand cells also require source acquisition. A join-operand extension crosses an ancestor term combined with a local main or joined term, or serving as the whole main key, with equality reversal, alias renaming, source mode, and five public-row cuts. The [outer-join follow-up](oracle-reviews/issue-2072-alias-shadowing-outer-joins.md) records RED and GREEN evidence. A foreign captured join operand must reject: a finite provenance matrix checks root and include placements, matching or different alias spelling, and eager or on-demand sources at construction or preload settlement; valid local and captured-ancestor operands remain controls. A two-LEFT-join history now checks that a later routed QueryRef or captured operand cannot restore an absent shadowed local source, across alias renaming, eager/on-demand mode, two parents, and local and second-joined arrival/removal. It also selects and updates a parent label while joins are matched, checking that the main route retains that ancestor value. A nested joined QueryRef control projects its join key from an inner tag source while the post alias shadows or renames an outer user alias; an independent plain-row model and installed on-demand post/tag rows are compared at preload, a user-key move, and a tag update. A wrong Collection/SourceId pair loads an irrelevant post at preload and fails the installed-row check. Other nested source shapes and real-provider request schedules remain outside this control. The route-work owner compares public rows and assembled parent-route work as irrelevant joined rows grow for direct main-correlated RIGHT/FULL joins in both source modes; an unconditional-route mutant grows from two to six pairs at preload. RIGHT joins correlated on main now join the outer-join row matrix, beside joined-side correlation and FULL controls. An include child rejects a bound join operand declared inside its parent QueryRef or union branch, across matching/renamed aliases and eager/on-demand sources; the original source-tree collector resolves preload instead of rejecting. A joined QueryRef rejects a captured source declared beside it in the enclosing query: the matrix crosses root/include placement, matching/renamed inner alias, and eager/on-demand sources; four true-ancestor controls assert rows, and the permissive sibling-scope mutant fails all eight invalid cells while those controls pass. The primary oracle now extends the hidden-source rejection through two QueryRef or union levels, with matching/renamed aliases and both source modes; a source-tree-binding mutant fails all eight added cells at the rejection cut. An unrelated bound reference is rejected in WHERE, GROUP BY, HAVING, ORDER BY, direct SELECT, and conditional SELECT across matching/renamed aliases; legal local controls and a grouped include with captured ancestor predicates, HAVING, ordering, shadowing, and parent/child writes prevent a blanket rejection. It also rejects foreign references moved into extracted include parent filters or into the removed correlation equality, each across alias spelling and source mode with valid parent controls. Disabling expression validation fails twelve non-join cells; omitting parent-filter or extracted-correlation validation fails four cells each. A separate plain relational model checks RIGHT-to-LEFT, RIGHT-to-RIGHT, FULL-to-FULL, and FULL-to-LEFT two-join chains under shadowing/renaming, both source modes, main/joined/tag correlation, and six source or ancestor writes; a later-FULL-as-LEFT mutant fails two initial public-row comparisons. The route-work owner now compares public rows and parent-route assembly counts at preload and four main-source writes while irrelevant joined rows grow. Other expression forms, arbitrary join-subquery and lazy-target shapes, deeper captures, other chained outer joins, real-provider schedules, and broader source-work bounds remain open under this owner. The alias-scope owner also checks outer grouping through a nested QueryRef and joined source with shadowed or renamed inner alias, eager or on-demand acquisition, and four source/tag writes at five public-row cuts. The tag insertion duplicates one joined source and the plain model counts both copies. A controlled rewrite probe reached an unbound pushed WHERE inside the QueryRef while the bound GROUP BY remained outside; an inner GROUP BY blocked that pushdown. A matcher mutant that ignored binding IDs failed two same-path group cells at the first public-row cut; a dropped-pushed-predicate mutant failed all four new cells by publishing an extra group. This refutes the reported WHERE-rewrite-to-GROUP-BY-matcher route in those shapes; another lost-provenance route needs a concrete fixture. Base/head work probes confirmed an extra pure-wrapper stage after #2079, but the same full-source outer-WHERE request and keyed lazy-join request on both revisions. No wrapper-stage work budget or safe binding remap is established. Bound root refs caused two route-metadata symbol checks per evaluation on the current head versus zero on base, while captured-ancestor rows remain protected by the primary oracle; a compile-time fast path needs a justified work budget and a preservation witness. The same function-form subquery placed twice compiled two results and invoked its callback four times for two source rows on both revisions. Cache comparison read more query properties on the current head, and a stateful callback produced placement-specific values. A callback identity or purity rule and work budget are needed before cache-equivalence changes. | | Join equality and cold acquisition | `packages/db/tests/query/cold-join-reconciliation-oracle.test.ts` | Independent recomputation for cold acquisition plus direct join/predicate equivalence across established equality domains. Binary/string and nullish classes, replacement histories, raw on-demand values, and both scan/auto-index paths are explicit; the compound lane also covers tuple equality, term order, all four join types, controlled cold sources, and keyed histories. Positive acquisition witnesses require a satisfiable tuple. | -| Aggregate selects | [aggregate select oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/aggregate-select-oracle.property.test.ts) | Generated select shapes of aggregate queries with and without `groupBy` by `c.k`, `upper(c.k)`, `inArray(c.k, ['a', 'b'])`, a Date-valued `coalesce` or a `NaN`-valued `add`, rebuilt per use (literals, parent fields, the group key, the raw field `c.k`, source fields, `count`/`sum`/`max`, `add`, `caseWhen` with expression or object branches, nested objects, spreads, a nested include) and a HAVING condition that may compare an aggregate, selected as a field or not, at the top level, directly or through a `unionAll`, and in an include whose source may shadow the parent alias, through parent updates and source inserts, updates (including key changes that move a row to another group and route) and deletes. A shape throws `NonAggregateExpressionNotInGroupByError` exactly when a non-key source field appears outside an aggregate in its select or HAVING, or the shape spreads a row or holds a nested include; otherwise every published group equals the model and keeps the select's field order. Open: several group keys. | +| Aggregate selects | [aggregate select oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/aggregate-select-oracle.property.test.ts) | Generated select shapes of aggregate queries with and without `groupBy` by `c.k`, the pair `c.k`, `gt(c.v, 1)`, the triple that adds `gt(c.v, 2)`, `upper(c.k)`, `inArray(c.k, ['a', 'b'])`, a Date-valued `coalesce` or a `NaN`-valued `add`, rebuilt per use (literals, parent fields, the group key, the raw field `c.k`, source fields, `count`/`sum`/`max`, `add`, `caseWhen` with expression or object branches, nested objects, spreads, a nested include) and a HAVING condition that may compare an aggregate, selected as a field or not, at the top level, directly or through a `unionAll`, and in an include whose source may shadow the parent alias, through parent updates and source inserts, updates (including key changes that move a row to another group and route) and deletes. A shape throws `NonAggregateExpressionNotInGroupByError` exactly when a non-key source field appears outside an aggregate in its select or HAVING, or the shape spreads a row or holds a nested include; otherwise every published group equals the model and keeps the select's field order. The same grammar checks the opt-in operator laws: a grouped query whose select and HAVING hold no aggregate throws `GroupingOperatorRequiredError` when its keys are passed bare, and works through `groupKeys()`; a bare query builder throws `IncludeOperatorRequiredError` as a field, inside a nested object, or in a conditional branch; a wrapped include inside a nested object throws (a conditional branch may be an include; the includes-collection oracle publishes one, and a compiler that rejects one fails both that oracle and this one). When several errors apply, any one of them passes. Includes are wrapped in `toArray()`, `include()`, or `materialize()`. In three generated histories of six, the driver strips one kind of operator code, the grouping compiler, the include runtime, or the grouping compiler of the `orderBy` aggregate only, from the IR it built. One history in three is a forced history that strips the grouping compiler everywhere or from `orderBy` only, with no, one, or two group keys in turn, so every campaign reaches each path and compiles it through `new BaseQueryBuilder(ir)`; IR without its operator code must throw the coded error for the missing operator. A runtime-stripped history gives its include a child query that is valid on its own, so other errors cannot hide the missing runtime. Half the grouping-stripped histories give the top-level query a select of `count()` alone, with an aggregate HAVING when the generated shape has one, so a model error set of exactly `groupingRequired` witnesses the grouped, one-group, and HAVING paths. The top-level and include queries may order their groups with one or two `orderBy` calls, each on an aggregate or on a number or string select field through `$selected`, strings with the documented `en-US` collation: published rows and each parent's `kids` fall into blocks of equal values in that order. An `orderBy` aggregate that no top-level field selects throws `AggregateFunctionNotInSelectError` (now stated in the guide's "Ordering by SELECT Fields"), and a stripped `orderBy` aggregate compiles with the compiler that `groupKeys()` or a select or HAVING aggregate brings. One history in four takes ordered shapes from a grammar whose shapes compile, because generated ordered shapes compile in about one case in forty; the source starts with a key `A`, where locale and code-unit order differ. The property declares twenty-one states with `createOracleReach`, and each campaign of 160 histories reaches each of them; without the forced select, the one-group and HAVING states were never reached, and a mutant that throws the wrong error class only on the implicit path passed three campaigns. Pinned witnesses in `hand-built-ir-operators.test.ts` cover the old `IncludesSubquery` argument order, an aggregate only in `orderBy` without a select aggregate, and the mixed `orderBy` replay. The compiler before the order step took the query's grouping compiler fails the generated `orderBy` dimension (`top compiles`); an order step that ignores direction, keeps only the first `orderBy` call, or sorts strings by code unit, an include query that ignores direction, a group step that keeps only the first one or two keys, and a check that throws the wrong error class only beside HAVING each fail it (the last passed two of three campaigns before the alone states). A mutant without the include-runtime check, which publishes `kids: null`, and one without the grouping guard, which throws a `TypeError`, both fail it. A mutant that skips the grouping-operator check fails it with a `TypeError`, and one that treats a bare builder as an include fails it with the wrong error class. Open: an `orderBy` term on a raw expression other than an aggregate, and on boolean, Date, `NaN`, or object fields, whose order the docs do not state. The operator laws are generated only for aggregate and grouped selects; for plain selects, `includes.test.ts` pins a bare builder as a field, nested, and in a conditional, because this model publishes one row per group and needs a separate row-per-source model for them. | | Query optimizer semantics | `packages/db/tests/query/optimizer-semantics-oracle.test.ts`, [aggregate review](oracle-reviews/2026-09-28-optimizer-aggregate-pushdown.md), [QueryRef repair](oracle-reviews/2026-09-30-queryref-publication-repair.md), [residual review](oracle-reviews/2026-10-01-pr-1977-residual-convergence.md) | Independent sum recomputation and a materialized-Collection formulation check the first public snapshot of a nested aggregate under a left join. Direct, arithmetic-wrapped, and conditional aggregates distinguish safe from unsafe pushdown; a grouped-key control covers the grouped boundary. Matching and nonmatching global aggregates also cross a nested QueryRef inner join with absent, accepting, and rejecting outer predicates. The matching initial cases fail on the pre-repair revision and pass after the computed-projection lookup repair. A separate fixed residual-convergence grammar checks exact predicate and outer WHERE clause counts at `optimizeQuery` return and after re-optimization: LEFT, RIGHT, INNER, and FULL joins; equality, greater-than, nullable-field `isUndefined`, same-source OR, cross-source equality, clause order, separate/AND form, two active predicates, two residual sources, and an existing residual marker. Plain-array LEFT/RIGHT and three-source join models check first public rows, including unmatched rows. Arbitrary expressions, more complex join chains, and incremental optimizer/publication histories remain outside this owner. | | QueryRef operators and user-value boundaries | `packages/db/tests/query/subquery-user-value-oracle.test.ts`, `packages/db/tests/transactions.test.ts`, [repair record](oracle-reviews/2026-09-30-queryref-publication-repair.md) | A finite array/Set model checks the compiled output bag for a joined DISTINCT subquery with an outer WHERE. Live-query drivers check selected containers, no-select, functional, and predicate-literal user objects with IR-like fields, top-level and aggregate-subquery proxy-shaped fields, arrays of selected references, outer virtual-field filters on joined DISTINCT and nested aggregate QueryRefs, and a renamed no-select source. The transaction suite documents the existing development-browser duplicate-load guard. The bounded cases run in `test:oracles` or the full DB suite. Joined DISTINCT, nested and flat aggregate source-update histories pass. Ordered joined `findOne()` checks initial, singleton, empty, restored, and changed-join-key cuts; an unordered `findOne()` QueryRef on either side of a join checks default-key candidate deletion and reinsertion. A materialized joined `findOne()` supplies a separate receiving formulation. Other `singleResult` forms, join forms, schedules, and cross-copy value handling outside the duplicate-load guard remain unproved. | | Opaque backend pagination | [window oracle](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.oracle.test.ts), [cache histories](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.cache-oracle.test.ts), [cache publication](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.publication-oracle.test.ts), [browser acquisition boundaries](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.boundary-oracle.test.ts), [QueryCollection integration](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination-oracle.integration.test.ts) | Full filter/sort/slice reference, opaque token transport, actual Query cache expiry/invalidation/GC, forced refresh during growth, protocol failure publication/recovery, bounded slice work, nested cancellation/replacement, reader abort, browser retry defaults, manual-write cache isolation, and production window publications. Stable backend sequences; not snapshot guarantees for changing endpoints. Peek-ahead remains enabled. | @@ -650,7 +650,7 @@ generic visitor behavior outside the named collectors. | WHERE predicate publication | [WHERE predicate publication oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/where-predicate-publication-oracle.property.test.ts), [`in` evaluator](https://github.com/TanStack/db/blob/main/packages/db/tests/query/compiler/in-evaluator-oracle.property.test.ts) ([review](oracle-reviews/perf-in-constant-set.md)) | An independent Kleene evaluator judges `eq`/`not`/`and`/`or` predicates over strings, a normalization-prefixed string, booleans, numbers, `NaN`, a valid Date, `null`, a missing field, and virtual fields. Snapshot histories with pending optimistic inserts compare a live query, direct subscribers with and without initial state, and `currentStateAsChanges`; change histories apply multi-key sync transactions, including reinsertion of a key deleted earlier, and compare every consumer's key set after each commit, on scan and `BasicIndex` paths. Peer subscribers share one field with different literals, plus one on a virtual field, so every published change must reach exactly the subscribers whose predicate it can satisfy; routing mutants that ignored the previous value, ignored the field, delivered a change twice, or routed while stale published rows awaited reconciliation fail here. Fixed witnesses cover a layout-only publication reaching a filtered subscriber as one empty batch, a filtered subscriber's empty Collection-readiness batch, retraction of a vanished row after eager cleanup and restart, and the new row plus exact subscriber update payload when a same-key row stays TRUE. A stale-live-value mutant survives the generated membership checks but fails the payload witness. A focused [property-visibility test](https://github.com/TanStack/db/blob/main/packages/db/tests/query/where-prefilter-property-visibility.test.ts) compares the public unindexed snapshot with the enriched-row contract for inherited, non-enumerable, enumerable-own, and nested getter paths; the pre-fix stored-row shortcut failed three of its four controls. Other property-descriptor and stateful-getter histories remain outside those fixed controls. Hostile mutants for FALSE-for-UNKNOWN `eq`, `or` or number-literal subscription prefilters, a prefilter that ignores the previous value, a skipped readiness batch, a skip while stale published rows await reconciliation, and an unindexed snapshot scan that reads only synced rows while an optimistic insert or delete changes visibility passed the prior `@tanstack/db` suite and fail here. Change routing withholds a dropped row from an `eq` subscription before its sent key could be recorded, so a mutant that records dropped rows as sent is equivalent for routed predicates in this oracle. A focused `collection-subscription.test.ts` witness checks that a dropped insert or update, alone or beside a matching change, cannot advance a limited subscription's page offset under a routed `eq` and an unrouted `or` predicate; the unrouted cases beside a matching change kill that mutant, and the unrouted `alone` case does not. A loose-equality prefilter is an equivalent mutant: it only skips less. Skipping during truncate replay also survives; the replay's baseline diff re-derives the same retraction, so the guard keeps the prior dataflow without its own witness. Generated cleanup and restart histories for filtered subscribers remain open for the lifecycle publication owner. No-op updates, other comparison operators, Temporal and binary operands, joins, ordering, optimistic updates, and truncate are outside this owner. It runs in `@tanstack/db`'s `test:oracles` campaign. The [2026-09-30 review](https://github.com/TanStack/db/blob/main/docs/contributing/oracle-reviews/2026-09-30-where-predicate-and-join-keys.md) records each ORC outcome. Change routing was later removed in favor of pooled live queries; the routing mutants above are historical, and the same peer laws now check plain subscription dispatch. The unindexed snapshot prefilter was restored for compiled queries as one stored-row `eq` test on a string or boolean literal. Its property-visibility test also covers the reverse direction: a stored row with an inherited or non-enumerable field must still match `isUndefined` on that field, which a scan that evaluated the full predicate on stored rows fails. The `in` evaluator owner checks `in` against an independent linear reference over Dates, `NaN`, `-0`, BigInt, bytes, Temporal values, objects and nullish items, for constant and row-supplied lists. | | Joined result keys | [Joined result key oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/join-result-key-oracle.property.test.ts) | A nested-loop model recomputes the pair set of an inner, left, or full join over source keys drawn from plain, comma-bearing, bracket-bearing, and quoted strings, numbers beside the strings that print the same, both infinities, and `NaN`. After preload and each synced group change, the published rows must equal the model's pairs and the result key count must equal the row count. The comma-joined key encoding fails its two pinned histories and both campaigns; plain `JSON.stringify`, which prints infinities as the missing-side `null`, fails the pinned infinity history and both campaigns. A pinned history in which a `NaN`-keyed pair leaves and re-forms fails when the join index compares source-key prefixes with `===`. Joins over subqueries, more than two sources, custom `getKey`, and optimistic mutations are outside this owner. It runs in `@tanstack/db`'s `test:oracles` campaign. The [2026-09-30 review](https://github.com/TanStack/db/blob/main/docs/contributing/oracle-reviews/2026-09-30-where-predicate-and-join-keys.md) records each ORC outcome. | | D2 Index storage | [Index refinement oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/index-refinement-oracle.property.test.ts) | A plain-`Map` model sums multiplicities under a type-and-value identity in which `NaN` equals `NaN` and `-0` equals `0`. Histories of up to twelve additions to two keys mix prefixed arrays with `NaN`, `0`, `-0`, `1`, `'1'`, `1n`, and `'a'` prefixes and unprefixed values, with cancellation and reappearance. After each addition, `get` and `has` must equal the model. Each step also checks that `get` returns what `getIterator` yields, in its order, since the two walk the storage separately. Comparing prefixes with `===`, which disagrees with the `Map` that holds them, fails three pinned histories and both campaigns; the `-0` control passes under both. Every history also runs with `prefixIdentity`, where values under one key with the same prefix are one value; mutants that merge unprefixed values or ignore the option in a prefix map fail. The [structural identity oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/structural-identity-oracle.property.test.ts) checks that `structuralIdentity` agrees with D2 value identity for equal representations (array subclasses, class instances, null prototypes, key order, non-enumerable symbols) and independent values, including separator-bearing strings and keys, enumerable symbol keys, Dates, RegExps with numeric `lastIndex`, Maps and Sets with zero to two entries (the second Set member is a fresh tagged array, so primitive deduplication and unwrapped reordering are outside this lane), arrays with an optional enumerable `note` field, binary values, holes, deep nesting, and a cyclic value; an independent normal-form model decides equality and is checked against `equalHashValues`. The model expands `deep` shorthand into nested arrays. A pinned equal `deep`/array pair and adjacent-depth control guard the model boundary exposed by seed 1158727847/path 255. The equal-pair grammar crosses same-spec values with explicit-array/`deep` equivalent specs; the independent-pair grammar uses two specs. Both run fixed and fresh campaigns with guarded one-case seed/path replay. Pinned same-description symbols and equal-body Map/Set/object pairs expose collisions that random independent pairs rarely reach. Mutants that stringify symbols, collapse those three object-kind tags, drop a string's or key's length or an array's length, or omit Date and RegExp headers fail it. A first-entry-only Map/Set mutant and an array-identity mutant that ignores enumerable non-index fields fail the pinned comparisons; lowering the depth cap is equivalent, because the cap only bounds work. Open: reference-valued RegExp `lastIndex` can give D2-unequal values one defined identity and merge `groupBy` contributions ([#2192](https://github.com/TanStack/db/issues/2192)); the structural-identity and incrementalization owners need a production repair and receiving witness. Temporal values, other opaque reference values, large binary values, getters/proxies, and mutation between observations are outside this generated grammar. groupBy's use of it is owned by the incrementalization law: its string-extrema lane fails a string identity without a length prefix, and a focused groupBy witness fails when the group key is left out of the identity. Compaction, presence tracking, and structural payloads are outside this owner; a focused test checks that an index built without presence (as reduce builds its indexes) throws when presence is read; the [reduce fast-path oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/reduce-fast-path-oracle.property.test.ts) compares the fast path's emitted entries one by one with the general path's, over keys that move between the paths, hash-merged outputs, -0, and zero and negative multiplicities, while an unchanged `NaN` output is owned by a focused reduce test; the join operator tests and incrementalization law own operator behavior. | -| Pooled live queries | [pooled live query oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/pooled-live-query-oracle.property.test.ts) | An independent `eq` model (nullish UNKNOWN, Date as timestamp, `NaN` equal to `NaN`, `-0` equal to `0`) gives membership; a live-query Collection compiled for the same query gives order, values, and status. Histories mix sync transactions, optimistic writes confirmed or rolled back, peer mounts and unmounts, and source cleanup and restart, including cleanup while a write is pending and a mount between cleanup and restart. Wholesale observers must match after every step, granular changes must match a reference observer by type, key, value, and previous value and replay to the model's rows, the layout revision must advance when keys reorder, and observing must never build the live-query Collection. Peers may add a residual `not(eq(...))` conjunct, which the model evaluates itself; a view that ignores it, reports an unfiltered `entries()`, or delivers a row entering or leaving it as an update fails it. Other residual operators use the same compiler evaluator but are not generated. Peers may also order their rows by `id` or by a `g` that can be null, with explicit `nulls`; order comes from the reference. A comparator that ignores direction or `nulls`, an order left out of the partition key, and key order instead of the query's order each fail the pinned reorder history and both campaigns. A partition that ignores a row's previous group, orders by arrival, skips normalization, skips the initial snapshot, follows the source's status after cleanup, or never terminates fails it. Two shared conformance scenarios (`eq-filter-rows`, `eq-filter-peers`) run the pooled path under React, Vue, Solid, Svelte, and Angular; a partition that ignores a row's previous group fails both under every adapter, and `eq-filter-peers` checks through `subscriberCount` that each adapter shares one source subscription per partition while mounted and releases every source subscription after the final framework unmount. A focused [release-timing test](https://github.com/TanStack/db/blob/main/packages/db/tests/query/pooled-live-query-gc-oracle.test.ts) compares when a pooled query and a live-query Collection release their sources across `gcTime` 1, 100, 0, and `Infinity`, with and without a subscriber, and with no `gcTime`, where both use the live-query Collection default, checks that a partition waits for its views' longest `gcTime`, checks that a view resubscribed after its partition released follows later source writes as its live-query Collection does, including a view with a residual conjunct, and checks that a partition that subscribes again still serves new mounts from one source subscription. Further focused witnesses check that a partition keeps groups only for values that have rows or watchers, that a detached reader never sees a dropped group's revision reused, keep a pooled query's public Collection live while the view is observed, accept it as a query source, and check that a pooled query turns terminal when source cleanup starts while the adapter's cleanup is still pending, as a live-query Collection does. A pinned oracle witness checks that an `eq` path whose getter throws excludes the row on both paths; a pooled path that rethrows fails it. The pooled oracle's grammar never releases a partition, so release and resubscribe histories rely on these fixed witnesses; a fixed delay, a missing unsubscribed floor, a last-`gcTime`-wins rule, and releasing at `gcTime` 0 each fail it. Removing the partition's final-listener release survives the mounted row checks but fails the shared framework unmount checkpoint and core release-timing owner. The [2026-10-01 review](oracle-reviews/2026-10-01-pooled-live-queries.md) records each ORC outcome. | +| Pooled live queries | [pooled live query oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/pooled-live-query-oracle.property.test.ts) | An independent `eq` model (nullish UNKNOWN, Date as timestamp, `NaN` equal to `NaN`, `-0` equal to `0`) gives membership; a live-query Collection compiled for the same query gives order, values, and status. Histories mix sync transactions, optimistic writes confirmed or rolled back, peer mounts and unmounts, and source cleanup and restart, including cleanup while a write is pending and a mount between cleanup and restart. Wholesale observers must match after every step, granular changes must match a reference observer by type, key, value, and previous value and replay to the model's rows, the layout revision must advance when keys reorder, and observing must never build the live-query Collection. Peers may add a residual `not(eq(...))` conjunct, which the model evaluates itself; a view that ignores it, reports an unfiltered `entries()`, or delivers a row entering or leaving it as an update fails it. Other residual operators use the same compiler evaluator but are not generated. Peers may also order their rows by `id` or by a `g` that can be null, with explicit `nulls`; order comes from the reference. A comparator that ignores direction or `nulls`, an order left out of the partition key, and key order instead of the query's order each fail the pinned reorder history and both campaigns. A partition that ignores a row's previous group, orders by arrival, skips normalization, skips the initial snapshot, follows the source's status after cleanup, or never terminates fails it. Two shared conformance scenarios (`eq-filter-rows`, `eq-filter-peers`) run the pooled path under React, Vue, Solid, Svelte, and Angular; a partition that ignores a row's previous group fails both under every adapter, and `eq-filter-peers` checks through `subscriberCount` that each adapter shares one source subscription per partition while mounted and releases every source subscription after the final framework unmount. A focused [release-timing test](https://github.com/TanStack/db/blob/main/packages/db/tests/query/pooled-live-query-gc-oracle.test.ts) compares when a pooled query and a live-query Collection release their sources across `gcTime` 1, 100, 0, and `Infinity`, with and without a subscriber, and with no `gcTime`, where both use the live-query Collection default, checks that a partition waits for its views' longest `gcTime`, checks that a view resubscribed after its partition released follows later source writes as its live-query Collection does, including a view with a residual conjunct, and checks that a partition that subscribes again still serves new mounts from one source subscription. Further focused witnesses check that a partition keeps groups only for values that have rows or watchers, that a detached reader never sees a dropped group's revision reused, keep a pooled query's public Collection live while the view is observed, accept it as a query source, and check that a pooled query turns terminal when source cleanup starts while the adapter's cleanup is still pending, as a live-query Collection does. A pinned oracle witness checks that an `eq` path whose getter throws excludes the row on both paths; a pooled path that rethrows fails it. The pooled oracle's grammar never releases a partition, so release and resubscribe histories rely on these fixed witnesses; a fixed delay, a missing unsubscribed floor, a last-`gcTime`-wins rule, and releasing at `gcTime` 0 each fail it. Removing the partition's final-listener release survives the mounted row checks but fails the shared framework unmount checkpoint and core release-timing owner. The shared adapter conformance suite checks the opt-in boundary in every framework: queries without `pool` keep a source subscription each in the function, config, and parameterized forms, and a pooled parameterized query follows its parameter between pool groups beside an unchanged peer. Pooling every query by default, a pooled identity that ignores literal values (React then reuses the old query), and an Angular `params` form that drops `pool` each fail it. `pooled-param-change` also reads each driver's publication log: every value published after a switch holds only the new group's rows. Each driver also logs its keyed `state` map at the same points, and the law applies to it. A pooled view that serves the previous view's rows until a later task replaces them passes every final-state check and fails only the log, in all six drivers. A view that sends the previous rows as its first change set fails the `state` log in Vue; Solid rebuilds its map from a snapshot within the same turn, and Angular builds its map from `entries()`, so that mutant cannot reach them. The `invalid-pool` scenario requires `InvalidPoolOptionError` for `true` and for `false` (the value adapters pass to say a hook cannot pool), alone, beside `gcTime`, beside `queryKey`, on a parameterized query, and on a disabled config or parameterized query, in all six drivers; a check in only one of the three readers of `pool` fails it in four of them, and Angular's `params` form failed the `false` and disabled cases until it checked its pool first. The shared `grouping-operator-switch` scenario re-renders a grouped query without `groupKeys()`, and `compile-error-after-rerender` re-renders to a select that mixes a source field with an aggregate; both require the error and no rows after it. A binding that keeps its last compiled query when compilation throws fails both in Solid, Svelte, and Angular; one that throws once and then serves that query fails the no-rows check in Solid and Svelte, and cannot occur in Angular, whose `computed` rethrows a cached error until an input changes. In `grouping-operator-switch`, React and Preact kept the grouped query before query identity held operator code, and Vue served the previous rows before its compile error stopped reaching readers through a throwing computed (also on `main` at `5649a07d4`). The [2026-10-01 review](oracle-reviews/2026-10-01-pooled-live-queries.md) records each ORC outcome. | | Live-query deferred acquisition | [deferred acquisition oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/live-query-deferred-acquisition-oracle.test.ts), [React Suspense warm-cache witness](https://github.com/TanStack/db/blob/main/packages/react-db/tests/suspense-warm-cache.test.tsx) | An independent model of the law that a live-query Collection starts no provider work before a subscriber or a preload in its current sync run crosses five source states (eager or on-demand, idle or already started, and an on-demand source that already holds the rows), an unordered query and an ordered window, one or two live-query levels, and seven histories: start and read, subscribe or preload after a start, subscribe or preload without one, cleanup before a subscriber, and restart after a run that had one. After every command it checks source sync-run starts, acquisition attempts, readiness when the call returns (the synchronous observation cut), settled readiness, and rows. A pooled block checks that building a pooled view starts no idle eager source until a view subscribes or preloads, and a DbClient block checks that a preload answered by a pending or settled stream still resumes acquisition. Mutants that never defer, keep the subscriber-or-preload fact across cleanup, ignore preload, never resume a builder's subscriptions, let a deferring subscriber start a base source, build a pooled partition without deferral, never resume it, mark a preload one microtask late, or drop the stream's preload mark each fail it. The React witness checks that `useLiveSuspenseQuery` over a source that already holds the rows renders without the fallback, for eager and on-demand sources and both shapes; creating the Suspense collection started, or not preloading it in render, fails it. A later review added `peer` and `truncate` commands: a peer that starts an idle on-demand source, or a truncate on a running one, made the live query acquire before any subscriber, and both guards are killed separately. Pinned blocks check that a subscriber surviving cleanup is served after a restart while a surviving deferring subscriber is not, and that `toArrayWhenReady()` and `stateWhenReady()` count as a preload. A pinned block checks that a deferring live query is not counted in its source's `subscriberCount` or `subscribers:change` until its first subscriber, while still keeping the source from garbage collection; a Query Collection test checks that it causes no refetch. Another pinned block checks that a preload or subscriber requested by an inner live query's status handler, while the outer query creates its subscription to it, still resumes acquisition. Solid's Suspense receiving witnesses check that a resource whose preload settles first retains its live-query Collection while its sibling remains pending past the unsubscribed floor, then releases that temporary owner on both commit and abandonment, and that a preload settling after its owner unmounts cannot publish an error through the abandoned result. Open: source failure and asynchronous loads before resumption, which the subscription lifecycle oracle owns once acquisition resumes; a resume that throws, which leaves later sources unstarted only for a live query that is already in error; asynchronous sources; and Suspense with a `DbClient`, where render already defers source starts. | | Live-query notification algebra | [real-query and checker calibration](https://github.com/TanStack/db/blob/main/packages/db/tests/notification-laws-oracle.test.ts), [independent Map checker](https://github.com/TanStack/db/blob/main/packages/db-collection-e2e/src/utils/notification-laws-oracle.ts) | One-key synchronous source histories check exact live-query callback reconstruction and unsubscribe silence. Synthetic multi-key histories calibrate the checker; direct source Collection events remain with the change-event history owner. | | Flat-row change tracking | [flat change tracking oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/flat-change-tracking-oracle.property.test.ts) | Generated callbacks over flat rows, frozen or not and with or without a non-enumerable field (assignments of `0`, `-0`, `NaN`, `undefined`, functions, and fresh objects; reverts; deletions; added fields; data and accessor `defineProperty`; stored drafts; throws), run through the flat tracker, the draft proxy, and an independent model, which must report the same change sets with `0` and `-0` equal, or throw the same error and leave the rows unchanged. Defining a field acts as assigning it, and a non-enumerable field is row data only once written; the proxy broke three of those laws on `main` and the flat tracker one. Diffs using `!==`, `Object.is` alone, or no deletions fail it. It found that the proxy dropped a field added as `undefined` when another field reverted; the pinned history and both campaigns fail without that fix. Non-flat rows must fall back. The [2026-10-01 review](oracle-reviews/2026-10-01-pooled-live-queries.md) records each ORC outcome. | @@ -3766,3 +3766,91 @@ do not depend on its resolution. Real server ordering, framework unmount scheduling, and published npm behavior require receiving integration witnesses; the present evidence is controlled DB source execution. The revision-bound review record is `docs/contributing/oracle-reviews/issue-2058-paced-serialization.md`. + +## Opt-in operator bundles + +Grouping, includes, and pooling ship only with the value that names them: an +aggregate or `groupKeys`, an include function, or `pooledLiveQueries`. Joins +are core. `packages/db/tests/opt-in-operator-bundle.test.ts` owns that law. It +bundles small apps from source with esbuild and reads which modules reach the +output. Each of the 10 operator values runs alone, and every one of the 8 +combinations of operator families runs with rotating values. An operator's +modules must be present exactly when the app imports one of its values. The +join compiler and public-key reduction must be present in every app. The +single-value runs are positive controls, so a renamed or moved module cannot +pass the absence checks. A mutant that statically imports +`BucketFacadeAdapter` into the live-query builder, and one that uses a +`group-by.ts` helper from the live-query Collection builder, fail it. + +The owner checks module reachability only, not byte budgets; `pnpm +size:compare` measures bytes. It bundles `@tanstack/db` alone, with other +packages external, so it does not check db-ivm operators or framework +adapters. Query results under the new API are owned by the existing query and +includes oracles, which run with migrated queries; no oracle compares a +query's old and new spellings on one input. + +Operator code is part of query identity. The +[IR identity oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/query/ir-stable-identity-oracle.test.ts) +runs every combination of `groupBy` (none, bare keys, `groupKeys()`), a select +aggregate (none, built, without its grouping compiler), and an include (none, +built, without its runtime), checks each compile outcome against a small +model, and requires distinct identities for any two combinations whose +outcomes differ. Removing the identity mark for `groupKeys()`, for an +aggregate without its compiler, or for an include without its runtime each +fails it. A second check runs a wider product: `groupBy`, a select, HAVING, +and `orderBy` aggregate each built or without its compiler, an include as a +field, in a `caseWhen` branch, or with a child aggregate, each built or +stripped, and an `eq`-filtered query without a select that a pool can serve. +It takes each outcome from production compilation and requires one outcome +per identity, for the IR identity and for the prepared identity with +`pooledLiveQueries`. Each identity mark's removal fails it, and so does a +pool shortcut that accepts a `groupBy` query. Bare and `groupKeys()` keys +beside an aggregate may share an identity or not; the law leaves that open. + +Open counterexample, not fixed by this change: the ordered-window random +campaign in +`packages/db/tests/query/load-subset-replay-refinement-oracle.test.ts` fails +with `TANSTACK_DB_ORACLE_SEED=1593300187` (path `121:1:2:3:2:2`) and with +seed `-1005431615` (path `68:1:2`). Both shrink to one history: `hold`, +`truncateMarkers`, `settle` with a load failure, over an ascending limit-one +window on an on-demand marker source with includes. The convergence check +sees a terminal error. Main at `65d9ada50` and the merge-base `5649a07d4` +fail with the same counterexample, so the operator change did not introduce +it. The campaign reaches this defect in ordinary runs, so CI can fail on it +intermittently until the defect is fixed. Owner: the load-subset replay +owner. Needed witness: a decision whether a truncate during a held, then +failed, load may end the window in a terminal error, and the fix or model +change that follows. + +Open counterexample on `main`, not caused by this change: the random +campaign of `packages/db/tests/query/ordered-work-oracle.property.test.ts` +(`eager indexed ordered windows > keeps rows and per-change work bounded`) +fails with `TANSTACK_DB_ORACLE_SEED=1023848973` (path +`34:3:3:3:3:4:3:3:4:4:4`). The history is an on-demand, refetch-signal, +anti-joined, unindexed, ascending limit-one window with nulls first: upsert +row 0 as ineligible, then delete the visible row. The check finds two +full-source requests after a signalling refetch, where it expects none. +`main` at `5649a07d4` fails the same way. Seed `-436579979` reaches the same +check through a joined window (`filter: joined`, `via: link`), fails on +`main` at `5649a07d4` too, and in CI timed out at 5 seconds while it shrank. +The campaign can fail CI intermittently until the defect is fixed. Owner: the ordered-work owner. +Needed witness: whether this unindexed window may use full-source recovery, +which this map allows for unindexed underfilled continuations; if so the +check is too strict for it, and if not production is wrong. + +Open observation without a trace: in one CI run of this change, the random +campaign of `packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts` +timed out at 120 seconds before it printed a seed. The previous CI run took +29 seconds, and twelve local runs with random seeds passed in 38 to 50 +seconds each. Owner: the refetch ordered-prefix owner. Needed witness: a +seed that reproduces the slow history, and whether it is a slow shrink of a +real counterexample or a slow passing history. + +Open observation without a trace: during this change, one run of the random +campaign in `packages/db/tests/query/includes-oracle.property.test.ts` failed +on the branch. The run did not record its seed or counterexample. Thirteen +later dedicated runs and two full-suite runs on the branch passed, and the +campaign was not run on `main` at that time. Owner: the includes oracle owner. Needed witness: the failing seed and +path from a later run. Until then, this entry cannot tell an operator +regression from an older defect. A later failure of that campaign must record +its seed here before anyone calls it pre-existing. diff --git a/docs/errors.md b/docs/errors.md index 784bb6cbcb..06a56ab68f 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -1000,6 +1000,36 @@ Development builds show the full message. For example: [useLiveQuery] Collection ID "todos" named a different source Collection than one this live query already read. While the hook is mounted, each Collection ID must keep naming one source Collection. Unmount the hook and clean up its previous source and client scope before reusing the ID. ``` + + +## Error 247: `GroupingOperatorRequiredError` + +Development builds show the full message. For example: + +```text +groupBy() without an aggregate needs groupKeys(), such as .groupBy(groupKeys(({ todo }) => todo.status)). Import groupKeys from @tanstack/db. A query with an aggregate such as count() needs nothing extra. Query IR built by hand passes groupingCompiler to each IR.Aggregate. +``` + + + +## Error 248: `IncludeOperatorRequiredError` + +Development builds show the full message. For example: + +```text +A subquery in select() needs include(), toArray(), or materialize(). Wrap the issues field: issues: include(q.from({ i: issues }).where(...)). Import include from @tanstack/db. Query IR built by hand passes includesRuntime to each IR.IncludesSubquery. +``` + + + +## Error 251: `InvalidPoolOptionError` + +Development builds show the full message. For example: + +```text +A live query's pool option takes pooledLiveQueries from @tanstack/db, or nothing. Pooling is opt-in: pass pool: pooledLiveQueries to share one partition among equality-filtered queries. +``` + ## Other errors and log messages These are plain `Error`, `TypeError`, `RangeError`, or @@ -1668,7 +1698,7 @@ Query '${this.id}' has no collection sources. This should not happen; please rep -## Error 154: `query/live/materialized-pipeline.ts` +## Error 154: `query/live/public-key-reduction.ts` Development builds show this message, with `${...}` replaced by values: @@ -1678,7 +1708,7 @@ Canonical query row has negative multiplicity -## Error 155: `query/live/materialized-pipeline.ts` +## Error 155: `query/live/public-key-reduction.ts` Development builds show this message, with `${...}` replaced by values: @@ -1688,7 +1718,7 @@ Canonical query row has no positive contributor -## Error 156: `query/live/materialized-pipeline.ts` +## Error 156: `query/live/public-key-reduction.ts` Development builds show this message, with `${...}` replaced by values: @@ -2489,3 +2519,23 @@ Development builds show this message, with `${...}` replaced by values: ```text getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination. ``` + + + +## Error 249: `query/builder/index.ts` + +Development builds show this message, with `${...}` replaced by values: + +```text +include() must wrap a subquery builder +``` + + + +## Error 250: `query/builder/index.ts` + +Development builds show this message, with `${...}` replaced by values: + +```text +include() must wrap a subquery builder +``` diff --git a/docs/framework/angular/reference/functions/injectLiveQuery.md b/docs/framework/angular/reference/functions/injectLiveQuery.md index 0bb18bb03e..34c82b063a 100644 --- a/docs/framework/angular/reference/functions/injectLiveQuery.md +++ b/docs/framework/angular/reference/functions/injectLiveQuery.md @@ -9,7 +9,7 @@ title: injectLiveQuery function injectLiveQuery(options): InjectLiveQueryResult; ``` -Defined in: [index.ts:119](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L119) +Defined in: [angular-db/src/index.ts:121](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L121) ### Type Parameters @@ -29,6 +29,13 @@ Defined in: [index.ts:119](https://github.com/TanStack/db/blob/main/packages/ang () => `TParams` +##### pool? + +\{ +\} + +Pass `pooledLiveQueries` to share one partition among equal-shape queries. + ##### query (`args`) => `QueryBuilder`\<`TContext`\> @@ -43,7 +50,7 @@ Defined in: [index.ts:119](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(options): InjectConditionalLiveQueryResult; ``` -Defined in: [index.ts:129](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L129) +Defined in: [angular-db/src/index.ts:133](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L133) ### Type Parameters @@ -63,6 +70,13 @@ Defined in: [index.ts:129](https://github.com/TanStack/db/blob/main/packages/ang () => `TParams` +##### pool? + +\{ +\} + +Pass `pooledLiveQueries` to share one partition among equal-shape queries. + ##### query (`args`) => `QueryBuilder`\<`TContext`\> \| `null` \| `undefined` @@ -77,7 +91,7 @@ Defined in: [index.ts:129](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(queryFn): InjectLiveQueryResult; ``` -Defined in: [index.ts:139](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L139) +Defined in: [angular-db/src/index.ts:145](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L145) ### Type Parameters @@ -101,7 +115,7 @@ Defined in: [index.ts:139](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(queryFn): InjectConditionalLiveQueryResult; ``` -Defined in: [index.ts:142](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L142) +Defined in: [angular-db/src/index.ts:148](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L148) ### Type Parameters @@ -125,7 +139,7 @@ Defined in: [index.ts:142](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(config): InjectLiveQueryResult; ``` -Defined in: [index.ts:147](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L147) +Defined in: [angular-db/src/index.ts:153](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L153) ### Type Parameters @@ -137,7 +151,7 @@ Defined in: [index.ts:147](https://github.com/TanStack/db/blob/main/packages/ang #### config -`LiveQueryCollectionConfig`\<`TContext`\> +`LiveQueryHookConfig`\<`TContext`\> ### Returns @@ -149,7 +163,7 @@ Defined in: [index.ts:147](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(liveQueryCollection): InjectLiveQueryResultWithCollection; ``` -Defined in: [index.ts:151](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L151) +Defined in: [angular-db/src/index.ts:157](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L157) ### Type Parameters @@ -181,7 +195,7 @@ Defined in: [index.ts:151](https://github.com/TanStack/db/blob/main/packages/ang function injectLiveQuery(liveQueryCollection): InjectLiveQueryResultWithSingleResultCollection; ``` -Defined in: [index.ts:159](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L159) +Defined in: [angular-db/src/index.ts:165](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L165) ### Type Parameters diff --git a/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md b/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md index a9ffbe32b2..2b75bbccc7 100644 --- a/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md +++ b/docs/framework/angular/reference/interfaces/InjectLiveQueryResult.md @@ -3,7 +3,7 @@ id: InjectLiveQueryResult title: InjectLiveQueryResult --- -Defined in: [index.ts:41](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L41) +Defined in: [angular-db/src/index.ts:43](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L43) The result of calling `injectLiveQuery`. Contains reactive signals for the query state and data. @@ -25,7 +25,7 @@ collection: Signal< | null>; ``` -Defined in: [index.ts:47](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L47) +Defined in: [angular-db/src/index.ts:49](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L49) A signal containing the underlying collection instance (null for disabled queries) @@ -37,7 +37,7 @@ A signal containing the underlying collection instance (null for disabled querie data: Signal>; ``` -Defined in: [index.ts:45](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L45) +Defined in: [angular-db/src/index.ts:47](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L47) A signal containing the results as an array, or single result for findOne queries @@ -49,7 +49,7 @@ A signal containing the results as an array, or single result for findOne querie isCleanedUp: Signal; ``` -Defined in: [index.ts:66](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L66) +Defined in: [angular-db/src/index.ts:68](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L68) A signal indicating whether the collection has been cleaned up @@ -61,7 +61,7 @@ A signal indicating whether the collection has been cleaned up isError: Signal; ``` -Defined in: [index.ts:64](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L64) +Defined in: [angular-db/src/index.ts:66](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L66) A signal indicating whether the collection has an error @@ -73,7 +73,7 @@ A signal indicating whether the collection has an error isIdle: Signal; ``` -Defined in: [index.ts:62](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L62) +Defined in: [angular-db/src/index.ts:64](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L64) A signal indicating whether the collection is idle @@ -85,7 +85,7 @@ A signal indicating whether the collection is idle isLoading: Signal; ``` -Defined in: [index.ts:55](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L55) +Defined in: [angular-db/src/index.ts:57](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L57) A signal indicating whether the collection is currently loading @@ -97,7 +97,7 @@ A signal indicating whether the collection is currently loading isPersistedReady: Signal; ``` -Defined in: [index.ts:59](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L59) +Defined in: [angular-db/src/index.ts:61](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L61) *** @@ -107,7 +107,7 @@ Defined in: [index.ts:59](https://github.com/TanStack/db/blob/main/packages/angu isReady: Signal; ``` -Defined in: [index.ts:57](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L57) +Defined in: [angular-db/src/index.ts:59](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L59) A signal indicating whether the collection is ready @@ -119,7 +119,7 @@ A signal indicating whether the collection is ready persistedError: Signal; ``` -Defined in: [index.ts:60](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L60) +Defined in: [angular-db/src/index.ts:62](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L62) *** @@ -129,7 +129,7 @@ Defined in: [index.ts:60](https://github.com/TanStack/db/blob/main/packages/angu persistedStatus: Signal; ``` -Defined in: [index.ts:58](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L58) +Defined in: [angular-db/src/index.ts:60](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L60) *** @@ -139,7 +139,7 @@ Defined in: [index.ts:58](https://github.com/TanStack/db/blob/main/packages/angu state: Signal[K] }>>; ``` -Defined in: [index.ts:43](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L43) +Defined in: [angular-db/src/index.ts:45](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L45) A signal containing the complete state map of results keyed by their ID @@ -151,6 +151,6 @@ A signal containing the complete state map of results keyed by their ID status: Signal; ``` -Defined in: [index.ts:53](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L53) +Defined in: [angular-db/src/index.ts:55](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L55) A signal containing the current status of the collection diff --git a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md index 5892128e83..73015809c5 100644 --- a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md +++ b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithCollection.md @@ -3,7 +3,7 @@ id: InjectLiveQueryResultWithCollection title: InjectLiveQueryResultWithCollection --- -Defined in: [index.ts:81](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L81) +Defined in: [angular-db/src/index.ts:83](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L83) ## Type Parameters @@ -30,7 +30,7 @@ collection: Signal< | null>; ``` -Defined in: [index.ts:88](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L88) +Defined in: [angular-db/src/index.ts:90](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L90) *** @@ -40,7 +40,7 @@ Defined in: [index.ts:88](https://github.com/TanStack/db/blob/main/packages/angu data: Signal; ``` -Defined in: [index.ts:87](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L87) +Defined in: [angular-db/src/index.ts:89](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L89) *** @@ -50,7 +50,7 @@ Defined in: [index.ts:87](https://github.com/TanStack/db/blob/main/packages/angu isCleanedUp: Signal; ``` -Defined in: [index.ts:97](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L97) +Defined in: [angular-db/src/index.ts:99](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L99) *** @@ -60,7 +60,7 @@ Defined in: [index.ts:97](https://github.com/TanStack/db/blob/main/packages/angu isError: Signal; ``` -Defined in: [index.ts:96](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L96) +Defined in: [angular-db/src/index.ts:98](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L98) *** @@ -70,7 +70,7 @@ Defined in: [index.ts:96](https://github.com/TanStack/db/blob/main/packages/angu isIdle: Signal; ``` -Defined in: [index.ts:95](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L95) +Defined in: [angular-db/src/index.ts:97](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L97) *** @@ -80,7 +80,7 @@ Defined in: [index.ts:95](https://github.com/TanStack/db/blob/main/packages/angu isLoading: Signal; ``` -Defined in: [index.ts:90](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L90) +Defined in: [angular-db/src/index.ts:92](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L92) *** @@ -90,7 +90,7 @@ Defined in: [index.ts:90](https://github.com/TanStack/db/blob/main/packages/angu isPersistedReady: Signal; ``` -Defined in: [index.ts:93](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L93) +Defined in: [angular-db/src/index.ts:95](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L95) *** @@ -100,7 +100,7 @@ Defined in: [index.ts:93](https://github.com/TanStack/db/blob/main/packages/angu isReady: Signal; ``` -Defined in: [index.ts:91](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L91) +Defined in: [angular-db/src/index.ts:93](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L93) *** @@ -110,7 +110,7 @@ Defined in: [index.ts:91](https://github.com/TanStack/db/blob/main/packages/angu persistedError: Signal; ``` -Defined in: [index.ts:94](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L94) +Defined in: [angular-db/src/index.ts:96](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L96) *** @@ -120,7 +120,7 @@ Defined in: [index.ts:94](https://github.com/TanStack/db/blob/main/packages/angu persistedStatus: Signal; ``` -Defined in: [index.ts:92](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L92) +Defined in: [angular-db/src/index.ts:94](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L94) *** @@ -130,7 +130,7 @@ Defined in: [index.ts:92](https://github.com/TanStack/db/blob/main/packages/angu state: Signal>; ``` -Defined in: [index.ts:86](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L86) +Defined in: [angular-db/src/index.ts:88](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L88) *** @@ -140,4 +140,4 @@ Defined in: [index.ts:86](https://github.com/TanStack/db/blob/main/packages/angu status: Signal; ``` -Defined in: [index.ts:89](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L89) +Defined in: [angular-db/src/index.ts:91](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L91) diff --git a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md index e456bd589b..4177723fa5 100644 --- a/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md +++ b/docs/framework/angular/reference/interfaces/InjectLiveQueryResultWithSingleResultCollection.md @@ -3,7 +3,7 @@ id: InjectLiveQueryResultWithSingleResultCollection title: InjectLiveQueryResultWithSingleResultCollection --- -Defined in: [index.ts:100](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L100) +Defined in: [angular-db/src/index.ts:102](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L102) ## Type Parameters @@ -30,7 +30,7 @@ collection: Signal< | null>; ``` -Defined in: [index.ts:107](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L107) +Defined in: [angular-db/src/index.ts:109](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L109) *** @@ -40,7 +40,7 @@ Defined in: [index.ts:107](https://github.com/TanStack/db/blob/main/packages/ang data: Signal; ``` -Defined in: [index.ts:106](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L106) +Defined in: [angular-db/src/index.ts:108](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L108) *** @@ -50,7 +50,7 @@ Defined in: [index.ts:106](https://github.com/TanStack/db/blob/main/packages/ang isCleanedUp: Signal; ``` -Defined in: [index.ts:116](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L116) +Defined in: [angular-db/src/index.ts:118](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L118) *** @@ -60,7 +60,7 @@ Defined in: [index.ts:116](https://github.com/TanStack/db/blob/main/packages/ang isError: Signal; ``` -Defined in: [index.ts:115](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L115) +Defined in: [angular-db/src/index.ts:117](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L117) *** @@ -70,7 +70,7 @@ Defined in: [index.ts:115](https://github.com/TanStack/db/blob/main/packages/ang isIdle: Signal; ``` -Defined in: [index.ts:114](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L114) +Defined in: [angular-db/src/index.ts:116](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L116) *** @@ -80,7 +80,7 @@ Defined in: [index.ts:114](https://github.com/TanStack/db/blob/main/packages/ang isLoading: Signal; ``` -Defined in: [index.ts:109](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L109) +Defined in: [angular-db/src/index.ts:111](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L111) *** @@ -90,7 +90,7 @@ Defined in: [index.ts:109](https://github.com/TanStack/db/blob/main/packages/ang isPersistedReady: Signal; ``` -Defined in: [index.ts:112](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L112) +Defined in: [angular-db/src/index.ts:114](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L114) *** @@ -100,7 +100,7 @@ Defined in: [index.ts:112](https://github.com/TanStack/db/blob/main/packages/ang isReady: Signal; ``` -Defined in: [index.ts:110](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L110) +Defined in: [angular-db/src/index.ts:112](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L112) *** @@ -110,7 +110,7 @@ Defined in: [index.ts:110](https://github.com/TanStack/db/blob/main/packages/ang persistedError: Signal; ``` -Defined in: [index.ts:113](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L113) +Defined in: [angular-db/src/index.ts:115](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L115) *** @@ -120,7 +120,7 @@ Defined in: [index.ts:113](https://github.com/TanStack/db/blob/main/packages/ang persistedStatus: Signal; ``` -Defined in: [index.ts:111](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L111) +Defined in: [angular-db/src/index.ts:113](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L113) *** @@ -130,7 +130,7 @@ Defined in: [index.ts:111](https://github.com/TanStack/db/blob/main/packages/ang state: Signal>; ``` -Defined in: [index.ts:105](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L105) +Defined in: [angular-db/src/index.ts:107](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L107) *** @@ -140,4 +140,4 @@ Defined in: [index.ts:105](https://github.com/TanStack/db/blob/main/packages/ang status: Signal; ``` -Defined in: [index.ts:108](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L108) +Defined in: [angular-db/src/index.ts:110](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L110) diff --git a/docs/framework/angular/reference/type-aliases/InjectConditionalLiveQueryResult.md b/docs/framework/angular/reference/type-aliases/InjectConditionalLiveQueryResult.md index 9f231ce34a..efcb3e4295 100644 --- a/docs/framework/angular/reference/type-aliases/InjectConditionalLiveQueryResult.md +++ b/docs/framework/angular/reference/type-aliases/InjectConditionalLiveQueryResult.md @@ -7,7 +7,7 @@ title: InjectConditionalLiveQueryResult type InjectConditionalLiveQueryResult = Omit, "data"> & object; ``` -Defined in: [index.ts:74](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L74) +Defined in: [angular-db/src/index.ts:76](https://github.com/TanStack/db/blob/main/packages/angular-db/src/index.ts#L76) ## Type Declaration diff --git a/docs/framework/preact/reference/functions/useLiveQuery.md b/docs/framework/preact/reference/functions/useLiveQuery.md index 9542b7a160..d1a5d86a53 100644 --- a/docs/framework/preact/reference/functions/useLiveQuery.md +++ b/docs/framework/preact/reference/functions/useLiveQuery.md @@ -9,7 +9,7 @@ title: useLiveQuery function useLiveQuery(queryFn): QueryResult; ``` -Defined in: [useLiveQuery.ts:147](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L147) +Defined in: [useLiveQuery.ts:153](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L153) Subscribe a Preact component to a live query. @@ -64,7 +64,7 @@ const { data, isEnabled } = useLiveQuery((q) => function useLiveQuery(queryFn): ConditionalQueryResult; ``` -Defined in: [useLiveQuery.ts:151](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L151) +Defined in: [useLiveQuery.ts:157](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L157) Subscribe a Preact component to a live query. @@ -119,7 +119,7 @@ const { data, isEnabled } = useLiveQuery((q) => function useLiveQuery(queryFn): ConditionalQueryResult; ``` -Defined in: [useLiveQuery.ts:157](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L157) +Defined in: [useLiveQuery.ts:163](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L163) Subscribe a Preact component to a live query. @@ -177,7 +177,7 @@ const { data, isEnabled } = useLiveQuery((q) => function useLiveQuery(queryFn): UseConditionalLiveQueryResult, TResult>>; ``` -Defined in: [useLiveQuery.ts:163](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L163) +Defined in: [useLiveQuery.ts:169](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L169) Subscribe a Preact component to a live query. @@ -243,7 +243,7 @@ const { data, isEnabled } = useLiveQuery((q) => function useLiveQuery(config): QueryResult; ``` -Defined in: [useLiveQuery.ts:187](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L187) +Defined in: [useLiveQuery.ts:193](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L193) Subscribe to a live query described by a config object. @@ -278,7 +278,7 @@ const { data } = useLiveQuery({ function useLiveQuery(config): ConditionalQueryResult; ``` -Defined in: [useLiveQuery.ts:191](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L191) +Defined in: [useLiveQuery.ts:197](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L197) Subscribe a Preact component to a live query. @@ -333,7 +333,7 @@ const { data, isEnabled } = useLiveQuery((q) => function useLiveQuery(liveQueryCollection): UseLiveQueryResult, TResult>>; ``` -Defined in: [useLiveQuery.ts:205](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L205) +Defined in: [useLiveQuery.ts:211](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L211) Subscribe to an existing live-query Collection. The hook starts its sync and leaves its retention policy to the caller. @@ -377,7 +377,7 @@ const { data, collection } = useLiveQuery(activeTodos) function useLiveQuery(liveQueryCollection): UseLiveQueryResult, TResult> & SingleResult>; ``` -Defined in: [useLiveQuery.ts:218](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L218) +Defined in: [useLiveQuery.ts:224](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L224) Subscribe a Preact component to a live query. diff --git a/docs/framework/preact/reference/interfaces/UseConditionalLiveQueryResult.md b/docs/framework/preact/reference/interfaces/UseConditionalLiveQueryResult.md index 5f52579be5..fae81b3eba 100644 --- a/docs/framework/preact/reference/interfaces/UseConditionalLiveQueryResult.md +++ b/docs/framework/preact/reference/interfaces/UseConditionalLiveQueryResult.md @@ -3,7 +3,7 @@ id: UseConditionalLiveQueryResult title: UseConditionalLiveQueryResult --- -Defined in: [useLiveQuery.ts:91](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L91) +Defined in: [useLiveQuery.ts:97](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L97) The result of a live query whose callback can disable it. @@ -37,7 +37,7 @@ The result of a live query whose callback can disable it. collection: TCollection | undefined; ``` -Defined in: [useLiveQuery.ts:99](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L99) +Defined in: [useLiveQuery.ts:105](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L105) *** @@ -47,7 +47,7 @@ Defined in: [useLiveQuery.ts:99](https://github.com/TanStack/db/blob/main/packag data: TData | undefined; ``` -Defined in: [useLiveQuery.ts:98](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L98) +Defined in: [useLiveQuery.ts:104](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L104) *** @@ -57,7 +57,7 @@ Defined in: [useLiveQuery.ts:98](https://github.com/TanStack/db/blob/main/packag isCleanedUp: boolean; ``` -Defined in: [useLiveQuery.ts:73](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L73) +Defined in: [useLiveQuery.ts:79](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L79) #### Inherited from @@ -73,7 +73,7 @@ LiveQueryResultFlags.isCleanedUp isEnabled: boolean; ``` -Defined in: [useLiveQuery.ts:101](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L101) +Defined in: [useLiveQuery.ts:107](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L107) *** @@ -83,7 +83,7 @@ Defined in: [useLiveQuery.ts:101](https://github.com/TanStack/db/blob/main/packa isError: boolean; ``` -Defined in: [useLiveQuery.ts:72](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L72) +Defined in: [useLiveQuery.ts:78](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L78) #### Inherited from @@ -99,7 +99,7 @@ LiveQueryResultFlags.isError isIdle: boolean; ``` -Defined in: [useLiveQuery.ts:71](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L71) +Defined in: [useLiveQuery.ts:77](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L77) #### Inherited from @@ -115,7 +115,7 @@ LiveQueryResultFlags.isIdle isLoading: boolean; ``` -Defined in: [useLiveQuery.ts:66](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L66) +Defined in: [useLiveQuery.ts:72](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L72) #### Inherited from @@ -131,7 +131,7 @@ LiveQueryResultFlags.isLoading isPersistedReady: boolean; ``` -Defined in: [useLiveQuery.ts:69](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L69) +Defined in: [useLiveQuery.ts:75](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L75) #### Inherited from @@ -147,7 +147,7 @@ LiveQueryResultFlags.isPersistedReady isReady: boolean; ``` -Defined in: [useLiveQuery.ts:67](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L67) +Defined in: [useLiveQuery.ts:73](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L73) #### Inherited from @@ -163,7 +163,7 @@ LiveQueryResultFlags.isReady persistedError: unknown; ``` -Defined in: [useLiveQuery.ts:70](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L70) +Defined in: [useLiveQuery.ts:76](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L76) #### Inherited from @@ -179,7 +179,7 @@ LiveQueryResultFlags.persistedError persistedStatus: LiveQueryPersistedStatus; ``` -Defined in: [useLiveQuery.ts:68](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L68) +Defined in: [useLiveQuery.ts:74](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L74) #### Inherited from @@ -195,7 +195,7 @@ LiveQueryResultFlags.persistedStatus state: ReadonlyMap | undefined; ``` -Defined in: [useLiveQuery.ts:97](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L97) +Defined in: [useLiveQuery.ts:103](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L103) *** @@ -205,4 +205,4 @@ Defined in: [useLiveQuery.ts:97](https://github.com/TanStack/db/blob/main/packag status: UseLiveQueryStatus; ``` -Defined in: [useLiveQuery.ts:100](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L100) +Defined in: [useLiveQuery.ts:106](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L106) diff --git a/docs/framework/preact/reference/interfaces/UseLiveQueryResult.md b/docs/framework/preact/reference/interfaces/UseLiveQueryResult.md index 49c2c10762..dd99bba681 100644 --- a/docs/framework/preact/reference/interfaces/UseLiveQueryResult.md +++ b/docs/framework/preact/reference/interfaces/UseLiveQueryResult.md @@ -3,7 +3,7 @@ id: UseLiveQueryResult title: UseLiveQueryResult --- -Defined in: [useLiveQuery.ts:77](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L77) +Defined in: [useLiveQuery.ts:83](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L83) The result of a live query that is always enabled. @@ -37,7 +37,7 @@ The result of a live query that is always enabled. collection: TCollection; ``` -Defined in: [useLiveQuery.ts:85](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L85) +Defined in: [useLiveQuery.ts:91](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L91) *** @@ -47,7 +47,7 @@ Defined in: [useLiveQuery.ts:85](https://github.com/TanStack/db/blob/main/packag data: TData; ``` -Defined in: [useLiveQuery.ts:84](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L84) +Defined in: [useLiveQuery.ts:90](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L90) *** @@ -57,7 +57,7 @@ Defined in: [useLiveQuery.ts:84](https://github.com/TanStack/db/blob/main/packag isCleanedUp: boolean; ``` -Defined in: [useLiveQuery.ts:73](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L73) +Defined in: [useLiveQuery.ts:79](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L79) #### Inherited from @@ -73,7 +73,7 @@ LiveQueryResultFlags.isCleanedUp isEnabled: true; ``` -Defined in: [useLiveQuery.ts:87](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L87) +Defined in: [useLiveQuery.ts:93](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L93) *** @@ -83,7 +83,7 @@ Defined in: [useLiveQuery.ts:87](https://github.com/TanStack/db/blob/main/packag isError: boolean; ``` -Defined in: [useLiveQuery.ts:72](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L72) +Defined in: [useLiveQuery.ts:78](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L78) #### Inherited from @@ -99,7 +99,7 @@ LiveQueryResultFlags.isError isIdle: boolean; ``` -Defined in: [useLiveQuery.ts:71](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L71) +Defined in: [useLiveQuery.ts:77](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L77) #### Inherited from @@ -115,7 +115,7 @@ LiveQueryResultFlags.isIdle isLoading: boolean; ``` -Defined in: [useLiveQuery.ts:66](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L66) +Defined in: [useLiveQuery.ts:72](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L72) #### Inherited from @@ -131,7 +131,7 @@ LiveQueryResultFlags.isLoading isPersistedReady: boolean; ``` -Defined in: [useLiveQuery.ts:69](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L69) +Defined in: [useLiveQuery.ts:75](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L75) #### Inherited from @@ -147,7 +147,7 @@ LiveQueryResultFlags.isPersistedReady isReady: boolean; ``` -Defined in: [useLiveQuery.ts:67](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L67) +Defined in: [useLiveQuery.ts:73](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L73) #### Inherited from @@ -163,7 +163,7 @@ LiveQueryResultFlags.isReady persistedError: unknown; ``` -Defined in: [useLiveQuery.ts:70](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L70) +Defined in: [useLiveQuery.ts:76](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L76) #### Inherited from @@ -179,7 +179,7 @@ LiveQueryResultFlags.persistedError persistedStatus: LiveQueryPersistedStatus; ``` -Defined in: [useLiveQuery.ts:68](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L68) +Defined in: [useLiveQuery.ts:74](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L74) #### Inherited from @@ -195,7 +195,7 @@ LiveQueryResultFlags.persistedStatus state: ReadonlyMap; ``` -Defined in: [useLiveQuery.ts:83](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L83) +Defined in: [useLiveQuery.ts:89](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L89) *** @@ -205,4 +205,4 @@ Defined in: [useLiveQuery.ts:83](https://github.com/TanStack/db/blob/main/packag status: CollectionStatus; ``` -Defined in: [useLiveQuery.ts:86](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L86) +Defined in: [useLiveQuery.ts:92](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L92) diff --git a/docs/framework/preact/reference/type-aliases/ConditionalUseLiveQueryConfig.md b/docs/framework/preact/reference/type-aliases/ConditionalUseLiveQueryConfig.md index 203d6ccebf..34e9645937 100644 --- a/docs/framework/preact/reference/type-aliases/ConditionalUseLiveQueryConfig.md +++ b/docs/framework/preact/reference/type-aliases/ConditionalUseLiveQueryConfig.md @@ -7,7 +7,7 @@ title: ConditionalUseLiveQueryConfig type ConditionalUseLiveQueryConfig = UseLiveQueryConfigOptions & object; ``` -Defined in: [useLiveQuery.ts:56](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L56) +Defined in: [useLiveQuery.ts:62](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L62) ## Type Declaration diff --git a/docs/framework/preact/reference/type-aliases/UseLiveQueryConfig.md b/docs/framework/preact/reference/type-aliases/UseLiveQueryConfig.md index 3b70c0b69b..d37cf43186 100644 --- a/docs/framework/preact/reference/type-aliases/UseLiveQueryConfig.md +++ b/docs/framework/preact/reference/type-aliases/UseLiveQueryConfig.md @@ -7,7 +7,7 @@ title: UseLiveQueryConfig type UseLiveQueryConfig = UseLiveQueryConfigOptions & Pick, "query">; ``` -Defined in: [useLiveQuery.ts:52](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L52) +Defined in: [useLiveQuery.ts:58](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L58) ## Type Parameters diff --git a/docs/framework/preact/reference/type-aliases/UseLiveQueryStatus.md b/docs/framework/preact/reference/type-aliases/UseLiveQueryStatus.md index a363085f99..b62eb6f9f0 100644 --- a/docs/framework/preact/reference/type-aliases/UseLiveQueryStatus.md +++ b/docs/framework/preact/reference/type-aliases/UseLiveQueryStatus.md @@ -7,4 +7,4 @@ title: UseLiveQueryStatus type UseLiveQueryStatus = CollectionStatus | "disabled"; ``` -Defined in: [useLiveQuery.ts:34](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L34) +Defined in: [useLiveQuery.ts:35](https://github.com/TanStack/db/blob/main/packages/preact-db/src/useLiveQuery.ts#L35) diff --git a/docs/framework/react/reference/functions/useLiveInfiniteQuery.md b/docs/framework/react/reference/functions/useLiveInfiniteQuery.md index 1d1cdea03d..20b857081c 100644 --- a/docs/framework/react/reference/functions/useLiveInfiniteQuery.md +++ b/docs/framework/react/reference/functions/useLiveInfiniteQuery.md @@ -9,7 +9,7 @@ title: useLiveInfiniteQuery function useLiveInfiniteQuery(liveQueryCollection, config): UseLiveInfiniteQueryReturnWithCollection; ``` -Defined in: [useLiveInfiniteQuery.ts:126](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L126) +Defined in: [useLiveInfiniteQuery.ts:129](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L129) Create an infinite query using a query function with live updates. @@ -57,7 +57,7 @@ function useLiveInfiniteQuery( deps?): UseLiveInfiniteQueryReturn; ``` -Defined in: [useLiveInfiniteQuery.ts:136](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L136) +Defined in: [useLiveInfiniteQuery.ts:139](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L139) Create an infinite query using a query function with live updates. diff --git a/docs/framework/react/reference/functions/useLiveQuery.md b/docs/framework/react/reference/functions/useLiveQuery.md index 225df385b6..b50338be2d 100644 --- a/docs/framework/react/reference/functions/useLiveQuery.md +++ b/docs/framework/react/reference/functions/useLiveQuery.md @@ -9,7 +9,7 @@ title: useLiveQuery function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:379](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L379) +Defined in: [useLiveQuery.ts:386](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L386) Create a live query using a query function. @@ -196,7 +196,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:399](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L399) +Defined in: [useLiveQuery.ts:406](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L406) Create a live query using a query function. @@ -387,7 +387,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:421](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L421) +Defined in: [useLiveQuery.ts:428](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L428) Create a live query using a query function. @@ -581,7 +581,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:443](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L443) +Defined in: [useLiveQuery.ts:450](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L450) Create a live query using a query function. @@ -780,7 +780,7 @@ return ( function useLiveQuery(queryFn, deps?): object; ``` -Defined in: [useLiveQuery.ts:469](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L469) +Defined in: [useLiveQuery.ts:476](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L476) Create a live query using a query function. @@ -990,7 +990,7 @@ return ( function useLiveQuery(config): object; ``` -Defined in: [useLiveQuery.ts:540](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L540) +Defined in: [useLiveQuery.ts:547](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L547) Create a live query using configuration object @@ -1134,7 +1134,7 @@ return
{data.length} items loaded
function useLiveQuery(config): object; ``` -Defined in: [useLiveQuery.ts:559](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L559) +Defined in: [useLiveQuery.ts:566](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L566) Create a live query using a query function. @@ -1317,7 +1317,7 @@ return ( function useLiveQuery(config, deps?): object; ``` -Defined in: [useLiveQuery.ts:578](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L578) +Defined in: [useLiveQuery.ts:585](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L585) Create a live query using a query function. @@ -1502,7 +1502,7 @@ return ( function useLiveQuery(config, deps): object; ``` -Defined in: [useLiveQuery.ts:598](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L598) +Defined in: [useLiveQuery.ts:605](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L605) Create a live query using a query function. @@ -1691,7 +1691,7 @@ return ( function useLiveQuery(liveQueryCollection): object; ``` -Defined in: [useLiveQuery.ts:647](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L647) +Defined in: [useLiveQuery.ts:654](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L654) Subscribe to an existing live query collection @@ -1837,7 +1837,7 @@ return
{data.map(item => )}
function useLiveQuery(liveQueryCollection): object; ``` -Defined in: [useLiveQuery.ts:670](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L670) +Defined in: [useLiveQuery.ts:677](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L677) Create a live query using a query function. diff --git a/docs/framework/react/reference/type-aliases/ConditionalUseLiveQueryConfig.md b/docs/framework/react/reference/type-aliases/ConditionalUseLiveQueryConfig.md index a1028ff4db..c1a0daa3a6 100644 --- a/docs/framework/react/reference/type-aliases/ConditionalUseLiveQueryConfig.md +++ b/docs/framework/react/reference/type-aliases/ConditionalUseLiveQueryConfig.md @@ -7,7 +7,7 @@ title: ConditionalUseLiveQueryConfig type ConditionalUseLiveQueryConfig = UseLiveQueryConfigOptions & object; ``` -Defined in: [useLiveQuery.ts:138](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L138) +Defined in: [useLiveQuery.ts:145](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L145) ## Type Declaration diff --git a/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryConfig.md b/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryConfig.md index e5b66073a2..b0cd7defcb 100644 --- a/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryConfig.md +++ b/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryConfig.md @@ -7,7 +7,7 @@ title: UseLiveInfiniteQueryConfig type UseLiveInfiniteQueryConfig<_TContext> = object; ``` -Defined in: [useLiveInfiniteQuery.ts:45](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L45) +Defined in: [useLiveInfiniteQuery.ts:46](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L46) ## Type Parameters @@ -23,7 +23,7 @@ Defined in: [useLiveInfiniteQuery.ts:45](https://github.com/TanStack/db/blob/mai optional client: DbClient; ``` -Defined in: [useLiveInfiniteQuery.ts:53](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L53) +Defined in: [useLiveInfiniteQuery.ts:54](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L54) Override the nearest DbProvider for this query. @@ -35,7 +35,7 @@ Override the nearest DbProvider for this query. optional initialPageParam: number; ``` -Defined in: [useLiveInfiniteQuery.ts:56](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L56) +Defined in: [useLiveInfiniteQuery.ts:57](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L57) First result-page label, not a server cursor or remote offset. @@ -47,7 +47,7 @@ First result-page label, not a server cursor or remote offset. optional pageSize: number; ``` -Defined in: [useLiveInfiniteQuery.ts:54](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L54) +Defined in: [useLiveInfiniteQuery.ts:55](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L55) *** @@ -57,7 +57,7 @@ Defined in: [useLiveInfiniteQuery.ts:54](https://github.com/TanStack/db/blob/mai optional queryKey: LiveQueryKey; ``` -Defined in: [useLiveInfiniteQuery.ts:51](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L51) +Defined in: [useLiveInfiniteQuery.ts:52](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L52) Explicit identity for queries that contain opaque functional variants or are hot enough that deriving identity from structured IR is too expensive. diff --git a/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturn.md b/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturn.md index 39d83d380d..838a782998 100644 --- a/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturn.md +++ b/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturn.md @@ -7,7 +7,7 @@ title: UseLiveInfiniteQueryReturn type UseLiveInfiniteQueryReturn = Omit, "data"> & object; ``` -Defined in: [useLiveInfiniteQuery.ts:59](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L59) +Defined in: [useLiveInfiniteQuery.ts:60](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L60) ## Type Declaration diff --git a/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturnWithCollection.md b/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturnWithCollection.md index 83484bdae2..7ee2fa59e8 100644 --- a/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturnWithCollection.md +++ b/docs/framework/react/reference/type-aliases/UseLiveInfiniteQueryReturnWithCollection.md @@ -7,7 +7,7 @@ title: UseLiveInfiniteQueryReturnWithCollection type UseLiveInfiniteQueryReturnWithCollection = object; ``` -Defined in: [useLiveInfiniteQuery.ts:72](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L72) +Defined in: [useLiveInfiniteQuery.ts:73](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L73) ## Type Parameters @@ -31,7 +31,7 @@ Defined in: [useLiveInfiniteQuery.ts:72](https://github.com/TanStack/db/blob/mai collection: Collection & NonSingleResult; ``` -Defined in: [useLiveInfiniteQuery.ts:79](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L79) +Defined in: [useLiveInfiniteQuery.ts:80](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L80) *** @@ -41,7 +41,7 @@ Defined in: [useLiveInfiniteQuery.ts:79](https://github.com/TanStack/db/blob/mai data: TResult[]; ``` -Defined in: [useLiveInfiniteQuery.ts:77](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L77) +Defined in: [useLiveInfiniteQuery.ts:78](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L78) *** @@ -51,7 +51,7 @@ Defined in: [useLiveInfiniteQuery.ts:77](https://github.com/TanStack/db/blob/mai error: unknown; ``` -Defined in: [useLiveInfiniteQuery.ts:95](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L95) +Defined in: [useLiveInfiniteQuery.ts:96](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L96) *** @@ -61,7 +61,7 @@ Defined in: [useLiveInfiniteQuery.ts:95](https://github.com/TanStack/db/blob/mai fetchNextPage: () => Promise; ``` -Defined in: [useLiveInfiniteQuery.ts:92](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L92) +Defined in: [useLiveInfiniteQuery.ts:93](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L93) #### Returns @@ -75,7 +75,7 @@ Defined in: [useLiveInfiniteQuery.ts:92](https://github.com/TanStack/db/blob/mai hasNextPage: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:93](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L93) +Defined in: [useLiveInfiniteQuery.ts:94](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L94) *** @@ -85,7 +85,7 @@ Defined in: [useLiveInfiniteQuery.ts:93](https://github.com/TanStack/db/blob/mai isCleanedUp: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:88](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L88) +Defined in: [useLiveInfiniteQuery.ts:89](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L89) *** @@ -95,7 +95,7 @@ Defined in: [useLiveInfiniteQuery.ts:88](https://github.com/TanStack/db/blob/mai isEnabled: true; ``` -Defined in: [useLiveInfiniteQuery.ts:89](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L89) +Defined in: [useLiveInfiniteQuery.ts:90](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L90) *** @@ -105,7 +105,7 @@ Defined in: [useLiveInfiniteQuery.ts:89](https://github.com/TanStack/db/blob/mai isError: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:87](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L87) +Defined in: [useLiveInfiniteQuery.ts:88](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L88) *** @@ -115,7 +115,7 @@ Defined in: [useLiveInfiniteQuery.ts:87](https://github.com/TanStack/db/blob/mai isFetchingNextPage: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:94](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L94) +Defined in: [useLiveInfiniteQuery.ts:95](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L95) *** @@ -125,7 +125,7 @@ Defined in: [useLiveInfiniteQuery.ts:94](https://github.com/TanStack/db/blob/mai isIdle: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:86](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L86) +Defined in: [useLiveInfiniteQuery.ts:87](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L87) *** @@ -135,7 +135,7 @@ Defined in: [useLiveInfiniteQuery.ts:86](https://github.com/TanStack/db/blob/mai isLoading: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:81](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L81) +Defined in: [useLiveInfiniteQuery.ts:82](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L82) *** @@ -145,7 +145,7 @@ Defined in: [useLiveInfiniteQuery.ts:81](https://github.com/TanStack/db/blob/mai isPersistedReady: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:84](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L84) +Defined in: [useLiveInfiniteQuery.ts:85](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L85) *** @@ -155,7 +155,7 @@ Defined in: [useLiveInfiniteQuery.ts:84](https://github.com/TanStack/db/blob/mai isReady: boolean; ``` -Defined in: [useLiveInfiniteQuery.ts:82](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L82) +Defined in: [useLiveInfiniteQuery.ts:83](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L83) *** @@ -165,7 +165,7 @@ Defined in: [useLiveInfiniteQuery.ts:82](https://github.com/TanStack/db/blob/mai pageParams: number[]; ``` -Defined in: [useLiveInfiniteQuery.ts:91](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L91) +Defined in: [useLiveInfiniteQuery.ts:92](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L92) *** @@ -175,7 +175,7 @@ Defined in: [useLiveInfiniteQuery.ts:91](https://github.com/TanStack/db/blob/mai pages: TResult[][]; ``` -Defined in: [useLiveInfiniteQuery.ts:90](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L90) +Defined in: [useLiveInfiniteQuery.ts:91](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L91) *** @@ -185,7 +185,7 @@ Defined in: [useLiveInfiniteQuery.ts:90](https://github.com/TanStack/db/blob/mai persistedError: unknown | undefined; ``` -Defined in: [useLiveInfiniteQuery.ts:85](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L85) +Defined in: [useLiveInfiniteQuery.ts:86](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L86) *** @@ -195,7 +195,7 @@ Defined in: [useLiveInfiniteQuery.ts:85](https://github.com/TanStack/db/blob/mai persistedStatus: LiveQueryPersistedStatus; ``` -Defined in: [useLiveInfiniteQuery.ts:83](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L83) +Defined in: [useLiveInfiniteQuery.ts:84](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L84) *** @@ -205,7 +205,7 @@ Defined in: [useLiveInfiniteQuery.ts:83](https://github.com/TanStack/db/blob/mai state: Map; ``` -Defined in: [useLiveInfiniteQuery.ts:78](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L78) +Defined in: [useLiveInfiniteQuery.ts:79](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L79) *** @@ -215,4 +215,4 @@ Defined in: [useLiveInfiniteQuery.ts:78](https://github.com/TanStack/db/blob/mai status: CollectionStatus; ``` -Defined in: [useLiveInfiniteQuery.ts:80](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L80) +Defined in: [useLiveInfiniteQuery.ts:81](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveInfiniteQuery.ts#L81) diff --git a/docs/framework/react/reference/type-aliases/UseLiveQueryConfig.md b/docs/framework/react/reference/type-aliases/UseLiveQueryConfig.md index 34499af168..fc3be54259 100644 --- a/docs/framework/react/reference/type-aliases/UseLiveQueryConfig.md +++ b/docs/framework/react/reference/type-aliases/UseLiveQueryConfig.md @@ -7,7 +7,7 @@ title: UseLiveQueryConfig type UseLiveQueryConfig = UseLiveQueryConfigOptions & Pick, "query">; ``` -Defined in: [useLiveQuery.ts:134](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L134) +Defined in: [useLiveQuery.ts:141](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L141) ## Type Parameters diff --git a/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md b/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md index 878105acea..988d049f50 100644 --- a/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md +++ b/docs/framework/react/reference/type-aliases/UseLiveQueryStatus.md @@ -7,4 +7,4 @@ title: UseLiveQueryStatus type UseLiveQueryStatus = CollectionStatus | "disabled"; ``` -Defined in: [useLiveQuery.ts:113](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L113) +Defined in: [useLiveQuery.ts:115](https://github.com/TanStack/db/blob/main/packages/react-db/src/useLiveQuery.ts#L115) diff --git a/docs/framework/solid/reference/functions/useLiveQuery.md b/docs/framework/solid/reference/functions/useLiveQuery.md index 065ee1bc2c..3e238c2198 100644 --- a/docs/framework/solid/reference/functions/useLiveQuery.md +++ b/docs/framework/solid/reference/functions/useLiveQuery.md @@ -223,7 +223,7 @@ Create a live query using configuration object #### config -`Accessor`\<`LiveQueryCollectionConfig`\<`TContext`, `RootQueryResult`\<`TContext`\>\>\> +`Accessor`\<`LiveQueryHookConfig`\<`TContext`\>\> Configuration object with query and options diff --git a/docs/framework/svelte/reference/functions/useLiveQuery.md b/docs/framework/svelte/reference/functions/useLiveQuery.md index 8fbbb5b92d..10e20a8860 100644 --- a/docs/framework/svelte/reference/functions/useLiveQuery.md +++ b/docs/framework/svelte/reference/functions/useLiveQuery.md @@ -9,7 +9,7 @@ title: useLiveQuery function useLiveQuery(queryFn, deps?): UseLiveQueryReturn<{ [K in string | number | symbol]: ResultValue[K] }, InferResultType>; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:212](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L212) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:213](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L213) Create a live query using a query function @@ -135,7 +135,7 @@ const todosQuery = useLiveQuery((q) => function useLiveQuery(queryFn, deps?): ConditionalUseLiveQueryReturn<{ [K in string | number | symbol]: ResultValue[K] }, InferConditionalResultType>; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:218](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L218) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:219](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L219) Create a live query using a query function @@ -261,7 +261,7 @@ const todosQuery = useLiveQuery((q) => function useLiveQuery(config, deps?): UseLiveQueryReturn<{ [K in string | number | symbol]: ResultValue[K] }, InferResultType>; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:266](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L266) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:267](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L267) Create a live query using configuration object @@ -334,7 +334,7 @@ const itemsQuery = useLiveQuery({ function useLiveQuery(liveQueryCollection): UseLiveQueryReturnWithCollection; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:315](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L315) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:316](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L316) Subscribe to an existing query collection (can be reactive) @@ -417,7 +417,7 @@ const queryResult = useLiveQuery(sharedQuery) function useLiveQuery(liveQueryCollection): UseLiveQueryReturnWithCollection; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:326](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L326) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:327](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L327) Create a live query using a query function diff --git a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md index 900ea4cd44..9aeff5f220 100644 --- a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md +++ b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturn.md @@ -3,7 +3,7 @@ id: UseLiveQueryReturn title: UseLiveQueryReturn --- -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:60](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L60) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:61](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L61) Return type for useLiveQuery hook @@ -26,7 +26,7 @@ collection: Collection; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:63](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L63) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:64](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L64) The underlying query collection instance @@ -38,7 +38,7 @@ The underlying query collection instance data: TData; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:62](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L62) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:63](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L63) Reactive array of query results in order, or single item when using findOne() @@ -50,7 +50,7 @@ Reactive array of query results in order, or single item when using findOne() isCleanedUp: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:72](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L72) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:73](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L73) True when query has been cleaned up @@ -62,7 +62,7 @@ True when query has been cleaned up isError: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:71](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L71) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:72](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L72) True when query encountered an error @@ -74,7 +74,7 @@ True when query encountered an error isIdle: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:70](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L70) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:71](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L71) True when query hasn't started yet @@ -86,7 +86,7 @@ True when query hasn't started yet isLoading: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:65](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L65) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:66](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L66) True while initial query data is loading @@ -98,7 +98,7 @@ True while initial query data is loading isPersistedReady: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:68](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L68) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:69](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L69) *** @@ -108,7 +108,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:68](https://github.co isReady: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:66](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L66) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:67](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L67) True when query has received first data and is ready @@ -120,7 +120,7 @@ True when query has received first data and is ready persistedError: unknown; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:69](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L69) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:70](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L70) *** @@ -130,7 +130,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:69](https://github.co persistedStatus: LiveQueryPersistedStatus; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:67](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L67) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:68](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L68) *** @@ -140,7 +140,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:67](https://github.co state: Map; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:61](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L61) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:62](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L62) Reactive Map of query results (key → item) @@ -152,6 +152,6 @@ Reactive Map of query results (key → item) status: CollectionStatus; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:64](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L64) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:65](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L65) Current query status diff --git a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md index 7d70025831..e106a0f86d 100644 --- a/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md +++ b/docs/framework/svelte/reference/interfaces/UseLiveQueryReturnWithCollection.md @@ -3,7 +3,7 @@ id: UseLiveQueryReturnWithCollection title: UseLiveQueryReturnWithCollection --- -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:88](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L88) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:89](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L89) ## Type Parameters @@ -31,7 +31,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:88](https://github.co collection: Collection; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:96](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L96) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:97](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L97) *** @@ -41,7 +41,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:96](https://github.co data: TData; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:95](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L95) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:96](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L96) *** @@ -51,7 +51,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:95](https://github.co isCleanedUp: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:105](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L105) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:106](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L106) *** @@ -61,7 +61,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:105](https://github.c isError: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:104](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L104) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:105](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L105) *** @@ -71,7 +71,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:104](https://github.c isIdle: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:103](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L103) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:104](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L104) *** @@ -81,7 +81,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:103](https://github.c isLoading: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:98](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L98) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:99](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L99) *** @@ -91,7 +91,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:98](https://github.co isPersistedReady: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:101](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L101) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:102](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L102) *** @@ -101,7 +101,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:101](https://github.c isReady: boolean; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:99](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L99) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:100](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L100) *** @@ -111,7 +111,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:99](https://github.co persistedError: unknown; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:102](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L102) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:103](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L103) *** @@ -121,7 +121,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:102](https://github.c persistedStatus: LiveQueryPersistedStatus; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:100](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L100) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:101](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L101) *** @@ -131,7 +131,7 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:100](https://github.c state: Map; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:94](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L94) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:95](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L95) *** @@ -141,4 +141,4 @@ Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:94](https://github.co status: CollectionStatus; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:97](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L97) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:98](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L98) diff --git a/docs/framework/svelte/reference/type-aliases/ConditionalUseLiveQueryReturn.md b/docs/framework/svelte/reference/type-aliases/ConditionalUseLiveQueryReturn.md index f5c199d08f..c4880575c8 100644 --- a/docs/framework/svelte/reference/type-aliases/ConditionalUseLiveQueryReturn.md +++ b/docs/framework/svelte/reference/type-aliases/ConditionalUseLiveQueryReturn.md @@ -7,7 +7,7 @@ title: ConditionalUseLiveQueryReturn type ConditionalUseLiveQueryReturn = Omit, "collection" | "status"> & object; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:80](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L80) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:81](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L81) ## Type Declaration diff --git a/docs/framework/svelte/reference/type-aliases/UseLiveQueryConfig.md b/docs/framework/svelte/reference/type-aliases/UseLiveQueryConfig.md index e8d7b55b3e..bf18547ba1 100644 --- a/docs/framework/svelte/reference/type-aliases/UseLiveQueryConfig.md +++ b/docs/framework/svelte/reference/type-aliases/UseLiveQueryConfig.md @@ -4,10 +4,10 @@ title: UseLiveQueryConfig --- ```ts -type UseLiveQueryConfig = LiveQueryCollectionConfig & object; +type UseLiveQueryConfig = LiveQueryHookConfig & object; ``` -Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:110](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L110) +Defined in: [packages/svelte-db/src/useLiveQuery.svelte.ts:111](https://github.com/TanStack/db/blob/main/packages/svelte-db/src/useLiveQuery.svelte.ts#L111) ## Type Declaration diff --git a/docs/framework/vue/reference/functions/useLiveQuery.md b/docs/framework/vue/reference/functions/useLiveQuery.md index 86d6402dac..0cfc491a4f 100644 --- a/docs/framework/vue/reference/functions/useLiveQuery.md +++ b/docs/framework/vue/reference/functions/useLiveQuery.md @@ -195,7 +195,7 @@ Create a live query using configuration object #### config -`LiveQueryCollectionConfig`\<`TContext`\> +`LiveQueryHookConfig`\<`TContext`\> Configuration object with query and options diff --git a/docs/guides/live-queries.md b/docs/guides/live-queries.md index cee2386e42..40e938e305 100644 --- a/docs/guides/live-queries.md +++ b/docs/guides/live-queries.md @@ -916,7 +916,7 @@ The result type of a join will take into account the join type, with the optiona ### Method Signature -```ts +```ts nocheck join( { [alias]: Collection | Query }, condition: (row: TRow) => Expression, // `eq(...)` or `and(eq(...), ...)` @@ -1205,7 +1205,7 @@ In this example, the `activeUsers` subquery is used twice but executed only once Build complex queries with multiple levels of nesting: ```ts -import { count } from '@tanstack/db' +import { count, createCollection, eq, liveQueryCollectionOptions } from '@tanstack/db' const topUsers = createCollection(liveQueryCollectionOptions({ query: (q) => { @@ -1229,7 +1229,7 @@ const topUsers = createCollection(liveQueryCollectionOptions({ name: user.name, postCount: postCount.count, })) - .orderBy(({ userStats }) => userStats.postCount, 'desc') + .orderBy(({ $selected }) => $selected.postCount, 'desc') .limit(10) // Use the user stats subquery in the main query @@ -1242,20 +1242,22 @@ const topUsers = createCollection(liveQueryCollectionOptions({ Includes let you nest subqueries inside `.select()` to produce hierarchical results. Instead of joins that flatten 1:N relationships into repeated rows, each parent row gets a nested collection of its related items. +Wrap a child query in `include()` to get a live child Collection, or in `toArray()` or `materialize()` to get inline values. A child query in `.select()` without one of these throws `IncludeOperatorRequiredError`. These functions bring the include code with them, so an app that never includes subqueries does not bundle it. + ```ts -import { createLiveQueryCollection, eq } from '@tanstack/db' +import { createLiveQueryCollection, eq, include } from '@tanstack/db' const projectsWithIssues = createLiveQueryCollection((q) => q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q + issues: include(q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) .select(({ i }) => ({ id: i.id, title: i.title, - })), + }))), })), ) ``` @@ -1288,7 +1290,7 @@ Child queries support additional `.where()` clauses beyond the correlation condi q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q + issues: include(q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) // correlation .where(({ i }) => eq(i.createdBy, p.createdBy)) // parent-referencing filter @@ -1296,7 +1298,7 @@ q.from({ p: projectsCollection }).select(({ p }) => ({ .select(({ i }) => ({ id: i.id, title: i.title, - })), + }))), })) ``` @@ -1310,7 +1312,7 @@ Child queries support `.orderBy()` and `.limit()`, applied per parent: q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q + issues: include(q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) .orderBy(({ i }) => i.createdAt, 'desc') @@ -1318,7 +1320,7 @@ q.from({ p: projectsCollection }).select(({ p }) => ({ .select(({ i }) => ({ id: i.id, title: i.title, - })), + }))), })) ``` @@ -1326,7 +1328,7 @@ Each project gets its own top-5 issues, not 5 issues shared across all projects. ### toArray -By default, each child result is a live `Collection`. If you want a plain array instead, wrap the child query with `toArray()`: +`include()` makes each child result a live `Collection`. If you want a plain array instead, wrap the child query with `toArray()`: ```ts import { createLiveQueryCollection, eq, toArray } from '@tanstack/db' @@ -1348,7 +1350,7 @@ const projectsWithIssues = createLiveQueryCollection((q) => ) ``` -With `toArray()`, the project row is re-emitted whenever its issues change. Without it, the child `Collection` updates independently. +With `toArray()`, the project row is re-emitted whenever its issues change. With `include()`, the child `Collection` updates independently. ### materialize @@ -1399,16 +1401,16 @@ Do not return child queries, `toArray()`, `materialize()`, or query expressions You can use aggregate functions in child queries. Aggregates are computed per parent: ```ts -import { createLiveQueryCollection, eq, count } from '@tanstack/db' +import { count, createLiveQueryCollection, eq, include } from '@tanstack/db' const projectsWithCounts = createLiveQueryCollection((q) => q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issueCount: q + issueCount: include(q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ total: count(i.id) })), + .select(({ i }) => ({ total: count(i.id) }))), })), ) ``` @@ -1424,20 +1426,20 @@ const tree = createLiveQueryCollection((q) => q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q + issues: include(q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) .select(({ i }) => ({ id: i.id, title: i.title, - comments: q + comments: include(q .from({ c: commentsCollection }) .where(({ c }) => eq(c.issueId, i.id)) .select(({ c }) => ({ id: c.id, body: c.body, - })), - })), + }))), + }))), })), ) ``` @@ -1450,7 +1452,7 @@ When using includes with React, each child `Collection` needs its own `useLiveQu ```tsx import { useLiveQuery } from '@tanstack/react-db' -import { eq } from '@tanstack/db' +import { eq, include } from '@tanstack/db' function ProjectList() { const { data: projects } = useLiveQuery({ @@ -1458,13 +1460,13 @@ function ProjectList() { q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q + issues: include(q .from({ i: issuesCollection }) .where(({ i }) => eq(i.projectId, p.id)) .select(({ i }) => ({ id: i.id, title: i.title, - })), + }))), })), }) @@ -1504,6 +1506,29 @@ Each `IssueList` component independently subscribes to its project's issues. Whe Use `groupBy` to group your data and apply aggregate functions. When you use aggregates in `select` without `groupBy`, the entire result set is treated as a single group. +Aggregate functions such as `count()` bring the grouping code with them. A `groupBy` with no aggregate wraps its keys in `groupKeys()` instead; without it, the query throws `GroupingOperatorRequiredError`: + +```ts +import { createLiveQueryCollection, groupKeys } from '@tanstack/db' + +const departments = createLiveQueryCollection((q) => + q + .from({ user: usersCollection }) + .groupBy(groupKeys(({ user }) => user.departmentId)) + .select(({ user }) => ({ departmentId: user.departmentId })), +) +``` + +`groupKeys()` built outside `.groupBy()` needs the query's context as a type argument. Without it, the callback is untyped: + +```ts +import { groupKeys } from '@tanstack/db' +import type { ContextFromSource } from '@tanstack/db' + +type UserQuery = ContextFromSource<{ user: typeof usersCollection }> +export const byDepartment = groupKeys(({ user }) => user.departmentId) +``` + ### Method Signature ```ts @@ -1997,6 +2022,11 @@ const topCustomers = createLiveQueryCollection((q) => ) ``` +An aggregate in `orderBy`, such as `.orderBy(({ order }) => sum(order.amount))`, +reads the value of the same aggregate in `select()`, as an aggregate in +`having` does. Select it as a field first. Otherwise the query throws +`AggregateFunctionNotInSelectError`. + ### Descending Order Use `desc` for descending order: @@ -2032,6 +2062,31 @@ const page2Users = createLiveQueryCollection((q) => ) ``` +## Pooled Live Queries + +Apps that mount many live queries of the same shape at once, such as one per +list row, can share their work. Pass `pooledLiveQueries` as `pool` in a hook's +query config. Queries that filter one eager source with `eq(field, literal)` +then read one shared partition instead of compiling a graph each: + +```tsx +import { eq, pooledLiveQueries } from '@tanstack/db' +import { useLiveQuery } from '@tanstack/react-db' + +function TodoRow({ listId }: { listId: string }) { + const { data } = useLiveQuery({ + query: (q) => + q.from({ todo: todosCollection }).where(({ todo }) => eq(todo.listId, listId)), + pool: pooledLiveQueries, + }) + // ... +} +``` + +Results are the same with or without pooling. Without `pool`, each query +compiles its own live-query Collection, and the app does not bundle pooling. +Suspense hooks and queries with a `DbClient` never pool. + ## Composable Queries Build complex queries by composing smaller, reusable parts. This approach makes your queries more maintainable and allows for better performance through caching. @@ -2094,7 +2149,7 @@ const activeUserPosts = createLiveQueryCollection((q) => You can use the `Query` class to create reusable query definitions. This is useful for building complex queries where you want to reuse the same query builder instance multiple times throughout your application. ```ts -import { Query, eq } from '@tanstack/db' +import { Query, createLiveQueryCollection, eq } from '@tanstack/db' // Create a reusable query builder const userQuery = new Query() diff --git a/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md b/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md index 55bd52309d..80868212b3 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/Aggregate.md @@ -3,7 +3,7 @@ id: Aggregate title: Aggregate --- -Defined in: [packages/db/src/query/ir.ts:260](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L260) +Defined in: [packages/db/src/query/ir.ts:265](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L265) ## Extends @@ -20,10 +20,13 @@ Defined in: [packages/db/src/query/ir.ts:260](https://github.com/TanStack/db/blo ### Constructor ```ts -new Aggregate(name, args): Aggregate; +new Aggregate( + name, + args, +grouping): Aggregate; ``` -Defined in: [packages/db/src/query/ir.ts:262](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L262) +Defined in: [packages/db/src/query/ir.ts:267](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L267) #### Parameters @@ -35,6 +38,16 @@ Defined in: [packages/db/src/query/ir.ts:262](https://github.com/TanStack/db/blo [`BasicExpression`](../type-aliases/BasicExpression.md)\<`any`\>[] +##### grouping + +###### processGroupBy + +(`pipeline`, `groupByClause`, `valueIdentity`, `isSourceRef`, `havingClauses?`, `selectClause?`, `fnHavingClauses?`, `aggregateCollectionId?`, `mainSource?`, `sanitizeCallbackRows`) => [`NamespacedAndKeyedStream`](../../../../type-aliases/NamespacedAndKeyedStream.md) + +###### replaceAggregatesByRefs + +(`havingExpr`, `selectClause`, `resultAlias`) => [`BasicExpression`](../type-aliases/BasicExpression.md) + #### Returns `Aggregate`\<`T`\> @@ -53,7 +66,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -73,7 +86,114 @@ BaseExpression.__returnType args: BasicExpression[]; ``` -Defined in: [packages/db/src/query/ir.ts:264](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L264) +Defined in: [packages/db/src/query/ir.ts:269](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L269) + +*** + +### grouping + +```ts +grouping: object; +``` + +Defined in: [packages/db/src/query/ir.ts:271](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L271) + +#### processGroupBy() + +```ts +processGroupBy: (pipeline, groupByClause, valueIdentity, isSourceRef, havingClauses?, selectClause?, fnHavingClauses?, aggregateCollectionId?, mainSource?, sanitizeCallbackRows) => NamespacedAndKeyedStream; +``` + +Processes the GROUP BY clause with optional HAVING and SELECT +Works with the new $selected structure from early SELECT processing + +##### Parameters + +###### pipeline + +[`NamespacedAndKeyedStream`](../../../../type-aliases/NamespacedAndKeyedStream.md) + +###### groupByClause + +[`GroupBy`](../type-aliases/GroupBy.md) + +###### valueIdentity + +`ValueIdentity` + +###### isSourceRef + +(`ref`) => `boolean` + +Whether a ref reads a field of the query's own rows. + +###### havingClauses? + +[`Where`](../type-aliases/Where.md)[] + +###### selectClause? + +[`Select`](../type-aliases/Select.md) + +###### fnHavingClauses? + +(`row`) => `any`[] + +###### aggregateCollectionId? + +`string` + +###### mainSource? + +`string` + +###### sanitizeCallbackRows? + +`boolean` = `false` + +##### Returns + +[`NamespacedAndKeyedStream`](../../../../type-aliases/NamespacedAndKeyedStream.md) + +#### replaceAggregatesByRefs() + +```ts +replaceAggregatesByRefs: (havingExpr, selectClause, resultAlias) => BasicExpression; +``` + +Transforms expressions to replace aggregate functions with references to computed values. + +For aggregate expressions, finds matching aggregates in the SELECT clause and replaces them +with PropRef([resultAlias, alias]) to reference the computed aggregate value. + +Ref expressions (table columns and $selected fields) and value expressions are passed through unchanged. +Function expressions are recursively transformed. + +##### Parameters + +###### havingExpr + +The expression to transform (can be aggregate, ref, func, or val) + +`Aggregate`\<`any`\> | [`BasicExpression`](../type-aliases/BasicExpression.md) + +###### selectClause + +[`Select`](../type-aliases/Select.md) + +The SELECT clause containing aliases and aggregate definitions + +###### resultAlias + +`string` = `...` + +The namespace alias for SELECT results (default: '$selected') + +##### Returns + +[`BasicExpression`](../type-aliases/BasicExpression.md) + +A transformed BasicExpression that references computed values instead of raw expressions *** @@ -83,7 +203,7 @@ Defined in: [packages/db/src/query/ir.ts:264](https://github.com/TanStack/db/blo name: string; ``` -Defined in: [packages/db/src/query/ir.ts:263](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L263) +Defined in: [packages/db/src/query/ir.ts:268](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L268) *** @@ -93,7 +213,7 @@ Defined in: [packages/db/src/query/ir.ts:263](https://github.com/TanStack/db/blo type: "agg"; ``` -Defined in: [packages/db/src/query/ir.ts:261](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L261) +Defined in: [packages/db/src/query/ir.ts:266](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L266) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md b/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md index 3d41bb5c0d..e43078d92a 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/CollectionRef.md @@ -3,7 +3,7 @@ id: CollectionRef title: CollectionRef --- -Defined in: [packages/db/src/query/ir.ts:86](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L86) +Defined in: [packages/db/src/query/ir.ts:91](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L91) ## Extends @@ -20,7 +20,7 @@ new CollectionRef( bindingId?): CollectionRef; ``` -Defined in: [packages/db/src/query/ir.ts:93](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L93) +Defined in: [packages/db/src/query/ir.ts:98](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L98) #### Parameters @@ -54,7 +54,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -74,7 +74,7 @@ BaseExpression.__returnType alias: string; ``` -Defined in: [packages/db/src/query/ir.ts:91](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L91) +Defined in: [packages/db/src/query/ir.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L96) *** @@ -84,7 +84,7 @@ Defined in: [packages/db/src/query/ir.ts:91](https://github.com/TanStack/db/blob collection: CollectionImpl; ``` -Defined in: [packages/db/src/query/ir.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L90) +Defined in: [packages/db/src/query/ir.ts:95](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L95) *** @@ -94,7 +94,7 @@ Defined in: [packages/db/src/query/ir.ts:90](https://github.com/TanStack/db/blob type: "collectionRef"; ``` -Defined in: [packages/db/src/query/ir.ts:87](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L87) +Defined in: [packages/db/src/query/ir.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L92) #### Overrides @@ -112,7 +112,7 @@ BaseExpression.type get bindingId(): string; ``` -Defined in: [packages/db/src/query/ir.ts:109](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L109) +Defined in: [packages/db/src/query/ir.ts:114](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L114) ##### Returns @@ -128,7 +128,7 @@ Defined in: [packages/db/src/query/ir.ts:109](https://github.com/TanStack/db/blo get sourceId(): string; ``` -Defined in: [packages/db/src/query/ir.ts:105](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L105) +Defined in: [packages/db/src/query/ir.ts:110](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L110) Opaque runtime identity; aliases are lexical names only. diff --git a/docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md b/docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md index 939dbc4b16..7563d193e6 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/ConditionalSelect.md @@ -3,7 +3,7 @@ id: ConditionalSelect title: ConditionalSelect --- -Defined in: [packages/db/src/query/ir.ts:304](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L304) +Defined in: [packages/db/src/query/ir.ts:313](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L313) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/query/ir.ts:304](https://github.com/TanStack/db/blo new ConditionalSelect(branches, defaultValue?): ConditionalSelect; ``` -Defined in: [packages/db/src/query/ir.ts:306](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L306) +Defined in: [packages/db/src/query/ir.ts:315](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L315) #### Parameters @@ -47,7 +47,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -67,7 +67,7 @@ BaseExpression.__returnType branches: ConditionalSelectBranch[]; ``` -Defined in: [packages/db/src/query/ir.ts:307](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L307) +Defined in: [packages/db/src/query/ir.ts:316](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L316) *** @@ -77,7 +77,7 @@ Defined in: [packages/db/src/query/ir.ts:307](https://github.com/TanStack/db/blo optional defaultValue: SelectValueExpression; ``` -Defined in: [packages/db/src/query/ir.ts:308](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L308) +Defined in: [packages/db/src/query/ir.ts:317](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L317) *** @@ -87,7 +87,7 @@ Defined in: [packages/db/src/query/ir.ts:308](https://github.com/TanStack/db/blo type: "conditionalSelect"; ``` -Defined in: [packages/db/src/query/ir.ts:305](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L305) +Defined in: [packages/db/src/query/ir.ts:314](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L314) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/DescriptorRef.md b/docs/reference/@tanstack/namespaces/IR/classes/DescriptorRef.md index ff2f45ab94..8ea97d4fbc 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/DescriptorRef.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/DescriptorRef.md @@ -3,7 +3,7 @@ id: DescriptorRef title: DescriptorRef --- -Defined in: [packages/db/src/query/ir.ts:114](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L114) +Defined in: [packages/db/src/query/ir.ts:119](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L119) ## Extends @@ -20,7 +20,7 @@ new DescriptorRef( bindingId?): DescriptorRef; ``` -Defined in: [packages/db/src/query/ir.ts:118](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L118) +Defined in: [packages/db/src/query/ir.ts:123](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L123) #### Parameters @@ -54,7 +54,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -74,7 +74,7 @@ BaseExpression.__returnType alias: string; ``` -Defined in: [packages/db/src/query/ir.ts:120](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L120) +Defined in: [packages/db/src/query/ir.ts:125](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L125) *** @@ -84,7 +84,7 @@ Defined in: [packages/db/src/query/ir.ts:120](https://github.com/TanStack/db/blo descriptor: CollectionOptionsIdentity; ``` -Defined in: [packages/db/src/query/ir.ts:119](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L119) +Defined in: [packages/db/src/query/ir.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L124) *** @@ -94,7 +94,7 @@ Defined in: [packages/db/src/query/ir.ts:119](https://github.com/TanStack/db/blo type: "descriptorRef"; ``` -Defined in: [packages/db/src/query/ir.ts:115](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L115) +Defined in: [packages/db/src/query/ir.ts:120](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L120) #### Overrides @@ -112,7 +112,7 @@ BaseExpression.type get bindingId(): string; ``` -Defined in: [packages/db/src/query/ir.ts:132](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L132) +Defined in: [packages/db/src/query/ir.ts:137](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L137) ##### Returns @@ -128,7 +128,7 @@ Defined in: [packages/db/src/query/ir.ts:132](https://github.com/TanStack/db/blo get sourceId(): string; ``` -Defined in: [packages/db/src/query/ir.ts:128](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L128) +Defined in: [packages/db/src/query/ir.ts:133](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L133) Opaque runtime identity for this unbound source placement. diff --git a/docs/reference/@tanstack/namespaces/IR/classes/Func.md b/docs/reference/@tanstack/namespaces/IR/classes/Func.md index c5c812ef86..c906b727ba 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/Func.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/Func.md @@ -3,7 +3,7 @@ id: Func title: Func --- -Defined in: [packages/db/src/query/ir.ts:245](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L245) +Defined in: [packages/db/src/query/ir.ts:250](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L250) ## Extends @@ -23,7 +23,7 @@ Defined in: [packages/db/src/query/ir.ts:245](https://github.com/TanStack/db/blo new Func(name, args): Func; ``` -Defined in: [packages/db/src/query/ir.ts:247](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L247) +Defined in: [packages/db/src/query/ir.ts:252](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L252) #### Parameters @@ -53,7 +53,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -73,7 +73,7 @@ BaseExpression.__returnType args: BasicExpression[]; ``` -Defined in: [packages/db/src/query/ir.ts:249](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L249) +Defined in: [packages/db/src/query/ir.ts:254](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L254) *** @@ -83,7 +83,7 @@ Defined in: [packages/db/src/query/ir.ts:249](https://github.com/TanStack/db/blo name: string; ``` -Defined in: [packages/db/src/query/ir.ts:248](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L248) +Defined in: [packages/db/src/query/ir.ts:253](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L253) *** @@ -93,7 +93,7 @@ Defined in: [packages/db/src/query/ir.ts:248](https://github.com/TanStack/db/blo type: "func"; ``` -Defined in: [packages/db/src/query/ir.ts:246](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L246) +Defined in: [packages/db/src/query/ir.ts:251](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L251) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md b/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md index 5f519316c3..12e597d9f3 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/IncludesSubquery.md @@ -3,7 +3,7 @@ id: IncludesSubquery title: IncludesSubquery --- -Defined in: [packages/db/src/query/ir.ts:280](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L280) +Defined in: [packages/db/src/query/ir.ts:287](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L287) ## Extends @@ -19,13 +19,14 @@ new IncludesSubquery( correlationField, childCorrelationField, fieldName, + runtime, parentFilters?, parentProjection?, materialization?, scalarField?): IncludesSubquery; ``` -Defined in: [packages/db/src/query/ir.ts:282](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L282) +Defined in: [packages/db/src/query/ir.ts:289](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L289) #### Parameters @@ -45,6 +46,16 @@ Defined in: [packages/db/src/query/ir.ts:282](https://github.com/TanStack/db/blo `string` +##### runtime + +###### createBucketFacades + +(...`args`) => `BucketFacadeAdapter` = `...` + +###### materializeCompilation + +(`compilation`, `getRootKey?`) => `MaterializedRootCompilation` + ##### parentFilters? [`Where`](../type-aliases/Where.md)[] @@ -79,7 +90,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -99,7 +110,7 @@ BaseExpression.__returnType childCorrelationField: PropRef; ``` -Defined in: [packages/db/src/query/ir.ts:285](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L285) +Defined in: [packages/db/src/query/ir.ts:292](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L292) *** @@ -109,7 +120,7 @@ Defined in: [packages/db/src/query/ir.ts:285](https://github.com/TanStack/db/blo correlationField: PropRef; ``` -Defined in: [packages/db/src/query/ir.ts:284](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L284) +Defined in: [packages/db/src/query/ir.ts:291](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L291) *** @@ -119,7 +130,7 @@ Defined in: [packages/db/src/query/ir.ts:284](https://github.com/TanStack/db/blo fieldName: string; ``` -Defined in: [packages/db/src/query/ir.ts:286](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L286) +Defined in: [packages/db/src/query/ir.ts:293](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L293) *** @@ -129,7 +140,7 @@ Defined in: [packages/db/src/query/ir.ts:286](https://github.com/TanStack/db/blo materialization: IncludesMaterialization; ``` -Defined in: [packages/db/src/query/ir.ts:289](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L289) +Defined in: [packages/db/src/query/ir.ts:298](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L298) *** @@ -139,7 +150,7 @@ Defined in: [packages/db/src/query/ir.ts:289](https://github.com/TanStack/db/blo optional parentFilters: Where[]; ``` -Defined in: [packages/db/src/query/ir.ts:287](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L287) +Defined in: [packages/db/src/query/ir.ts:296](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L296) *** @@ -149,7 +160,7 @@ Defined in: [packages/db/src/query/ir.ts:287](https://github.com/TanStack/db/blo optional parentProjection: PropRef[]; ``` -Defined in: [packages/db/src/query/ir.ts:288](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L288) +Defined in: [packages/db/src/query/ir.ts:297](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L297) *** @@ -159,7 +170,57 @@ Defined in: [packages/db/src/query/ir.ts:288](https://github.com/TanStack/db/blo query: QueryIR; ``` -Defined in: [packages/db/src/query/ir.ts:283](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L283) +Defined in: [packages/db/src/query/ir.ts:290](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L290) + +*** + +### runtime + +```ts +runtime: object; +``` + +Defined in: [packages/db/src/query/ir.ts:295](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L295) + +#### createBucketFacades() + +```ts +createBucketFacades: (...args) => BucketFacadeAdapter; +``` + +##### Parameters + +###### args + +...\[`string`, `BucketFacadeCompilation`[], (`count`) => `void`\] + +##### Returns + +`BucketFacadeAdapter` + +#### materializeCompilation() + +```ts +materializeCompilation: (compilation, getRootKey?) => MaterializedRootCompilation; +``` + +Compiles inline includes into the same D2 graph as their parent relation. +Collection-valued includes become inert bucket references. The public facade +adapter resolves those references after the graph reaches quiescence. + +##### Parameters + +###### compilation + +`CompilationResult` + +###### getRootKey? + +(`row`) => `unknown` + +##### Returns + +`MaterializedRootCompilation` *** @@ -169,7 +230,7 @@ Defined in: [packages/db/src/query/ir.ts:283](https://github.com/TanStack/db/blo optional scalarField: string; ``` -Defined in: [packages/db/src/query/ir.ts:290](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L290) +Defined in: [packages/db/src/query/ir.ts:299](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L299) *** @@ -179,7 +240,7 @@ Defined in: [packages/db/src/query/ir.ts:290](https://github.com/TanStack/db/blo type: "includesSubquery"; ``` -Defined in: [packages/db/src/query/ir.ts:281](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L281) +Defined in: [packages/db/src/query/ir.ts:288](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L288) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md b/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md index b4f843dc13..9822bcd13e 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/PropRef.md @@ -3,7 +3,7 @@ id: PropRef title: PropRef --- -Defined in: [packages/db/src/query/ir.ts:200](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L200) +Defined in: [packages/db/src/query/ir.ts:205](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L205) ## Extends @@ -26,7 +26,7 @@ new PropRef( bindingId?): PropRef; ``` -Defined in: [packages/db/src/query/ir.ts:204](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L204) +Defined in: [packages/db/src/query/ir.ts:209](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L209) #### Parameters @@ -60,7 +60,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -80,7 +80,7 @@ BaseExpression.__returnType readonly optional bindingId: string; ``` -Defined in: [packages/db/src/query/ir.ts:203](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L203) +Defined in: [packages/db/src/query/ir.ts:208](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L208) *** @@ -90,7 +90,7 @@ Defined in: [packages/db/src/query/ir.ts:203](https://github.com/TanStack/db/blo path: string[]; ``` -Defined in: [packages/db/src/query/ir.ts:205](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L205) +Defined in: [packages/db/src/query/ir.ts:210](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L210) *** @@ -100,7 +100,7 @@ Defined in: [packages/db/src/query/ir.ts:205](https://github.com/TanStack/db/blo readonly optional sourceAlias: string; ``` -Defined in: [packages/db/src/query/ir.ts:202](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L202) +Defined in: [packages/db/src/query/ir.ts:207](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L207) *** @@ -110,7 +110,7 @@ Defined in: [packages/db/src/query/ir.ts:202](https://github.com/TanStack/db/blo type: "ref"; ``` -Defined in: [packages/db/src/query/ir.ts:201](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L201) +Defined in: [packages/db/src/query/ir.ts:206](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L206) #### Overrides diff --git a/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md b/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md index 9050a6ba10..f057878bbc 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/QueryRef.md @@ -3,7 +3,7 @@ id: QueryRef title: QueryRef --- -Defined in: [packages/db/src/query/ir.ts:155](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L155) +Defined in: [packages/db/src/query/ir.ts:160](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L160) ## Extends @@ -20,7 +20,7 @@ new QueryRef( bindingId?): QueryRef; ``` -Defined in: [packages/db/src/query/ir.ts:158](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L158) +Defined in: [packages/db/src/query/ir.ts:163](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L163) #### Parameters @@ -54,7 +54,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -74,7 +74,7 @@ BaseExpression.__returnType alias: string; ``` -Defined in: [packages/db/src/query/ir.ts:160](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L160) +Defined in: [packages/db/src/query/ir.ts:165](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L165) *** @@ -84,7 +84,7 @@ Defined in: [packages/db/src/query/ir.ts:160](https://github.com/TanStack/db/blo query: QueryIR; ``` -Defined in: [packages/db/src/query/ir.ts:159](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L159) +Defined in: [packages/db/src/query/ir.ts:164](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L164) *** @@ -94,7 +94,7 @@ Defined in: [packages/db/src/query/ir.ts:159](https://github.com/TanStack/db/blo type: "queryRef"; ``` -Defined in: [packages/db/src/query/ir.ts:156](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L156) +Defined in: [packages/db/src/query/ir.ts:161](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L161) #### Overrides @@ -112,7 +112,7 @@ BaseExpression.type get bindingId(): string; ``` -Defined in: [packages/db/src/query/ir.ts:167](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L167) +Defined in: [packages/db/src/query/ir.ts:172](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L172) ##### Returns diff --git a/docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md b/docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md index 88c347b38d..ad1fcecb7e 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/UnionAll.md @@ -3,7 +3,7 @@ id: UnionAll title: UnionAll --- -Defined in: [packages/db/src/query/ir.ts:183](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L183) +Defined in: [packages/db/src/query/ir.ts:188](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L188) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/query/ir.ts:183](https://github.com/TanStack/db/blo new UnionAll(queries): UnionAll; ``` -Defined in: [packages/db/src/query/ir.ts:191](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L191) +Defined in: [packages/db/src/query/ir.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L196) Result-level UNION ALL. Downstream query clauses see the union result row shape, not the branch source aliases. Optimizers may push safe operations @@ -48,7 +48,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -68,7 +68,7 @@ BaseExpression.__returnType queries: QueryIR[]; ``` -Defined in: [packages/db/src/query/ir.ts:191](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L191) +Defined in: [packages/db/src/query/ir.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L196) *** @@ -78,7 +78,7 @@ Defined in: [packages/db/src/query/ir.ts:191](https://github.com/TanStack/db/blo type: "unionAll"; ``` -Defined in: [packages/db/src/query/ir.ts:184](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L184) +Defined in: [packages/db/src/query/ir.ts:189](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L189) #### Overrides @@ -96,7 +96,7 @@ BaseExpression.type get alias(): string; ``` -Defined in: [packages/db/src/query/ir.ts:195](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L195) +Defined in: [packages/db/src/query/ir.ts:200](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L200) ##### Returns diff --git a/docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md b/docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md index 53204f94e7..d32971a8ac 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/UnionFrom.md @@ -3,7 +3,7 @@ id: UnionFrom title: UnionFrom --- -Defined in: [packages/db/src/query/ir.ts:172](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L172) +Defined in: [packages/db/src/query/ir.ts:177](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L177) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/query/ir.ts:172](https://github.com/TanStack/db/blo new UnionFrom(sources): UnionFrom; ``` -Defined in: [packages/db/src/query/ir.ts:174](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L174) +Defined in: [packages/db/src/query/ir.ts:179](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L179) #### Parameters @@ -45,7 +45,7 @@ BaseExpression.constructor readonly __returnType: any; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -67,7 +67,7 @@ sources: ( | QueryRef)[]; ``` -Defined in: [packages/db/src/query/ir.ts:174](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L174) +Defined in: [packages/db/src/query/ir.ts:179](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L179) *** @@ -77,7 +77,7 @@ Defined in: [packages/db/src/query/ir.ts:174](https://github.com/TanStack/db/blo type: "unionFrom"; ``` -Defined in: [packages/db/src/query/ir.ts:173](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L173) +Defined in: [packages/db/src/query/ir.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L178) #### Overrides @@ -95,7 +95,7 @@ BaseExpression.type get alias(): string; ``` -Defined in: [packages/db/src/query/ir.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L178) +Defined in: [packages/db/src/query/ir.ts:183](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L183) ##### Returns diff --git a/docs/reference/@tanstack/namespaces/IR/classes/Value.md b/docs/reference/@tanstack/namespaces/IR/classes/Value.md index 5f9eee3388..80ebf7e049 100644 --- a/docs/reference/@tanstack/namespaces/IR/classes/Value.md +++ b/docs/reference/@tanstack/namespaces/IR/classes/Value.md @@ -3,7 +3,7 @@ id: Value title: Value --- -Defined in: [packages/db/src/query/ir.ts:236](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L236) +Defined in: [packages/db/src/query/ir.ts:241](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L241) ## Extends @@ -23,7 +23,7 @@ Defined in: [packages/db/src/query/ir.ts:236](https://github.com/TanStack/db/blo new Value(value): Value; ``` -Defined in: [packages/db/src/query/ir.ts:238](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L238) +Defined in: [packages/db/src/query/ir.ts:243](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L243) #### Parameters @@ -49,7 +49,7 @@ BaseExpression.constructor readonly __returnType: T; ``` -Defined in: [packages/db/src/query/ir.ts:83](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L83) +Defined in: [packages/db/src/query/ir.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L88) **`Internal`** @@ -69,7 +69,7 @@ BaseExpression.__returnType type: "val"; ``` -Defined in: [packages/db/src/query/ir.ts:237](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L237) +Defined in: [packages/db/src/query/ir.ts:242](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L242) #### Overrides @@ -85,4 +85,4 @@ BaseExpression.type value: T; ``` -Defined in: [packages/db/src/query/ir.ts:239](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L239) +Defined in: [packages/db/src/query/ir.ts:244](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L244) diff --git a/docs/reference/@tanstack/namespaces/IR/functions/collectCollectionSources.md b/docs/reference/@tanstack/namespaces/IR/functions/collectCollectionSources.md index 9ce541176e..312394db9a 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/collectCollectionSources.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/collectCollectionSources.md @@ -7,7 +7,7 @@ title: collectCollectionSources function collectCollectionSources(query): CollectionRef[]; ``` -Defined in: [packages/db/src/query/ir.ts:384](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L384) +Defined in: [packages/db/src/query/ir.ts:393](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L393) Returns concrete Collection sources after requiring client binding. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/collectPropRefs.md b/docs/reference/@tanstack/namespaces/IR/functions/collectPropRefs.md index 77985cc3c3..0223bd233b 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/collectPropRefs.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/collectPropRefs.md @@ -7,7 +7,7 @@ title: collectPropRefs function collectPropRefs(expression): PropRef[]; ``` -Defined in: [packages/db/src/query/ir.ts:271](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L271) +Defined in: [packages/db/src/query/ir.ts:278](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L278) Collect refs from an IR expression, including aggregate arguments. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/collectSourceRefs.md b/docs/reference/@tanstack/namespaces/IR/functions/collectSourceRefs.md index 586bd48327..b834669192 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/collectSourceRefs.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/collectSourceRefs.md @@ -7,7 +7,7 @@ title: collectSourceRefs function collectSourceRefs(query): CollectionSourceRef[]; ``` -Defined in: [packages/db/src/query/ir.ts:335](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L335) +Defined in: [packages/db/src/query/ir.ts:344](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L344) Returns each lexical Collection or descriptor source in a query tree once. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md b/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md index 048dc97d13..9742297337 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/createResidualWhere.md @@ -7,7 +7,7 @@ title: createResidualWhere function createResidualWhere(expression): Where; ``` -Defined in: [packages/db/src/query/ir.ts:429](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L429) +Defined in: [packages/db/src/query/ir.ts:438](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L438) Create a residual Where clause from an expression diff --git a/docs/reference/@tanstack/namespaces/IR/functions/followRef.md b/docs/reference/@tanstack/namespaces/IR/functions/followRef.md index 9dacda480c..6739108ab0 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/followRef.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/followRef.md @@ -17,7 +17,7 @@ function followRef( }; ``` -Defined in: [packages/db/src/query/ir.ts:477](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L477) +Defined in: [packages/db/src/query/ir.ts:486](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L486) Follows the given reference in a query until its finds the root field the reference points to. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/getFromSources.md b/docs/reference/@tanstack/namespaces/IR/functions/getFromSources.md index c1c20db12c..17460ca42e 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/getFromSources.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/getFromSources.md @@ -9,7 +9,7 @@ function getFromSources(from): ( | QueryRef)[]; ``` -Defined in: [packages/db/src/query/ir.ts:436](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L436) +Defined in: [packages/db/src/query/ir.ts:445](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L445) Sources declared by a FROM clause. UnionAll branches own their sources. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md b/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md index ad73a98147..fd17036ced 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/getHavingExpression.md @@ -9,7 +9,7 @@ function getHavingExpression(having): | Aggregate; ``` -Defined in: [packages/db/src/query/ir.ts:407](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L407) +Defined in: [packages/db/src/query/ir.ts:416](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L416) Extract the expression from a HAVING clause HAVING clauses can contain aggregates, unlike regular WHERE clauses diff --git a/docs/reference/@tanstack/namespaces/IR/functions/getPropRefPropertyPath.md b/docs/reference/@tanstack/namespaces/IR/functions/getPropRefPropertyPath.md index 0a0a321607..a6eeadbf3a 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/getPropRefPropertyPath.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/getPropRefPropertyPath.md @@ -7,7 +7,7 @@ title: getPropRefPropertyPath function getPropRefPropertyPath(ref): string[]; ``` -Defined in: [packages/db/src/query/ir.ts:232](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L232) +Defined in: [packages/db/src/query/ir.ts:237](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L237) Returns the property path after removing only explicit source qualification. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/getPropRefSourceAlias.md b/docs/reference/@tanstack/namespaces/IR/functions/getPropRefSourceAlias.md index 5f0cffd844..b60421c265 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/getPropRefSourceAlias.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/getPropRefSourceAlias.md @@ -7,7 +7,7 @@ title: getPropRefSourceAlias function getPropRefSourceAlias(ref): string | undefined; ``` -Defined in: [packages/db/src/query/ir.ts:225](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L225) +Defined in: [packages/db/src/query/ir.ts:230](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L230) Returns an explicitly declared source alias without inferring from the path. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md b/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md index 085fe9e367..83fe4b1efc 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/getWhereExpression.md @@ -7,7 +7,7 @@ title: getWhereExpression function getWhereExpression(where): BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:397](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L397) +Defined in: [packages/db/src/query/ir.ts:406](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L406) Extract the expression from a Where clause diff --git a/docs/reference/@tanstack/namespaces/IR/functions/isBasicOrAggregateExpression.md b/docs/reference/@tanstack/namespaces/IR/functions/isBasicOrAggregateExpression.md index c2b0987c8b..7911959e79 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/isBasicOrAggregateExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/isBasicOrAggregateExpression.md @@ -7,7 +7,7 @@ title: isBasicOrAggregateExpression function isBasicOrAggregateExpression(value): value is BasicExpression | Aggregate; ``` -Defined in: [packages/db/src/query/ir.ts:315](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L315) +Defined in: [packages/db/src/query/ir.ts:324](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L324) Distinguish compiler expressions from user objects with IR-like fields. diff --git a/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md b/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md index ade97402d8..d92d563c9d 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/isExpressionLike.md @@ -7,7 +7,7 @@ title: isExpressionLike function isExpressionLike(value): boolean; ``` -Defined in: [packages/db/src/query/ir.ts:326](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L326) +Defined in: [packages/db/src/query/ir.ts:335](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L335) ## Parameters diff --git a/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md b/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md index c350afc596..740727e4b6 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/isResidualWhere.md @@ -7,7 +7,7 @@ title: isResidualWhere function isResidualWhere(where): boolean; ``` -Defined in: [packages/db/src/query/ir.ts:418](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L418) +Defined in: [packages/db/src/query/ir.ts:427](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L427) Check if a Where clause is marked as residual diff --git a/docs/reference/@tanstack/namespaces/IR/functions/requireCollectionSource.md b/docs/reference/@tanstack/namespaces/IR/functions/requireCollectionSource.md index d0fdee7500..188fb1bd3e 100644 --- a/docs/reference/@tanstack/namespaces/IR/functions/requireCollectionSource.md +++ b/docs/reference/@tanstack/namespaces/IR/functions/requireCollectionSource.md @@ -9,7 +9,7 @@ title: requireCollectionSource function requireCollectionSource(source): never; ``` -Defined in: [packages/db/src/query/ir.ts:138](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L138) +Defined in: [packages/db/src/query/ir.ts:143](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L143) A descriptor must bind to a DbClient before a Collection is required. @@ -29,7 +29,7 @@ A descriptor must bind to a DbClient before a Collection is required. function requireCollectionSource(source): CollectionImpl; ``` -Defined in: [packages/db/src/query/ir.ts:139](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L139) +Defined in: [packages/db/src/query/ir.ts:144](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L144) A descriptor must bind to a DbClient before a Collection is required. diff --git a/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md b/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md index 133eedad18..72387b2769 100644 --- a/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md +++ b/docs/reference/@tanstack/namespaces/IR/interfaces/JoinClause.md @@ -3,7 +3,7 @@ id: JoinClause title: JoinClause --- -Defined in: [packages/db/src/query/ir.ts:48](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L48) +Defined in: [packages/db/src/query/ir.ts:53](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L53) ## Properties @@ -15,7 +15,7 @@ from: | QueryRef; ``` -Defined in: [packages/db/src/query/ir.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L49) +Defined in: [packages/db/src/query/ir.ts:54](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L54) *** @@ -25,7 +25,7 @@ Defined in: [packages/db/src/query/ir.ts:49](https://github.com/TanStack/db/blob on: BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L51) +Defined in: [packages/db/src/query/ir.ts:56](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L56) *** @@ -35,4 +35,4 @@ Defined in: [packages/db/src/query/ir.ts:51](https://github.com/TanStack/db/blob type: "inner" | "left" | "right" | "full" | "outer" | "cross"; ``` -Defined in: [packages/db/src/query/ir.ts:50](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L50) +Defined in: [packages/db/src/query/ir.ts:55](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L55) diff --git a/docs/reference/@tanstack/namespaces/IR/interfaces/QueryIR.md b/docs/reference/@tanstack/namespaces/IR/interfaces/QueryIR.md index 9508a24e38..f07f8f7bb3 100644 --- a/docs/reference/@tanstack/namespaces/IR/interfaces/QueryIR.md +++ b/docs/reference/@tanstack/namespaces/IR/interfaces/QueryIR.md @@ -3,7 +3,7 @@ id: QueryIR title: QueryIR --- -Defined in: [packages/db/src/query/ir.ts:13](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L13) +Defined in: [packages/db/src/query/ir.ts:16](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L16) ## Properties @@ -13,7 +13,7 @@ Defined in: [packages/db/src/query/ir.ts:13](https://github.com/TanStack/db/blob optional distinct: true; ``` -Defined in: [packages/db/src/query/ir.ts:23](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L23) +Defined in: [packages/db/src/query/ir.ts:28](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L28) *** @@ -23,7 +23,7 @@ Defined in: [packages/db/src/query/ir.ts:23](https://github.com/TanStack/db/blob optional fnHaving: (row) => any[]; ``` -Defined in: [packages/db/src/query/ir.ts:29](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L29) +Defined in: [packages/db/src/query/ir.ts:34](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L34) #### Parameters @@ -43,7 +43,7 @@ Defined in: [packages/db/src/query/ir.ts:29](https://github.com/TanStack/db/blob optional fnSelect: (row) => any; ``` -Defined in: [packages/db/src/query/ir.ts:27](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L27) +Defined in: [packages/db/src/query/ir.ts:32](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L32) #### Parameters @@ -63,7 +63,7 @@ Defined in: [packages/db/src/query/ir.ts:27](https://github.com/TanStack/db/blob optional fnWhere: (row) => any[]; ``` -Defined in: [packages/db/src/query/ir.ts:28](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L28) +Defined in: [packages/db/src/query/ir.ts:33](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L33) #### Parameters @@ -83,7 +83,7 @@ Defined in: [packages/db/src/query/ir.ts:28](https://github.com/TanStack/db/blob from: From; ``` -Defined in: [packages/db/src/query/ir.ts:14](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L14) +Defined in: [packages/db/src/query/ir.ts:17](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L17) *** @@ -93,7 +93,114 @@ Defined in: [packages/db/src/query/ir.ts:14](https://github.com/TanStack/db/blob optional groupBy: GroupBy; ``` -Defined in: [packages/db/src/query/ir.ts:18](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L18) +Defined in: [packages/db/src/query/ir.ts:21](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L21) + +*** + +### grouping? + +```ts +optional grouping: object; +``` + +Defined in: [packages/db/src/query/ir.ts:23](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L23) + +#### processGroupBy() + +```ts +processGroupBy: (pipeline, groupByClause, valueIdentity, isSourceRef, havingClauses?, selectClause?, fnHavingClauses?, aggregateCollectionId?, mainSource?, sanitizeCallbackRows) => NamespacedAndKeyedStream; +``` + +Processes the GROUP BY clause with optional HAVING and SELECT +Works with the new $selected structure from early SELECT processing + +##### Parameters + +###### pipeline + +[`NamespacedAndKeyedStream`](../../../../type-aliases/NamespacedAndKeyedStream.md) + +###### groupByClause + +[`GroupBy`](../type-aliases/GroupBy.md) + +###### valueIdentity + +`ValueIdentity` + +###### isSourceRef + +(`ref`) => `boolean` + +Whether a ref reads a field of the query's own rows. + +###### havingClauses? + +[`Where`](../type-aliases/Where.md)[] + +###### selectClause? + +[`Select`](../type-aliases/Select.md) + +###### fnHavingClauses? + +(`row`) => `any`[] + +###### aggregateCollectionId? + +`string` + +###### mainSource? + +`string` + +###### sanitizeCallbackRows? + +`boolean` = `false` + +##### Returns + +[`NamespacedAndKeyedStream`](../../../../type-aliases/NamespacedAndKeyedStream.md) + +#### replaceAggregatesByRefs() + +```ts +replaceAggregatesByRefs: (havingExpr, selectClause, resultAlias) => BasicExpression; +``` + +Transforms expressions to replace aggregate functions with references to computed values. + +For aggregate expressions, finds matching aggregates in the SELECT clause and replaces them +with PropRef([resultAlias, alias]) to reference the computed aggregate value. + +Ref expressions (table columns and $selected fields) and value expressions are passed through unchanged. +Function expressions are recursively transformed. + +##### Parameters + +###### havingExpr + +The expression to transform (can be aggregate, ref, func, or val) + +[`Aggregate`](../classes/Aggregate.md)\<`any`\> | [`BasicExpression`](../type-aliases/BasicExpression.md) + +###### selectClause + +[`Select`](../type-aliases/Select.md) + +The SELECT clause containing aliases and aggregate definitions + +###### resultAlias + +`string` = `...` + +The namespace alias for SELECT results (default: '$selected') + +##### Returns + +[`BasicExpression`](../type-aliases/BasicExpression.md) + +A transformed BasicExpression that references computed values instead of raw expressions *** @@ -103,7 +210,7 @@ Defined in: [packages/db/src/query/ir.ts:18](https://github.com/TanStack/db/blob optional having: Where[]; ``` -Defined in: [packages/db/src/query/ir.ts:19](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L19) +Defined in: [packages/db/src/query/ir.ts:24](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L24) *** @@ -113,7 +220,7 @@ Defined in: [packages/db/src/query/ir.ts:19](https://github.com/TanStack/db/blob optional join: Join; ``` -Defined in: [packages/db/src/query/ir.ts:16](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L16) +Defined in: [packages/db/src/query/ir.ts:19](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L19) *** @@ -123,7 +230,7 @@ Defined in: [packages/db/src/query/ir.ts:16](https://github.com/TanStack/db/blob optional limit: number; ``` -Defined in: [packages/db/src/query/ir.ts:21](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L21) +Defined in: [packages/db/src/query/ir.ts:26](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L26) *** @@ -133,7 +240,7 @@ Defined in: [packages/db/src/query/ir.ts:21](https://github.com/TanStack/db/blob optional offset: number; ``` -Defined in: [packages/db/src/query/ir.ts:22](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L22) +Defined in: [packages/db/src/query/ir.ts:27](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L27) *** @@ -143,7 +250,7 @@ Defined in: [packages/db/src/query/ir.ts:22](https://github.com/TanStack/db/blob optional orderBy: OrderBy; ``` -Defined in: [packages/db/src/query/ir.ts:20](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L20) +Defined in: [packages/db/src/query/ir.ts:25](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L25) *** @@ -153,7 +260,7 @@ Defined in: [packages/db/src/query/ir.ts:20](https://github.com/TanStack/db/blob optional select: Select; ``` -Defined in: [packages/db/src/query/ir.ts:15](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L15) +Defined in: [packages/db/src/query/ir.ts:18](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L18) *** @@ -163,7 +270,7 @@ Defined in: [packages/db/src/query/ir.ts:15](https://github.com/TanStack/db/blob optional singleResult: true; ``` -Defined in: [packages/db/src/query/ir.ts:24](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L24) +Defined in: [packages/db/src/query/ir.ts:29](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L29) *** @@ -173,4 +280,4 @@ Defined in: [packages/db/src/query/ir.ts:24](https://github.com/TanStack/db/blob optional where: Where[]; ``` -Defined in: [packages/db/src/query/ir.ts:17](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L17) +Defined in: [packages/db/src/query/ir.ts:20](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L20) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md index 93addc9dd4..679e081f3d 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/BasicExpression.md @@ -10,7 +10,7 @@ type BasicExpression = | Func; ``` -Defined in: [packages/db/src/query/ir.ts:258](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L258) +Defined in: [packages/db/src/query/ir.ts:263](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L263) ## Type Parameters diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/CollectionSourceRef.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/CollectionSourceRef.md index 463ab615cc..d53b2f80bb 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/CollectionSourceRef.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/CollectionSourceRef.md @@ -9,4 +9,4 @@ type CollectionSourceRef = | DescriptorRef; ``` -Defined in: [packages/db/src/query/ir.ts:37](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L37) +Defined in: [packages/db/src/query/ir.ts:42](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L42) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md index b377881258..7efc7e9abe 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/ConditionalSelectBranch.md @@ -7,7 +7,7 @@ title: ConditionalSelectBranch type ConditionalSelectBranch = object; ``` -Defined in: [packages/db/src/query/ir.ts:296](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L296) +Defined in: [packages/db/src/query/ir.ts:305](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L305) ## Properties @@ -17,7 +17,7 @@ Defined in: [packages/db/src/query/ir.ts:296](https://github.com/TanStack/db/blo condition: BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:297](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L297) +Defined in: [packages/db/src/query/ir.ts:306](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L306) *** @@ -27,4 +27,4 @@ Defined in: [packages/db/src/query/ir.ts:297](https://github.com/TanStack/db/blo value: SelectValueExpression; ``` -Defined in: [packages/db/src/query/ir.ts:298](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L298) +Defined in: [packages/db/src/query/ir.ts:307](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L307) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md index 36ed7c1405..e4b82f44af 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/From.md @@ -11,4 +11,4 @@ type From = | UnionAll; ``` -Defined in: [packages/db/src/query/ir.ts:39](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L39) +Defined in: [packages/db/src/query/ir.ts:44](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L44) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md index 04b5f782e8..688238e51f 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/GroupBy.md @@ -7,4 +7,4 @@ title: GroupBy type GroupBy = BasicExpression[]; ``` -Defined in: [packages/db/src/query/ir.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L58) +Defined in: [packages/db/src/query/ir.ts:63](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L63) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md index 3b3c1de2bd..a863bc9713 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Having.md @@ -7,4 +7,4 @@ title: Having type Having = Where; ``` -Defined in: [packages/db/src/query/ir.ts:60](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L60) +Defined in: [packages/db/src/query/ir.ts:65](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L65) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md index 59f0a02e51..b6a441fe38 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/IncludesMaterialization.md @@ -7,4 +7,4 @@ title: IncludesMaterialization type IncludesMaterialization = "collection" | "array" | "singleton" | "concat"; ``` -Defined in: [packages/db/src/query/ir.ts:32](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L32) +Defined in: [packages/db/src/query/ir.ts:37](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L37) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md index 66184f5b49..2ff4eeeb36 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Join.md @@ -7,4 +7,4 @@ title: Join type Join = JoinClause[]; ``` -Defined in: [packages/db/src/query/ir.ts:46](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L46) +Defined in: [packages/db/src/query/ir.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L51) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md index 75fa827380..babb6937fa 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Limit.md @@ -7,4 +7,4 @@ title: Limit type Limit = number; ``` -Defined in: [packages/db/src/query/ir.ts:71](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L71) +Defined in: [packages/db/src/query/ir.ts:76](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L76) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md index 7ad44b2d52..a5c929829a 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Offset.md @@ -7,4 +7,4 @@ title: Offset type Offset = number; ``` -Defined in: [packages/db/src/query/ir.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L73) +Defined in: [packages/db/src/query/ir.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L78) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md index ddf3008f96..92b7027abd 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderBy.md @@ -7,4 +7,4 @@ title: OrderBy type OrderBy = OrderByClause[]; ``` -Defined in: [packages/db/src/query/ir.ts:62](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L62) +Defined in: [packages/db/src/query/ir.ts:67](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L67) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md index b133297862..34afa49deb 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByClause.md @@ -7,7 +7,7 @@ title: OrderByClause type OrderByClause = object; ``` -Defined in: [packages/db/src/query/ir.ts:64](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L64) +Defined in: [packages/db/src/query/ir.ts:69](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L69) ## Properties @@ -17,7 +17,7 @@ Defined in: [packages/db/src/query/ir.ts:64](https://github.com/TanStack/db/blob compareOptions: CompareOptions; ``` -Defined in: [packages/db/src/query/ir.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L66) +Defined in: [packages/db/src/query/ir.ts:71](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L71) *** @@ -27,4 +27,4 @@ Defined in: [packages/db/src/query/ir.ts:66](https://github.com/TanStack/db/blob expression: BasicExpression; ``` -Defined in: [packages/db/src/query/ir.ts:65](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L65) +Defined in: [packages/db/src/query/ir.ts:70](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L70) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md index 7ce4889052..782f8a54d4 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/OrderByDirection.md @@ -7,4 +7,4 @@ title: OrderByDirection type OrderByDirection = "asc" | "desc"; ``` -Defined in: [packages/db/src/query/ir.ts:69](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L69) +Defined in: [packages/db/src/query/ir.ts:74](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L74) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md index 21efa898ba..144beb79ec 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Select.md @@ -7,7 +7,7 @@ title: Select type Select = object; ``` -Defined in: [packages/db/src/query/ir.ts:41](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L41) +Defined in: [packages/db/src/query/ir.ts:46](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L46) ## Index Signature diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md index 85c9a9c872..f432403273 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/SelectValueExpression.md @@ -12,4 +12,4 @@ type SelectValueExpression = | ConditionalSelect; ``` -Defined in: [packages/db/src/query/ir.ts:301](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L301) +Defined in: [packages/db/src/query/ir.ts:310](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L310) diff --git a/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md b/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md index 87240f0c49..dbfb16f992 100644 --- a/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md +++ b/docs/reference/@tanstack/namespaces/IR/type-aliases/Where.md @@ -12,4 +12,4 @@ type Where = }; ``` -Defined in: [packages/db/src/query/ir.ts:54](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L54) +Defined in: [packages/db/src/query/ir.ts:59](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L59) diff --git a/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md b/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md index a19de28691..97d35abe1d 100644 --- a/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md +++ b/docs/reference/@tanstack/namespaces/IR/variables/INCLUDES_SCALAR_FIELD.md @@ -7,4 +7,4 @@ title: INCLUDES_SCALAR_FIELD const INCLUDES_SCALAR_FIELD: "__includes_scalar__"; ``` -Defined in: [packages/db/src/query/ir.ts:35](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L35) +Defined in: [packages/db/src/query/ir.ts:40](https://github.com/TanStack/db/blob/main/packages/db/src/query/ir.ts#L40) diff --git a/docs/reference/classes/AggregateFunctionNotInSelectError.md b/docs/reference/classes/AggregateFunctionNotInSelectError.md index 54dbc5a00f..644d175fb2 100644 --- a/docs/reference/classes/AggregateFunctionNotInSelectError.md +++ b/docs/reference/classes/AggregateFunctionNotInSelectError.md @@ -3,7 +3,7 @@ id: AggregateFunctionNotInSelectError title: AggregateFunctionNotInSelectError --- -Defined in: [packages/db/src/errors.ts:967](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L967) +Defined in: [packages/db/src/errors.ts:987](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L987) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:967](https://github.com/TanStack/db/blob/ new AggregateFunctionNotInSelectError(functionName): AggregateFunctionNotInSelectError; ``` -Defined in: [packages/db/src/errors.ts:968](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L968) +Defined in: [packages/db/src/errors.ts:988](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L988) #### Parameters diff --git a/docs/reference/classes/BaseQueryBuilder.md b/docs/reference/classes/BaseQueryBuilder.md index 0b189533d2..4d637dd525 100644 --- a/docs/reference/classes/BaseQueryBuilder.md +++ b/docs/reference/classes/BaseQueryBuilder.md @@ -3,7 +3,7 @@ id: BaseQueryBuilder title: BaseQueryBuilder --- -Defined in: [packages/db/src/query/builder/index.ts:138](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L138) +Defined in: [packages/db/src/query/builder/index.ts:143](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L143) ## Type Parameters @@ -19,7 +19,7 @@ Defined in: [packages/db/src/query/builder/index.ts:138](https://github.com/TanS new BaseQueryBuilder(query, owned): BaseQueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:141](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L141) +Defined in: [packages/db/src/query/builder/index.ts:146](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L146) #### Parameters @@ -49,7 +49,7 @@ Whether the builder may keep `query` without copying it. get fn(): object; ``` -Defined in: [packages/db/src/query/builder/index.ts:946](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L946) +Defined in: [packages/db/src/query/builder/index.ts:965](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L965) Functional variants of the query builder These are imperative function that are called for ery row. @@ -192,7 +192,7 @@ query _bindCollectionSources(resolveCollection): BaseQueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:1051](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1051) +Defined in: [packages/db/src/query/builder/index.ts:1070](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1070) Bind this query for one consumer. Later descriptor clauses wait for another consumer. @@ -214,7 +214,7 @@ Bind this query for one consumer. Later descriptor clauses wait for another cons _getQuery(): QueryIR; ``` -Defined in: [packages/db/src/query/builder/index.ts:1043](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1043) +Defined in: [packages/db/src/query/builder/index.ts:1062](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1062) #### Returns @@ -228,7 +228,7 @@ Defined in: [packages/db/src/query/builder/index.ts:1043](https://github.com/Tan distinct(): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:863](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L863) +Defined in: [packages/db/src/query/builder/index.ts:882](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L882) Specify that the query should return distinct rows. Deduplicates rows based on the selected columns. @@ -257,7 +257,7 @@ query findOne(): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:883](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L883) +Defined in: [packages/db/src/query/builder/index.ts:902](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L902) Specify that the query should return a single result @@ -285,7 +285,7 @@ query from(source): QueryBuilder>; ``` -Defined in: [packages/db/src/query/builder/index.ts:248](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L248) +Defined in: [packages/db/src/query/builder/index.ts:253](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L253) Specify the source table or subquery for the query @@ -328,7 +328,7 @@ query.from({ activeUsers }) fullJoin(source, onCallback): QueryBuilder, "full">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:468](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L468) +Defined in: [packages/db/src/query/builder/index.ts:473](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L473) Perform a FULL JOIN with another table or subquery @@ -372,20 +372,22 @@ query ### groupBy() ```ts -groupBy(callback): QueryBuilder; +groupBy(keys): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:782](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L782) +Defined in: [packages/db/src/query/builder/index.ts:797](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L797) Group rows by one or more columns for aggregation #### Parameters -##### callback +##### keys -[`GroupByCallback`](../type-aliases/GroupByCallback.md)\<`TContext`\> +A function that receives table references and returns the + field(s) to group by. A query with no aggregate wraps it in `groupKeys()`; + aggregates such as `count()` bring the grouping code themselves. -A function that receives table references and returns the field(s) to group by +[`GroupByCallback`](../type-aliases/GroupByCallback.md)\<`TContext`\> | [`GroupKeys`](../interfaces/GroupKeys.md)\<`TContext`\> #### Returns @@ -414,6 +416,12 @@ query category: sales.category, totalSales: sum(sales.amount) })) + +// Group without an aggregate +query + .from({ orders: ordersCollection }) + .groupBy(groupKeys(({orders}) => orders.status)) + .select(({orders}) => ({ status: orders.status })) ``` *** @@ -424,7 +432,7 @@ query having(callback): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:565](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L565) +Defined in: [packages/db/src/query/builder/index.ts:570](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L570) Filter grouped rows based on aggregate conditions @@ -473,7 +481,7 @@ query innerJoin(source, onCallback): QueryBuilder, "inner">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:442](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L442) +Defined in: [packages/db/src/query/builder/index.ts:447](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L447) Perform an INNER JOIN with another table or subquery @@ -523,7 +531,7 @@ join( type): QueryBuilder, TJoinType>>; ``` -Defined in: [packages/db/src/query/builder/index.ts:337](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L337) +Defined in: [packages/db/src/query/builder/index.ts:342](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L342) Join another table or subquery to the current query @@ -591,7 +599,7 @@ query leftJoin(source, onCallback): QueryBuilder, "left">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:390](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L390) +Defined in: [packages/db/src/query/builder/index.ts:395](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L395) Perform a LEFT JOIN with another table or subquery @@ -638,7 +646,7 @@ query limit(count): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:818](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L818) +Defined in: [packages/db/src/query/builder/index.ts:837](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L837) Limit the number of rows returned by the query `orderBy` is required for `limit` @@ -675,7 +683,7 @@ query offset(count): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:842](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L842) +Defined in: [packages/db/src/query/builder/index.ts:861](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L861) Skip a number of rows before returning results `orderBy` is required for `offset` @@ -713,7 +721,7 @@ query orderBy(callback, options): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:697](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L697) +Defined in: [packages/db/src/query/builder/index.ts:704](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L704) Sort the query results by one or more columns @@ -763,7 +771,7 @@ query rightJoin(source, onCallback): QueryBuilder, "right">>; ``` -Defined in: [packages/db/src/query/builder/index.ts:416](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L416) +Defined in: [packages/db/src/query/builder/index.ts:421](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L421) Perform a RIGHT JOIN with another table or subquery @@ -812,7 +820,7 @@ query select(callback): QueryBuilder>>; ``` -Defined in: [packages/db/src/query/builder/index.ts:631](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L631) +Defined in: [packages/db/src/query/builder/index.ts:636](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L636) Select specific columns or computed values from the query @@ -871,7 +879,7 @@ query select(callback): QueryBuilder>>; ``` -Defined in: [packages/db/src/query/builder/index.ts:636](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L636) +Defined in: [packages/db/src/query/builder/index.ts:641](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L641) Select specific columns or computed values from the query @@ -885,7 +893,7 @@ Select specific columns or computed values from the query ###### callback -(`refs`) => `TSelectValue` +(`refs`) => `WithoutBareSubquery`\<`TSelectValue`\> A function that receives table references and returns an object with selected fields or expressions @@ -934,7 +942,7 @@ query unionAll(source): QueryBuilder>; ``` -Defined in: [packages/db/src/query/builder/index.ts:274](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L274) +Defined in: [packages/db/src/query/builder/index.ts:279](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L279) Union multiple independent source streams in one query. @@ -974,7 +982,7 @@ query unionAll(...branches): QueryBuilder>; ``` -Defined in: [packages/db/src/query/builder/index.ts:277](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L277) +Defined in: [packages/db/src/query/builder/index.ts:282](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L282) Union multiple independent source streams in one query. @@ -1014,7 +1022,7 @@ query where(callback): QueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:507](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L507) +Defined in: [packages/db/src/query/builder/index.ts:512](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L512) Filter rows based on a condition diff --git a/docs/reference/classes/CannotCombineEmptyExpressionListError.md b/docs/reference/classes/CannotCombineEmptyExpressionListError.md index 1507d2eac6..47ab410efd 100644 --- a/docs/reference/classes/CannotCombineEmptyExpressionListError.md +++ b/docs/reference/classes/CannotCombineEmptyExpressionListError.md @@ -3,7 +3,7 @@ id: CannotCombineEmptyExpressionListError title: CannotCombineEmptyExpressionListError --- -Defined in: [packages/db/src/errors.ts:1099](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1099) +Defined in: [packages/db/src/errors.ts:1119](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1119) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:1099](https://github.com/TanStack/db/blob new CannotCombineEmptyExpressionListError(): CannotCombineEmptyExpressionListError; ``` -Defined in: [packages/db/src/errors.ts:1100](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1100) +Defined in: [packages/db/src/errors.ts:1120](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1120) #### Returns diff --git a/docs/reference/classes/CollectionInputNotFoundError.md b/docs/reference/classes/CollectionInputNotFoundError.md index a680a77cbf..04f5838204 100644 --- a/docs/reference/classes/CollectionInputNotFoundError.md +++ b/docs/reference/classes/CollectionInputNotFoundError.md @@ -3,7 +3,7 @@ id: CollectionInputNotFoundError title: CollectionInputNotFoundError --- -Defined in: [packages/db/src/errors.ts:766](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L766) +Defined in: [packages/db/src/errors.ts:786](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L786) Error thrown when a collection input stream is not found during query compilation. In self-joins, each alias (e.g., 'employee', 'manager') requires its own input stream. @@ -23,7 +23,7 @@ new CollectionInputNotFoundError( availableKeys?): CollectionInputNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:767](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L767) +Defined in: [packages/db/src/errors.ts:787](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L787) #### Parameters diff --git a/docs/reference/classes/CollectionPreloadAbortedError.md b/docs/reference/classes/CollectionPreloadAbortedError.md index 5578be5873..23b34af565 100644 --- a/docs/reference/classes/CollectionPreloadAbortedError.md +++ b/docs/reference/classes/CollectionPreloadAbortedError.md @@ -3,7 +3,7 @@ id: CollectionPreloadAbortedError title: CollectionPreloadAbortedError --- -Defined in: [packages/db/src/errors.ts:1068](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1068) +Defined in: [packages/db/src/errors.ts:1088](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1088) A collection was cleaned up before its initial preload became ready. @@ -19,7 +19,7 @@ A collection was cleaned up before its initial preload became ready. new CollectionPreloadAbortedError(): CollectionPreloadAbortedError; ``` -Defined in: [packages/db/src/errors.ts:1069](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1069) +Defined in: [packages/db/src/errors.ts:1089](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1089) #### Returns diff --git a/docs/reference/classes/DistinctRequiresSelectError.md b/docs/reference/classes/DistinctRequiresSelectError.md index 42ef8ab241..e4cad8c58d 100644 --- a/docs/reference/classes/DistinctRequiresSelectError.md +++ b/docs/reference/classes/DistinctRequiresSelectError.md @@ -3,7 +3,7 @@ id: DistinctRequiresSelectError title: DistinctRequiresSelectError --- -Defined in: [packages/db/src/errors.ts:696](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L696) +Defined in: [packages/db/src/errors.ts:706](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L706) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:696](https://github.com/TanStack/db/blob/ new DistinctRequiresSelectError(): DistinctRequiresSelectError; ``` -Defined in: [packages/db/src/errors.ts:697](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L697) +Defined in: [packages/db/src/errors.ts:707](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L707) #### Returns diff --git a/docs/reference/classes/DuplicateAliasInSubqueryError.md b/docs/reference/classes/DuplicateAliasInSubqueryError.md index e9dd8e6dc2..55c064eebb 100644 --- a/docs/reference/classes/DuplicateAliasInSubqueryError.md +++ b/docs/reference/classes/DuplicateAliasInSubqueryError.md @@ -3,7 +3,7 @@ id: DuplicateAliasInSubqueryError title: DuplicateAliasInSubqueryError --- -Defined in: [packages/db/src/errors.ts:798](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L798) +Defined in: [packages/db/src/errors.ts:818](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L818) Retained for callers that imported this formerly thrown error. @@ -24,7 +24,7 @@ are rejected only within one lexical scope or union branch namespace. new DuplicateAliasInSubqueryError(alias, parentAliases): DuplicateAliasInSubqueryError; ``` -Defined in: [packages/db/src/errors.ts:799](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L799) +Defined in: [packages/db/src/errors.ts:819](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L819) #### Parameters diff --git a/docs/reference/classes/EmptyReferencePathError.md b/docs/reference/classes/EmptyReferencePathError.md index f6a0a9ba0c..58687903cc 100644 --- a/docs/reference/classes/EmptyReferencePathError.md +++ b/docs/reference/classes/EmptyReferencePathError.md @@ -3,7 +3,7 @@ id: EmptyReferencePathError title: EmptyReferencePathError --- -Defined in: [packages/db/src/errors.ts:831](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L831) +Defined in: [packages/db/src/errors.ts:851](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L851) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:831](https://github.com/TanStack/db/blob/ new EmptyReferencePathError(): EmptyReferencePathError; ``` -Defined in: [packages/db/src/errors.ts:832](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L832) +Defined in: [packages/db/src/errors.ts:852](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L852) #### Returns diff --git a/docs/reference/classes/FnSelectWithGroupByError.md b/docs/reference/classes/FnSelectWithGroupByError.md index f31d7e520c..ceef68332b 100644 --- a/docs/reference/classes/FnSelectWithGroupByError.md +++ b/docs/reference/classes/FnSelectWithGroupByError.md @@ -3,7 +3,7 @@ id: FnSelectWithGroupByError title: FnSelectWithGroupByError --- -Defined in: [packages/db/src/errors.ts:706](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L706) +Defined in: [packages/db/src/errors.ts:716](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L716) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:706](https://github.com/TanStack/db/blob/ new FnSelectWithGroupByError(): FnSelectWithGroupByError; ``` -Defined in: [packages/db/src/errors.ts:707](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L707) +Defined in: [packages/db/src/errors.ts:717](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L717) #### Returns diff --git a/docs/reference/classes/GroupByError.md b/docs/reference/classes/GroupByError.md index dda9264335..7b163aee6d 100644 --- a/docs/reference/classes/GroupByError.md +++ b/docs/reference/classes/GroupByError.md @@ -3,7 +3,7 @@ id: GroupByError title: GroupByError --- -Defined in: [packages/db/src/errors.ts:940](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L940) +Defined in: [packages/db/src/errors.ts:960](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L960) ## Extends @@ -24,7 +24,7 @@ Defined in: [packages/db/src/errors.ts:940](https://github.com/TanStack/db/blob/ new GroupByError(message): GroupByError; ``` -Defined in: [packages/db/src/errors.ts:941](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L941) +Defined in: [packages/db/src/errors.ts:961](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L961) #### Parameters diff --git a/docs/reference/classes/GroupingOperatorRequiredError.md b/docs/reference/classes/GroupingOperatorRequiredError.md new file mode 100644 index 0000000000..dafe9758cb --- /dev/null +++ b/docs/reference/classes/GroupingOperatorRequiredError.md @@ -0,0 +1,212 @@ +--- +id: GroupingOperatorRequiredError +title: GroupingOperatorRequiredError +--- + +Defined in: [packages/db/src/errors.ts:729](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L729) + +## Extends + +- [`QueryCompilationError`](QueryCompilationError.md) + +## Constructors + +### Constructor + +```ts +new GroupingOperatorRequiredError(): GroupingOperatorRequiredError; +``` + +Defined in: [packages/db/src/errors.ts:730](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L730) + +#### Returns + +`GroupingOperatorRequiredError` + +#### Overrides + +[`QueryCompilationError`](QueryCompilationError.md).[`constructor`](QueryCompilationError.md#constructor) + +## Properties + +### cause? + +```ts +optional cause: unknown; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`cause`](QueryCompilationError.md#cause) + +*** + +### message + +```ts +message: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`message`](QueryCompilationError.md#message) + +*** + +### name + +```ts +name: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`name`](QueryCompilationError.md#name) + +*** + +### stack? + +```ts +optional stack: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`stack`](QueryCompilationError.md#stack) + +*** + +### stackTraceLimit + +```ts +static stackTraceLimit: number; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:67 + +The `Error.stackTraceLimit` property specifies the number of stack frames +collected by a stack trace (whether generated by `new Error().stack` or +`Error.captureStackTrace(obj)`). + +The default value is `10` but may be set to any valid JavaScript number. Changes +will affect any stack trace captured _after_ the value has been changed. + +If set to a non-number value, or set to a negative number, stack traces will +not capture any frames. + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`stackTraceLimit`](QueryCompilationError.md#stacktracelimit) + +## Methods + +### captureStackTrace() + +```ts +static captureStackTrace(targetObject, constructorOpt?): void; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:51 + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +#### Parameters + +##### targetObject + +`object` + +##### constructorOpt? + +`Function` + +#### Returns + +`void` + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`captureStackTrace`](QueryCompilationError.md#capturestacktrace) + +*** + +### prepareStackTrace() + +```ts +static prepareStackTrace(err, stackTraces): any; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:55 + +#### Parameters + +##### err + +`Error` + +##### stackTraces + +`CallSite`[] + +#### Returns + +`any` + +#### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +#### Inherited from + +[`QueryCompilationError`](QueryCompilationError.md).[`prepareStackTrace`](QueryCompilationError.md#preparestacktrace) diff --git a/docs/reference/classes/HavingRequiresGroupByError.md b/docs/reference/classes/HavingRequiresGroupByError.md index e11ac460b9..a707575a7c 100644 --- a/docs/reference/classes/HavingRequiresGroupByError.md +++ b/docs/reference/classes/HavingRequiresGroupByError.md @@ -3,7 +3,7 @@ id: HavingRequiresGroupByError title: HavingRequiresGroupByError --- -Defined in: [packages/db/src/errors.ts:742](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L742) +Defined in: [packages/db/src/errors.ts:762](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L762) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:742](https://github.com/TanStack/db/blob/ new HavingRequiresGroupByError(): HavingRequiresGroupByError; ``` -Defined in: [packages/db/src/errors.ts:743](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L743) +Defined in: [packages/db/src/errors.ts:763](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L763) #### Returns diff --git a/docs/reference/classes/IncludeOperatorRequiredError.md b/docs/reference/classes/IncludeOperatorRequiredError.md new file mode 100644 index 0000000000..b3aad549de --- /dev/null +++ b/docs/reference/classes/IncludeOperatorRequiredError.md @@ -0,0 +1,218 @@ +--- +id: IncludeOperatorRequiredError +title: IncludeOperatorRequiredError +--- + +Defined in: [packages/db/src/errors.ts:613](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L613) + +## Extends + +- [`QueryBuilderError`](QueryBuilderError.md) + +## Constructors + +### Constructor + +```ts +new IncludeOperatorRequiredError(field): IncludeOperatorRequiredError; +``` + +Defined in: [packages/db/src/errors.ts:614](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L614) + +#### Parameters + +##### field + +`string` + +#### Returns + +`IncludeOperatorRequiredError` + +#### Overrides + +[`QueryBuilderError`](QueryBuilderError.md).[`constructor`](QueryBuilderError.md#constructor) + +## Properties + +### cause? + +```ts +optional cause: unknown; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 + +#### Inherited from + +[`QueryBuilderError`](QueryBuilderError.md).[`cause`](QueryBuilderError.md#cause) + +*** + +### message + +```ts +message: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 + +#### Inherited from + +[`QueryBuilderError`](QueryBuilderError.md).[`message`](QueryBuilderError.md#message) + +*** + +### name + +```ts +name: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 + +#### Inherited from + +[`QueryBuilderError`](QueryBuilderError.md).[`name`](QueryBuilderError.md#name) + +*** + +### stack? + +```ts +optional stack: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 + +#### Inherited from + +[`QueryBuilderError`](QueryBuilderError.md).[`stack`](QueryBuilderError.md#stack) + +*** + +### stackTraceLimit + +```ts +static stackTraceLimit: number; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:67 + +The `Error.stackTraceLimit` property specifies the number of stack frames +collected by a stack trace (whether generated by `new Error().stack` or +`Error.captureStackTrace(obj)`). + +The default value is `10` but may be set to any valid JavaScript number. Changes +will affect any stack trace captured _after_ the value has been changed. + +If set to a non-number value, or set to a negative number, stack traces will +not capture any frames. + +#### Inherited from + +[`QueryBuilderError`](QueryBuilderError.md).[`stackTraceLimit`](QueryBuilderError.md#stacktracelimit) + +## Methods + +### captureStackTrace() + +```ts +static captureStackTrace(targetObject, constructorOpt?): void; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:51 + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +#### Parameters + +##### targetObject + +`object` + +##### constructorOpt? + +`Function` + +#### Returns + +`void` + +#### Inherited from + +[`QueryBuilderError`](QueryBuilderError.md).[`captureStackTrace`](QueryBuilderError.md#capturestacktrace) + +*** + +### prepareStackTrace() + +```ts +static prepareStackTrace(err, stackTraces): any; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:55 + +#### Parameters + +##### err + +`Error` + +##### stackTraces + +`CallSite`[] + +#### Returns + +`any` + +#### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +#### Inherited from + +[`QueryBuilderError`](QueryBuilderError.md).[`prepareStackTrace`](QueryBuilderError.md#preparestacktrace) diff --git a/docs/reference/classes/InvalidJoinCondition.md b/docs/reference/classes/InvalidJoinCondition.md index b04f1d4d35..833feabca2 100644 --- a/docs/reference/classes/InvalidJoinCondition.md +++ b/docs/reference/classes/InvalidJoinCondition.md @@ -3,7 +3,7 @@ id: InvalidJoinCondition title: InvalidJoinCondition --- -Defined in: [packages/db/src/errors.ts:919](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L919) +Defined in: [packages/db/src/errors.ts:939](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L939) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:919](https://github.com/TanStack/db/blob/ new InvalidJoinCondition(): InvalidJoinCondition; ``` -Defined in: [packages/db/src/errors.ts:920](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L920) +Defined in: [packages/db/src/errors.ts:940](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L940) #### Returns diff --git a/docs/reference/classes/InvalidJoinConditionLeftSourceError.md b/docs/reference/classes/InvalidJoinConditionLeftSourceError.md index 262f678750..84eb8b3242 100644 --- a/docs/reference/classes/InvalidJoinConditionLeftSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionLeftSourceError.md @@ -3,7 +3,7 @@ id: InvalidJoinConditionLeftSourceError title: InvalidJoinConditionLeftSourceError --- -Defined in: [packages/db/src/errors.ts:899](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L899) +Defined in: [packages/db/src/errors.ts:919](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L919) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:899](https://github.com/TanStack/db/blob/ new InvalidJoinConditionLeftSourceError(sourceAlias): InvalidJoinConditionLeftSourceError; ``` -Defined in: [packages/db/src/errors.ts:900](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L900) +Defined in: [packages/db/src/errors.ts:920](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L920) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionRightSourceError.md b/docs/reference/classes/InvalidJoinConditionRightSourceError.md index 5e5006c01c..d334af511a 100644 --- a/docs/reference/classes/InvalidJoinConditionRightSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionRightSourceError.md @@ -3,7 +3,7 @@ id: InvalidJoinConditionRightSourceError title: InvalidJoinConditionRightSourceError --- -Defined in: [packages/db/src/errors.ts:909](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L909) +Defined in: [packages/db/src/errors.ts:929](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L929) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:909](https://github.com/TanStack/db/blob/ new InvalidJoinConditionRightSourceError(sourceAlias): InvalidJoinConditionRightSourceError; ``` -Defined in: [packages/db/src/errors.ts:910](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L910) +Defined in: [packages/db/src/errors.ts:930](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L930) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionSameSourceError.md b/docs/reference/classes/InvalidJoinConditionSameSourceError.md index 4c04251592..cd03e45a53 100644 --- a/docs/reference/classes/InvalidJoinConditionSameSourceError.md +++ b/docs/reference/classes/InvalidJoinConditionSameSourceError.md @@ -3,7 +3,7 @@ id: InvalidJoinConditionSameSourceError title: InvalidJoinConditionSameSourceError --- -Defined in: [packages/db/src/errors.ts:879](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L879) +Defined in: [packages/db/src/errors.ts:899](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L899) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:879](https://github.com/TanStack/db/blob/ new InvalidJoinConditionSameSourceError(sourceAlias): InvalidJoinConditionSameSourceError; ``` -Defined in: [packages/db/src/errors.ts:880](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L880) +Defined in: [packages/db/src/errors.ts:900](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L900) #### Parameters diff --git a/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md b/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md index 0465d5a644..14c8f7dd9b 100644 --- a/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md +++ b/docs/reference/classes/InvalidJoinConditionSourceMismatchError.md @@ -3,7 +3,7 @@ id: InvalidJoinConditionSourceMismatchError title: InvalidJoinConditionSourceMismatchError --- -Defined in: [packages/db/src/errors.ts:889](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L889) +Defined in: [packages/db/src/errors.ts:909](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L909) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:889](https://github.com/TanStack/db/blob/ new InvalidJoinConditionSourceMismatchError(): InvalidJoinConditionSourceMismatchError; ``` -Defined in: [packages/db/src/errors.ts:890](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L890) +Defined in: [packages/db/src/errors.ts:910](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L910) #### Returns diff --git a/docs/reference/classes/InvalidPoolOptionError.md b/docs/reference/classes/InvalidPoolOptionError.md new file mode 100644 index 0000000000..016e08e7c1 --- /dev/null +++ b/docs/reference/classes/InvalidPoolOptionError.md @@ -0,0 +1,212 @@ +--- +id: InvalidPoolOptionError +title: InvalidPoolOptionError +--- + +Defined in: [packages/db/src/errors.ts:1218](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1218) + +## Extends + +- [`TanStackDBError`](TanStackDBError.md) + +## Constructors + +### Constructor + +```ts +new InvalidPoolOptionError(): InvalidPoolOptionError; +``` + +Defined in: [packages/db/src/errors.ts:1219](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1219) + +#### Returns + +`InvalidPoolOptionError` + +#### Overrides + +[`TanStackDBError`](TanStackDBError.md).[`constructor`](TanStackDBError.md#constructor) + +## Properties + +### cause? + +```ts +optional cause: unknown; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 + +#### Inherited from + +[`TanStackDBError`](TanStackDBError.md).[`cause`](TanStackDBError.md#cause) + +*** + +### message + +```ts +message: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 + +#### Inherited from + +[`TanStackDBError`](TanStackDBError.md).[`message`](TanStackDBError.md#message) + +*** + +### name + +```ts +name: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 + +#### Inherited from + +[`TanStackDBError`](TanStackDBError.md).[`name`](TanStackDBError.md#name) + +*** + +### stack? + +```ts +optional stack: string; +``` + +Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 + +#### Inherited from + +[`TanStackDBError`](TanStackDBError.md).[`stack`](TanStackDBError.md#stack) + +*** + +### stackTraceLimit + +```ts +static stackTraceLimit: number; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:67 + +The `Error.stackTraceLimit` property specifies the number of stack frames +collected by a stack trace (whether generated by `new Error().stack` or +`Error.captureStackTrace(obj)`). + +The default value is `10` but may be set to any valid JavaScript number. Changes +will affect any stack trace captured _after_ the value has been changed. + +If set to a non-number value, or set to a negative number, stack traces will +not capture any frames. + +#### Inherited from + +[`TanStackDBError`](TanStackDBError.md).[`stackTraceLimit`](TanStackDBError.md#stacktracelimit) + +## Methods + +### captureStackTrace() + +```ts +static captureStackTrace(targetObject, constructorOpt?): void; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:51 + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +#### Parameters + +##### targetObject + +`object` + +##### constructorOpt? + +`Function` + +#### Returns + +`void` + +#### Inherited from + +[`TanStackDBError`](TanStackDBError.md).[`captureStackTrace`](TanStackDBError.md#capturestacktrace) + +*** + +### prepareStackTrace() + +```ts +static prepareStackTrace(err, stackTraces): any; +``` + +Defined in: node\_modules/.pnpm/@types+node@25.2.2/node\_modules/@types/node/globals.d.ts:55 + +#### Parameters + +##### err + +`Error` + +##### stackTraces + +`CallSite`[] + +#### Returns + +`any` + +#### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +#### Inherited from + +[`TanStackDBError`](TanStackDBError.md).[`prepareStackTrace`](TanStackDBError.md#preparestacktrace) diff --git a/docs/reference/classes/InvalidSourceTypeError.md b/docs/reference/classes/InvalidSourceTypeError.md index 6c672ddaaf..cff1999971 100644 --- a/docs/reference/classes/InvalidSourceTypeError.md +++ b/docs/reference/classes/InvalidSourceTypeError.md @@ -3,7 +3,7 @@ id: InvalidSourceTypeError title: InvalidSourceTypeError --- -Defined in: [packages/db/src/errors.ts:616](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L616) +Defined in: [packages/db/src/errors.ts:626](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L626) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:616](https://github.com/TanStack/db/blob/ new InvalidSourceTypeError(context, type): InvalidSourceTypeError; ``` -Defined in: [packages/db/src/errors.ts:617](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L617) +Defined in: [packages/db/src/errors.ts:627](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L627) #### Parameters diff --git a/docs/reference/classes/InvalidStorageDataFormatError.md b/docs/reference/classes/InvalidStorageDataFormatError.md index 3c15655e8e..78e9e71e62 100644 --- a/docs/reference/classes/InvalidStorageDataFormatError.md +++ b/docs/reference/classes/InvalidStorageDataFormatError.md @@ -3,7 +3,7 @@ id: InvalidStorageDataFormatError title: InvalidStorageDataFormatError --- -Defined in: [packages/db/src/errors.ts:1023](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1023) +Defined in: [packages/db/src/errors.ts:1043](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1043) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:1023](https://github.com/TanStack/db/blob new InvalidStorageDataFormatError(storageKey, key): InvalidStorageDataFormatError; ``` -Defined in: [packages/db/src/errors.ts:1024](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1024) +Defined in: [packages/db/src/errors.ts:1044](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1044) #### Parameters diff --git a/docs/reference/classes/InvalidStorageObjectFormatError.md b/docs/reference/classes/InvalidStorageObjectFormatError.md index adb1573d08..dc8b39fc16 100644 --- a/docs/reference/classes/InvalidStorageObjectFormatError.md +++ b/docs/reference/classes/InvalidStorageObjectFormatError.md @@ -3,7 +3,7 @@ id: InvalidStorageObjectFormatError title: InvalidStorageObjectFormatError --- -Defined in: [packages/db/src/errors.ts:1033](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1033) +Defined in: [packages/db/src/errors.ts:1053](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1053) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:1033](https://github.com/TanStack/db/blob new InvalidStorageObjectFormatError(storageKey): InvalidStorageObjectFormatError; ``` -Defined in: [packages/db/src/errors.ts:1034](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1034) +Defined in: [packages/db/src/errors.ts:1054](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1054) #### Parameters diff --git a/docs/reference/classes/InvalidWhereExpressionError.md b/docs/reference/classes/InvalidWhereExpressionError.md index 623a5eb4ac..e87dd6b535 100644 --- a/docs/reference/classes/InvalidWhereExpressionError.md +++ b/docs/reference/classes/InvalidWhereExpressionError.md @@ -3,7 +3,7 @@ id: InvalidWhereExpressionError title: InvalidWhereExpressionError --- -Defined in: [packages/db/src/errors.ts:661](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L661) +Defined in: [packages/db/src/errors.ts:671](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L671) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:661](https://github.com/TanStack/db/blob/ new InvalidWhereExpressionError(valueType): InvalidWhereExpressionError; ``` -Defined in: [packages/db/src/errors.ts:662](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L662) +Defined in: [packages/db/src/errors.ts:672](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L672) #### Parameters diff --git a/docs/reference/classes/JoinCollectionNotFoundError.md b/docs/reference/classes/JoinCollectionNotFoundError.md index 413f597c7a..8ea45dc86b 100644 --- a/docs/reference/classes/JoinCollectionNotFoundError.md +++ b/docs/reference/classes/JoinCollectionNotFoundError.md @@ -3,7 +3,7 @@ id: JoinCollectionNotFoundError title: JoinCollectionNotFoundError --- -Defined in: [packages/db/src/errors.ts:851](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L851) +Defined in: [packages/db/src/errors.ts:871](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L871) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:851](https://github.com/TanStack/db/blob/ new JoinCollectionNotFoundError(collectionId): JoinCollectionNotFoundError; ``` -Defined in: [packages/db/src/errors.ts:852](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L852) +Defined in: [packages/db/src/errors.ts:872](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L872) #### Parameters diff --git a/docs/reference/classes/JoinConditionMustBeEqualityError.md b/docs/reference/classes/JoinConditionMustBeEqualityError.md index a3dd24e4ce..091696c438 100644 --- a/docs/reference/classes/JoinConditionMustBeEqualityError.md +++ b/docs/reference/classes/JoinConditionMustBeEqualityError.md @@ -3,7 +3,7 @@ id: JoinConditionMustBeEqualityError title: JoinConditionMustBeEqualityError --- -Defined in: [packages/db/src/errors.ts:641](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L641) +Defined in: [packages/db/src/errors.ts:651](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L651) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:641](https://github.com/TanStack/db/blob/ new JoinConditionMustBeEqualityError(): JoinConditionMustBeEqualityError; ``` -Defined in: [packages/db/src/errors.ts:642](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L642) +Defined in: [packages/db/src/errors.ts:652](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L652) #### Returns diff --git a/docs/reference/classes/JoinError.md b/docs/reference/classes/JoinError.md index 930a82c829..750e4fc653 100644 --- a/docs/reference/classes/JoinError.md +++ b/docs/reference/classes/JoinError.md @@ -3,7 +3,7 @@ id: JoinError title: JoinError --- -Defined in: [packages/db/src/errors.ts:862](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L862) +Defined in: [packages/db/src/errors.ts:882](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L882) ## Extends @@ -27,7 +27,7 @@ Defined in: [packages/db/src/errors.ts:862](https://github.com/TanStack/db/blob/ new JoinError(message): JoinError; ``` -Defined in: [packages/db/src/errors.ts:863](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L863) +Defined in: [packages/db/src/errors.ts:883](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L883) #### Parameters diff --git a/docs/reference/classes/LimitOffsetRequireOrderByError.md b/docs/reference/classes/LimitOffsetRequireOrderByError.md index 96678fc0e3..1e0b4ec6cd 100644 --- a/docs/reference/classes/LimitOffsetRequireOrderByError.md +++ b/docs/reference/classes/LimitOffsetRequireOrderByError.md @@ -3,7 +3,7 @@ id: LimitOffsetRequireOrderByError title: LimitOffsetRequireOrderByError --- -Defined in: [packages/db/src/errors.ts:752](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L752) +Defined in: [packages/db/src/errors.ts:772](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L772) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:752](https://github.com/TanStack/db/blob/ new LimitOffsetRequireOrderByError(): LimitOffsetRequireOrderByError; ``` -Defined in: [packages/db/src/errors.ts:753](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L753) +Defined in: [packages/db/src/errors.ts:773](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L773) #### Returns diff --git a/docs/reference/classes/LoadSubsetOperationAbortedError.md b/docs/reference/classes/LoadSubsetOperationAbortedError.md index 40258450dc..3835561a45 100644 --- a/docs/reference/classes/LoadSubsetOperationAbortedError.md +++ b/docs/reference/classes/LoadSubsetOperationAbortedError.md @@ -3,7 +3,7 @@ id: LoadSubsetOperationAbortedError title: LoadSubsetOperationAbortedError --- -Defined in: [packages/db/src/errors.ts:1080](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1080) +Defined in: [packages/db/src/errors.ts:1100](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1100) A subset operation was canceled before its result became visible. @@ -19,7 +19,7 @@ A subset operation was canceled before its result became visible. new LoadSubsetOperationAbortedError(): LoadSubsetOperationAbortedError; ``` -Defined in: [packages/db/src/errors.ts:1081](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1081) +Defined in: [packages/db/src/errors.ts:1101](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1101) #### Returns diff --git a/docs/reference/classes/LocalStorageCollectionError.md b/docs/reference/classes/LocalStorageCollectionError.md index 6555c32205..79f4f2209e 100644 --- a/docs/reference/classes/LocalStorageCollectionError.md +++ b/docs/reference/classes/LocalStorageCollectionError.md @@ -3,7 +3,7 @@ id: LocalStorageCollectionError title: LocalStorageCollectionError --- -Defined in: [packages/db/src/errors.ts:1006](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1006) +Defined in: [packages/db/src/errors.ts:1026](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1026) ## Extends @@ -23,7 +23,7 @@ Defined in: [packages/db/src/errors.ts:1006](https://github.com/TanStack/db/blob new LocalStorageCollectionError(message): LocalStorageCollectionError; ``` -Defined in: [packages/db/src/errors.ts:1007](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1007) +Defined in: [packages/db/src/errors.ts:1027](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1027) #### Parameters diff --git a/docs/reference/classes/MissingAliasInputsError.md b/docs/reference/classes/MissingAliasInputsError.md index 97a76aa771..c426bf423d 100644 --- a/docs/reference/classes/MissingAliasInputsError.md +++ b/docs/reference/classes/MissingAliasInputsError.md @@ -3,7 +3,7 @@ id: MissingAliasInputsError title: MissingAliasInputsError --- -Defined in: [packages/db/src/errors.ts:1113](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1113) +Defined in: [packages/db/src/errors.ts:1133](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1133) Internal error when the compiler returns aliases that don't have corresponding input streams. This should never happen since all aliases come from user declarations. @@ -20,7 +20,7 @@ This should never happen since all aliases come from user declarations. new MissingAliasInputsError(missingAliases): MissingAliasInputsError; ``` -Defined in: [packages/db/src/errors.ts:1114](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1114) +Defined in: [packages/db/src/errors.ts:1134](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1134) #### Parameters diff --git a/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md b/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md index 64c0f0494a..122cd0987e 100644 --- a/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md +++ b/docs/reference/classes/NonAggregateExpressionNotInGroupByError.md @@ -3,7 +3,7 @@ id: NonAggregateExpressionNotInGroupByError title: NonAggregateExpressionNotInGroupByError --- -Defined in: [packages/db/src/errors.ts:947](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L947) +Defined in: [packages/db/src/errors.ts:967](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L967) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:947](https://github.com/TanStack/db/blob/ new NonAggregateExpressionNotInGroupByError(alias): NonAggregateExpressionNotInGroupByError; ``` -Defined in: [packages/db/src/errors.ts:948](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L948) +Defined in: [packages/db/src/errors.ts:968](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L968) #### Parameters diff --git a/docs/reference/classes/QueryBuilderError.md b/docs/reference/classes/QueryBuilderError.md index dccf34f433..e6c6a58429 100644 --- a/docs/reference/classes/QueryBuilderError.md +++ b/docs/reference/classes/QueryBuilderError.md @@ -14,6 +14,7 @@ Defined in: [packages/db/src/errors.ts:576](https://github.com/TanStack/db/blob/ - [`OnlyOneSourceAllowedError`](OnlyOneSourceAllowedError.md) - [`SubQueryMustHaveFromClauseError`](SubQueryMustHaveFromClauseError.md) - [`InvalidSourceError`](InvalidSourceError.md) +- [`IncludeOperatorRequiredError`](IncludeOperatorRequiredError.md) - [`InvalidSourceTypeError`](InvalidSourceTypeError.md) - [`JoinConditionMustBeEqualityError`](JoinConditionMustBeEqualityError.md) - [`QueryMustHaveFromClauseError`](QueryMustHaveFromClauseError.md) diff --git a/docs/reference/classes/QueryCompilationError.md b/docs/reference/classes/QueryCompilationError.md index 081cd0086b..d41977d8ea 100644 --- a/docs/reference/classes/QueryCompilationError.md +++ b/docs/reference/classes/QueryCompilationError.md @@ -3,7 +3,7 @@ id: QueryCompilationError title: QueryCompilationError --- -Defined in: [packages/db/src/errors.ts:677](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L677) +Defined in: [packages/db/src/errors.ts:687](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L687) ## Extends @@ -14,6 +14,7 @@ Defined in: [packages/db/src/errors.ts:677](https://github.com/TanStack/db/blob/ - [`UnsafeAliasPathError`](UnsafeAliasPathError.md) - [`DistinctRequiresSelectError`](DistinctRequiresSelectError.md) - [`FnSelectWithGroupByError`](FnSelectWithGroupByError.md) +- [`GroupingOperatorRequiredError`](GroupingOperatorRequiredError.md) - [`UnsupportedFnSelectResultError`](UnsupportedFnSelectResultError.md) - [`UnsupportedRootScalarSelectError`](UnsupportedRootScalarSelectError.md) - [`HavingRequiresGroupByError`](HavingRequiresGroupByError.md) @@ -37,7 +38,7 @@ Defined in: [packages/db/src/errors.ts:677](https://github.com/TanStack/db/blob/ new QueryCompilationError(message): QueryCompilationError; ``` -Defined in: [packages/db/src/errors.ts:678](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L678) +Defined in: [packages/db/src/errors.ts:688](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L688) #### Parameters diff --git a/docs/reference/classes/QueryMustHaveFromClauseError.md b/docs/reference/classes/QueryMustHaveFromClauseError.md index efffac467e..3118c5f841 100644 --- a/docs/reference/classes/QueryMustHaveFromClauseError.md +++ b/docs/reference/classes/QueryMustHaveFromClauseError.md @@ -3,7 +3,7 @@ id: QueryMustHaveFromClauseError title: QueryMustHaveFromClauseError --- -Defined in: [packages/db/src/errors.ts:651](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L651) +Defined in: [packages/db/src/errors.ts:661](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L661) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:651](https://github.com/TanStack/db/blob/ new QueryMustHaveFromClauseError(): QueryMustHaveFromClauseError; ``` -Defined in: [packages/db/src/errors.ts:652](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L652) +Defined in: [packages/db/src/errors.ts:662](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L662) #### Returns diff --git a/docs/reference/classes/QueryOptimizerError.md b/docs/reference/classes/QueryOptimizerError.md index 9951d5cb43..1056285c53 100644 --- a/docs/reference/classes/QueryOptimizerError.md +++ b/docs/reference/classes/QueryOptimizerError.md @@ -3,7 +3,7 @@ id: QueryOptimizerError title: QueryOptimizerError --- -Defined in: [packages/db/src/errors.ts:1092](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1092) +Defined in: [packages/db/src/errors.ts:1112](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1112) ## Extends @@ -21,7 +21,7 @@ Defined in: [packages/db/src/errors.ts:1092](https://github.com/TanStack/db/blob new QueryOptimizerError(message): QueryOptimizerError; ``` -Defined in: [packages/db/src/errors.ts:1093](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1093) +Defined in: [packages/db/src/errors.ts:1113](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1113) #### Parameters diff --git a/docs/reference/classes/SerializationError.md b/docs/reference/classes/SerializationError.md index 062bcd34b1..5316cec026 100644 --- a/docs/reference/classes/SerializationError.md +++ b/docs/reference/classes/SerializationError.md @@ -3,7 +3,7 @@ id: SerializationError title: SerializationError --- -Defined in: [packages/db/src/errors.ts:995](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L995) +Defined in: [packages/db/src/errors.ts:1015](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1015) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:995](https://github.com/TanStack/db/blob/ new SerializationError(operation, originalError): SerializationError; ``` -Defined in: [packages/db/src/errors.ts:996](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L996) +Defined in: [packages/db/src/errors.ts:1016](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1016) #### Parameters diff --git a/docs/reference/classes/SetWindowFindOneLimitError.md b/docs/reference/classes/SetWindowFindOneLimitError.md index a060541d64..ac85da1cf6 100644 --- a/docs/reference/classes/SetWindowFindOneLimitError.md +++ b/docs/reference/classes/SetWindowFindOneLimitError.md @@ -3,7 +3,7 @@ id: SetWindowFindOneLimitError title: SetWindowFindOneLimitError --- -Defined in: [packages/db/src/errors.ts:1141](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1141) +Defined in: [packages/db/src/errors.ts:1161](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1161) Error thrown when setWindow would change the limit of a findOne() query. @@ -19,7 +19,7 @@ Error thrown when setWindow would change the limit of a findOne() query. new SetWindowFindOneLimitError(): SetWindowFindOneLimitError; ``` -Defined in: [packages/db/src/errors.ts:1142](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1142) +Defined in: [packages/db/src/errors.ts:1162](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1162) #### Returns diff --git a/docs/reference/classes/SetWindowReentrancyError.md b/docs/reference/classes/SetWindowReentrancyError.md index f8a7b0285a..1d8c498eb4 100644 --- a/docs/reference/classes/SetWindowReentrancyError.md +++ b/docs/reference/classes/SetWindowReentrancyError.md @@ -3,7 +3,7 @@ id: SetWindowReentrancyError title: SetWindowReentrancyError --- -Defined in: [packages/db/src/errors.ts:1152](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1152) +Defined in: [packages/db/src/errors.ts:1172](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1172) Error thrown when setWindow is called from inside another setWindow call. @@ -19,7 +19,7 @@ Error thrown when setWindow is called from inside another setWindow call. new SetWindowReentrancyError(): SetWindowReentrancyError; ``` -Defined in: [packages/db/src/errors.ts:1153](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1153) +Defined in: [packages/db/src/errors.ts:1173](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1173) #### Returns diff --git a/docs/reference/classes/SetWindowRequiresOrderByError.md b/docs/reference/classes/SetWindowRequiresOrderByError.md index 9ce0624498..cbf09e17f9 100644 --- a/docs/reference/classes/SetWindowRequiresOrderByError.md +++ b/docs/reference/classes/SetWindowRequiresOrderByError.md @@ -3,7 +3,7 @@ id: SetWindowRequiresOrderByError title: SetWindowRequiresOrderByError --- -Defined in: [packages/db/src/errors.ts:1127](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1127) +Defined in: [packages/db/src/errors.ts:1147](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1147) Error thrown when setWindow is called on a collection without an ORDER BY clause. @@ -19,7 +19,7 @@ Error thrown when setWindow is called on a collection without an ORDER BY clause new SetWindowRequiresOrderByError(): SetWindowRequiresOrderByError; ``` -Defined in: [packages/db/src/errors.ts:1128](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1128) +Defined in: [packages/db/src/errors.ts:1148](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1148) #### Returns diff --git a/docs/reference/classes/SourceCollectionIdReuseError.md b/docs/reference/classes/SourceCollectionIdReuseError.md index db7e5101c8..a1280107f7 100644 --- a/docs/reference/classes/SourceCollectionIdReuseError.md +++ b/docs/reference/classes/SourceCollectionIdReuseError.md @@ -3,7 +3,7 @@ id: SourceCollectionIdReuseError title: SourceCollectionIdReuseError --- -Defined in: [packages/db/src/errors.ts:1184](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1184) +Defined in: [packages/db/src/errors.ts:1204](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1204) Error thrown when a mounted framework binding that compares query identity reads a different source Collection under a Collection ID it has already @@ -23,7 +23,7 @@ source. new SourceCollectionIdReuseError(bindingName, collectionId): SourceCollectionIdReuseError; ``` -Defined in: [packages/db/src/errors.ts:1185](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1185) +Defined in: [packages/db/src/errors.ts:1205](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1205) #### Parameters @@ -51,7 +51,7 @@ Defined in: [packages/db/src/errors.ts:1185](https://github.com/TanStack/db/blob readonly bindingName: string; ``` -Defined in: [packages/db/src/errors.ts:1186](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1186) +Defined in: [packages/db/src/errors.ts:1206](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1206) *** @@ -75,7 +75,7 @@ Defined in: node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/li readonly collectionId: string; ``` -Defined in: [packages/db/src/errors.ts:1187](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1187) +Defined in: [packages/db/src/errors.ts:1207](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1207) *** diff --git a/docs/reference/classes/StorageError.md b/docs/reference/classes/StorageError.md index 57ea54b824..94654fc200 100644 --- a/docs/reference/classes/StorageError.md +++ b/docs/reference/classes/StorageError.md @@ -3,7 +3,7 @@ id: StorageError title: StorageError --- -Defined in: [packages/db/src/errors.ts:988](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L988) +Defined in: [packages/db/src/errors.ts:1008](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1008) ## Extends @@ -22,7 +22,7 @@ Defined in: [packages/db/src/errors.ts:988](https://github.com/TanStack/db/blob/ new StorageError(message): StorageError; ``` -Defined in: [packages/db/src/errors.ts:989](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L989) +Defined in: [packages/db/src/errors.ts:1009](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1009) #### Parameters diff --git a/docs/reference/classes/StorageKeyRequiredError.md b/docs/reference/classes/StorageKeyRequiredError.md index d5409d93f0..64de2c736d 100644 --- a/docs/reference/classes/StorageKeyRequiredError.md +++ b/docs/reference/classes/StorageKeyRequiredError.md @@ -3,7 +3,7 @@ id: StorageKeyRequiredError title: StorageKeyRequiredError --- -Defined in: [packages/db/src/errors.ts:1013](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1013) +Defined in: [packages/db/src/errors.ts:1033](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1033) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:1013](https://github.com/TanStack/db/blob new StorageKeyRequiredError(): StorageKeyRequiredError; ``` -Defined in: [packages/db/src/errors.ts:1014](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1014) +Defined in: [packages/db/src/errors.ts:1034](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1034) #### Returns diff --git a/docs/reference/classes/SyncCleanupError.md b/docs/reference/classes/SyncCleanupError.md index 7cb34e4aef..5d695bdbb9 100644 --- a/docs/reference/classes/SyncCleanupError.md +++ b/docs/reference/classes/SyncCleanupError.md @@ -3,7 +3,7 @@ id: SyncCleanupError title: SyncCleanupError --- -Defined in: [packages/db/src/errors.ts:1044](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1044) +Defined in: [packages/db/src/errors.ts:1064](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1064) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:1044](https://github.com/TanStack/db/blob new SyncCleanupError(collectionId, error): SyncCleanupError; ``` -Defined in: [packages/db/src/errors.ts:1045](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1045) +Defined in: [packages/db/src/errors.ts:1065](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1065) #### Parameters diff --git a/docs/reference/classes/SyncTransactionAbortedError.md b/docs/reference/classes/SyncTransactionAbortedError.md index 6a05e786f9..952aaf9f86 100644 --- a/docs/reference/classes/SyncTransactionAbortedError.md +++ b/docs/reference/classes/SyncTransactionAbortedError.md @@ -3,7 +3,7 @@ id: SyncTransactionAbortedError title: SyncTransactionAbortedError --- -Defined in: [packages/db/src/errors.ts:1056](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1056) +Defined in: [packages/db/src/errors.ts:1076](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1076) A sync transaction was canceled before its writes became visible. @@ -19,7 +19,7 @@ A sync transaction was canceled before its writes became visible. new SyncTransactionAbortedError(): SyncTransactionAbortedError; ``` -Defined in: [packages/db/src/errors.ts:1057](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1057) +Defined in: [packages/db/src/errors.ts:1077](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1077) #### Returns diff --git a/docs/reference/classes/TanStackDBError.md b/docs/reference/classes/TanStackDBError.md index 48f83f56a7..03cd0abb0a 100644 --- a/docs/reference/classes/TanStackDBError.md +++ b/docs/reference/classes/TanStackDBError.md @@ -28,6 +28,7 @@ Defined in: [packages/db/src/errors.ts:4](https://github.com/TanStack/db/blob/ma - [`QueryOptimizerError`](QueryOptimizerError.md) - [`SetWindowReentrancyError`](SetWindowReentrancyError.md) - [`SourceCollectionIdReuseError`](SourceCollectionIdReuseError.md) +- [`InvalidPoolOptionError`](InvalidPoolOptionError.md) ## Constructors diff --git a/docs/reference/classes/UnhashableQueryIRError.md b/docs/reference/classes/UnhashableQueryIRError.md index e6ab2621e5..9d1cb7c815 100644 --- a/docs/reference/classes/UnhashableQueryIRError.md +++ b/docs/reference/classes/UnhashableQueryIRError.md @@ -3,7 +3,7 @@ id: UnhashableQueryIRError title: UnhashableQueryIRError --- -Defined in: [packages/db/src/errors.ts:1163](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1163) +Defined in: [packages/db/src/errors.ts:1183](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1183) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:1163](https://github.com/TanStack/db/blob new UnhashableQueryIRError(path, reason): UnhashableQueryIRError; ``` -Defined in: [packages/db/src/errors.ts:1164](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1164) +Defined in: [packages/db/src/errors.ts:1184](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1184) #### Parameters @@ -95,7 +95,7 @@ Error.name readonly path: string; ``` -Defined in: [packages/db/src/errors.ts:1165](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1165) +Defined in: [packages/db/src/errors.ts:1185](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1185) *** @@ -105,7 +105,7 @@ Defined in: [packages/db/src/errors.ts:1165](https://github.com/TanStack/db/blob readonly reason: string; ``` -Defined in: [packages/db/src/errors.ts:1166](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1166) +Defined in: [packages/db/src/errors.ts:1186](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L1186) *** diff --git a/docs/reference/classes/UnknownExpressionTypeError.md b/docs/reference/classes/UnknownExpressionTypeError.md index 5ec84e8123..598f0a73ee 100644 --- a/docs/reference/classes/UnknownExpressionTypeError.md +++ b/docs/reference/classes/UnknownExpressionTypeError.md @@ -3,7 +3,7 @@ id: UnknownExpressionTypeError title: UnknownExpressionTypeError --- -Defined in: [packages/db/src/errors.ts:821](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L821) +Defined in: [packages/db/src/errors.ts:841](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L841) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:821](https://github.com/TanStack/db/blob/ new UnknownExpressionTypeError(type): UnknownExpressionTypeError; ``` -Defined in: [packages/db/src/errors.ts:822](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L822) +Defined in: [packages/db/src/errors.ts:842](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L842) #### Parameters diff --git a/docs/reference/classes/UnknownFunctionError.md b/docs/reference/classes/UnknownFunctionError.md index eb91c7b91a..86133140b9 100644 --- a/docs/reference/classes/UnknownFunctionError.md +++ b/docs/reference/classes/UnknownFunctionError.md @@ -3,7 +3,7 @@ id: UnknownFunctionError title: UnknownFunctionError --- -Defined in: [packages/db/src/errors.ts:841](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L841) +Defined in: [packages/db/src/errors.ts:861](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L861) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:841](https://github.com/TanStack/db/blob/ new UnknownFunctionError(functionName): UnknownFunctionError; ``` -Defined in: [packages/db/src/errors.ts:842](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L842) +Defined in: [packages/db/src/errors.ts:862](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L862) #### Parameters diff --git a/docs/reference/classes/UnknownHavingExpressionTypeError.md b/docs/reference/classes/UnknownHavingExpressionTypeError.md index d8ab35a3e9..6c5c848df1 100644 --- a/docs/reference/classes/UnknownHavingExpressionTypeError.md +++ b/docs/reference/classes/UnknownHavingExpressionTypeError.md @@ -3,7 +3,7 @@ id: UnknownHavingExpressionTypeError title: UnknownHavingExpressionTypeError --- -Defined in: [packages/db/src/errors.ts:977](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L977) +Defined in: [packages/db/src/errors.ts:997](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L997) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:977](https://github.com/TanStack/db/blob/ new UnknownHavingExpressionTypeError(type): UnknownHavingExpressionTypeError; ``` -Defined in: [packages/db/src/errors.ts:978](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L978) +Defined in: [packages/db/src/errors.ts:998](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L998) #### Parameters diff --git a/docs/reference/classes/UnsafeAliasPathError.md b/docs/reference/classes/UnsafeAliasPathError.md index ce1e02fcdb..ea9f4fe62f 100644 --- a/docs/reference/classes/UnsafeAliasPathError.md +++ b/docs/reference/classes/UnsafeAliasPathError.md @@ -3,7 +3,7 @@ id: UnsafeAliasPathError title: UnsafeAliasPathError --- -Defined in: [packages/db/src/errors.ts:684](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L684) +Defined in: [packages/db/src/errors.ts:694](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L694) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:684](https://github.com/TanStack/db/blob/ new UnsafeAliasPathError(segment): UnsafeAliasPathError; ``` -Defined in: [packages/db/src/errors.ts:685](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L685) +Defined in: [packages/db/src/errors.ts:695](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L695) #### Parameters diff --git a/docs/reference/classes/UnsupportedAggregateFunctionError.md b/docs/reference/classes/UnsupportedAggregateFunctionError.md index 8173b0e2e5..0c7b4f2833 100644 --- a/docs/reference/classes/UnsupportedAggregateFunctionError.md +++ b/docs/reference/classes/UnsupportedAggregateFunctionError.md @@ -3,7 +3,7 @@ id: UnsupportedAggregateFunctionError title: UnsupportedAggregateFunctionError --- -Defined in: [packages/db/src/errors.ts:957](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L957) +Defined in: [packages/db/src/errors.ts:977](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L977) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:957](https://github.com/TanStack/db/blob/ new UnsupportedAggregateFunctionError(functionName): UnsupportedAggregateFunctionError; ``` -Defined in: [packages/db/src/errors.ts:958](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L958) +Defined in: [packages/db/src/errors.ts:978](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L978) #### Parameters diff --git a/docs/reference/classes/UnsupportedFnSelectResultError.md b/docs/reference/classes/UnsupportedFnSelectResultError.md index 5b79a8108d..0ee2f78edd 100644 --- a/docs/reference/classes/UnsupportedFnSelectResultError.md +++ b/docs/reference/classes/UnsupportedFnSelectResultError.md @@ -3,7 +3,7 @@ id: UnsupportedFnSelectResultError title: UnsupportedFnSelectResultError --- -Defined in: [packages/db/src/errors.ts:719](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L719) +Defined in: [packages/db/src/errors.ts:739](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L739) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:719](https://github.com/TanStack/db/blob/ new UnsupportedFnSelectResultError(valueDescription): UnsupportedFnSelectResultError; ``` -Defined in: [packages/db/src/errors.ts:720](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L720) +Defined in: [packages/db/src/errors.ts:740](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L740) #### Parameters diff --git a/docs/reference/classes/UnsupportedFromTypeError.md b/docs/reference/classes/UnsupportedFromTypeError.md index 2bb3716416..d97777aa20 100644 --- a/docs/reference/classes/UnsupportedFromTypeError.md +++ b/docs/reference/classes/UnsupportedFromTypeError.md @@ -3,7 +3,7 @@ id: UnsupportedFromTypeError title: UnsupportedFromTypeError --- -Defined in: [packages/db/src/errors.ts:811](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L811) +Defined in: [packages/db/src/errors.ts:831](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L831) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:811](https://github.com/TanStack/db/blob/ new UnsupportedFromTypeError(type): UnsupportedFromTypeError; ``` -Defined in: [packages/db/src/errors.ts:812](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L812) +Defined in: [packages/db/src/errors.ts:832](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L832) #### Parameters diff --git a/docs/reference/classes/UnsupportedJoinSourceTypeError.md b/docs/reference/classes/UnsupportedJoinSourceTypeError.md index f2352e4f8b..b3604c731c 100644 --- a/docs/reference/classes/UnsupportedJoinSourceTypeError.md +++ b/docs/reference/classes/UnsupportedJoinSourceTypeError.md @@ -3,7 +3,7 @@ id: UnsupportedJoinSourceTypeError title: UnsupportedJoinSourceTypeError --- -Defined in: [packages/db/src/errors.ts:929](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L929) +Defined in: [packages/db/src/errors.ts:949](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L949) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:929](https://github.com/TanStack/db/blob/ new UnsupportedJoinSourceTypeError(type): UnsupportedJoinSourceTypeError; ``` -Defined in: [packages/db/src/errors.ts:930](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L930) +Defined in: [packages/db/src/errors.ts:950](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L950) #### Parameters diff --git a/docs/reference/classes/UnsupportedJoinTypeError.md b/docs/reference/classes/UnsupportedJoinTypeError.md index fd08b7ef5d..ddb65bdc50 100644 --- a/docs/reference/classes/UnsupportedJoinTypeError.md +++ b/docs/reference/classes/UnsupportedJoinTypeError.md @@ -3,7 +3,7 @@ id: UnsupportedJoinTypeError title: UnsupportedJoinTypeError --- -Defined in: [packages/db/src/errors.ts:869](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L869) +Defined in: [packages/db/src/errors.ts:889](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L889) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:869](https://github.com/TanStack/db/blob/ new UnsupportedJoinTypeError(joinType): UnsupportedJoinTypeError; ``` -Defined in: [packages/db/src/errors.ts:870](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L870) +Defined in: [packages/db/src/errors.ts:890](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L890) #### Parameters diff --git a/docs/reference/classes/UnsupportedRootScalarSelectError.md b/docs/reference/classes/UnsupportedRootScalarSelectError.md index 02d20a9454..cc8e714fc7 100644 --- a/docs/reference/classes/UnsupportedRootScalarSelectError.md +++ b/docs/reference/classes/UnsupportedRootScalarSelectError.md @@ -3,7 +3,7 @@ id: UnsupportedRootScalarSelectError title: UnsupportedRootScalarSelectError --- -Defined in: [packages/db/src/errors.ts:731](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L731) +Defined in: [packages/db/src/errors.ts:751](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L751) ## Extends @@ -17,7 +17,7 @@ Defined in: [packages/db/src/errors.ts:731](https://github.com/TanStack/db/blob/ new UnsupportedRootScalarSelectError(): UnsupportedRootScalarSelectError; ``` -Defined in: [packages/db/src/errors.ts:732](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L732) +Defined in: [packages/db/src/errors.ts:752](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L752) #### Returns diff --git a/docs/reference/functions/add.md b/docs/reference/functions/add.md index 7b970fb86f..8438906228 100644 --- a/docs/reference/functions/add.md +++ b/docs/reference/functions/add.md @@ -7,7 +7,7 @@ title: add function add(left, right): BinaryNumericReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:618](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L618) +Defined in: [packages/db/src/query/builder/functions.ts:617](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L617) ## Type Parameters diff --git a/docs/reference/functions/and.md b/docs/reference/functions/and.md index f76163a8dd..74e1f61574 100644 --- a/docs/reference/functions/and.md +++ b/docs/reference/functions/and.md @@ -9,7 +9,7 @@ title: and function and(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:210](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L210) +Defined in: [packages/db/src/query/builder/functions.ts:207](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L207) ### Parameters @@ -34,7 +34,7 @@ function and( rest): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:214](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L214) +Defined in: [packages/db/src/query/builder/functions.ts:211](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L211) ### Parameters diff --git a/docs/reference/functions/avg.md b/docs/reference/functions/avg.md index 0063449b87..3eafd3d5c2 100644 --- a/docs/reference/functions/avg.md +++ b/docs/reference/functions/avg.md @@ -9,7 +9,7 @@ title: avg function avg(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:664](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L664) +Defined in: [packages/db/src/query/builder/functions.ts:663](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L663) ### Type Parameters @@ -33,7 +33,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:664](https://github.com/ function avg(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:665](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L665) +Defined in: [packages/db/src/query/builder/functions.ts:664](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L664) ### Type Parameters @@ -57,7 +57,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:665](https://github.com/ function avg(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:668](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L668) +Defined in: [packages/db/src/query/builder/functions.ts:667](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L667) ### Type Parameters diff --git a/docs/reference/functions/caseWhen.md b/docs/reference/functions/caseWhen.md index 1d3d5b7ac7..607c3dc771 100644 --- a/docs/reference/functions/caseWhen.md +++ b/docs/reference/functions/caseWhen.md @@ -9,7 +9,7 @@ title: caseWhen function caseWhen(condition1, value1): CaseWhenResult<[V1], false>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:408](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L408) +Defined in: [packages/db/src/query/builder/functions.ts:407](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L407) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -68,9 +68,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -83,7 +85,7 @@ function caseWhen( defaultValue): CaseWhenResult<[V1, D], true>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:412](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L412) +Defined in: [packages/db/src/query/builder/functions.ts:411](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L411) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -150,9 +152,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -166,7 +170,7 @@ function caseWhen( value2): CaseWhenResult<[V1, V2], false>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:417](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L417) +Defined in: [packages/db/src/query/builder/functions.ts:416](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L416) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -241,9 +245,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -258,7 +264,7 @@ function caseWhen( defaultValue): CaseWhenResult<[V1, V2, D], true>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:428](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L428) +Defined in: [packages/db/src/query/builder/functions.ts:427](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L427) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -341,9 +347,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -359,7 +367,7 @@ function caseWhen( value3): CaseWhenResult<[V1, V2, V3], false>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:441](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L441) +Defined in: [packages/db/src/query/builder/functions.ts:440](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L440) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -450,9 +458,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -469,7 +479,7 @@ function caseWhen( defaultValue): CaseWhenResult<[V1, V2, V3, D], true>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:456](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L456) +Defined in: [packages/db/src/query/builder/functions.ts:455](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L455) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -568,9 +578,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -588,7 +600,7 @@ function caseWhen( value4): CaseWhenResult<[V1, V2, V3, V4], false>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:473](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L473) +Defined in: [packages/db/src/query/builder/functions.ts:472](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L472) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -695,9 +707,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -716,7 +730,7 @@ function caseWhen( defaultValue): CaseWhenResult<[V1, V2, V3, V4, D], true>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:492](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L492) +Defined in: [packages/db/src/query/builder/functions.ts:491](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L491) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -831,9 +845,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -853,7 +869,7 @@ function caseWhen( value5): CaseWhenResult<[V1, V2, V3, V4, V5], false>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:513](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L513) +Defined in: [packages/db/src/query/builder/functions.ts:512](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L512) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -976,9 +992,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -999,7 +1017,7 @@ function caseWhen( defaultValue): CaseWhenResult<[V1, V2, V3, V4, V5, D], true>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:536](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L536) +Defined in: [packages/db/src/query/builder/functions.ts:535](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L535) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -1130,9 +1148,11 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` @@ -1155,7 +1175,7 @@ function caseWhen( rest): any; ``` -Defined in: [packages/db/src/query/builder/functions.ts:561](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L561) +Defined in: [packages/db/src/query/builder/functions.ts:560](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L560) Returns the value for the first matching condition, similar to SQL `CASE WHEN`. @@ -1290,8 +1310,10 @@ caseWhen( ```ts caseWhen(gt(user.age, 18), { ...user, - posts: q - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)), + posts: include( + q + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), }) ``` diff --git a/docs/reference/functions/claimLiveQuerySourceIds.md b/docs/reference/functions/claimLiveQuerySourceIds.md index 46b1c0190a..88fa2117cb 100644 --- a/docs/reference/functions/claimLiveQuerySourceIds.md +++ b/docs/reference/functions/claimLiveQuerySourceIds.md @@ -11,7 +11,7 @@ function claimLiveQuerySourceIds( bindingName): void; ``` -Defined in: [packages/db/src/live-query-adapter.ts:97](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L97) +Defined in: [packages/db/src/live-query-adapter.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L99) Record a query's sources, or throw before recording any. diff --git a/docs/reference/functions/coalesce.md b/docs/reference/functions/coalesce.md index 5b7ba034df..88aa7011fe 100644 --- a/docs/reference/functions/coalesce.md +++ b/docs/reference/functions/coalesce.md @@ -7,7 +7,7 @@ title: coalesce function coalesce(...args): CoalesceReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:358](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L358) +Defined in: [packages/db/src/query/builder/functions.ts:355](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L355) ## Type Parameters diff --git a/docs/reference/functions/compileExpression.md b/docs/reference/functions/compileExpression.md index fb7e934830..86f62d4310 100644 --- a/docs/reference/functions/compileExpression.md +++ b/docs/reference/functions/compileExpression.md @@ -4,7 +4,7 @@ title: compileExpression --- ```ts -function compileExpression(expr, isSingleRow): CompiledSingleRowExpression | CompiledExpression; +function compileExpression(expr, isSingleRow): CompiledExpression | CompiledSingleRowExpression; ``` Defined in: [packages/db/src/query/compiler/evaluators.ts:103](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/evaluators.ts#L103) @@ -24,4 +24,4 @@ This eliminates branching during evaluation by pre-compiling the expression stru ## Returns -`CompiledSingleRowExpression` \| `CompiledExpression` +`CompiledExpression` \| `CompiledSingleRowExpression` diff --git a/docs/reference/functions/compileQuery.md b/docs/reference/functions/compileQuery.md index 4e0bb08a2a..e2de5f3659 100644 --- a/docs/reference/functions/compileQuery.md +++ b/docs/reference/functions/compileQuery.md @@ -7,7 +7,7 @@ title: compileQuery function compileQuery(...args): CompilationResult; ``` -Defined in: [packages/db/src/query/compiler/index.ts:405](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/index.ts#L405) +Defined in: [packages/db/src/query/compiler/index.ts:411](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/index.ts#L411) ## Parameters diff --git a/docs/reference/functions/concat.md b/docs/reference/functions/concat.md index 7716a9cd0a..602f877c8d 100644 --- a/docs/reference/functions/concat.md +++ b/docs/reference/functions/concat.md @@ -9,7 +9,7 @@ title: concat function concat(arg): ConcatToArrayWrapper; ``` -Defined in: [packages/db/src/query/builder/functions.ts:308](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L308) +Defined in: [packages/db/src/query/builder/functions.ts:305](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L305) ### Type Parameters @@ -33,7 +33,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:308](https://github.com/ function concat(...args): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:311](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L311) +Defined in: [packages/db/src/query/builder/functions.ts:308](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L308) ### Parameters diff --git a/docs/reference/functions/configPool.md b/docs/reference/functions/configPool.md new file mode 100644 index 0000000000..744a046a33 --- /dev/null +++ b/docs/reference/functions/configPool.md @@ -0,0 +1,89 @@ +--- +id: configPool +title: configPool +--- + +```ts +function configPool(config): + | { + createPooledLiveQuery: (query, __namedParameters) => + | Collection, any> + | undefined; + getPooledQueryIdentity: (query) => string | undefined; +} + | undefined; +``` + +Defined in: [packages/db/src/live-query-options.ts:34](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L34) + +A hook config's pool: `pooledLiveQueries` or nothing. Any other value, such +as the removed boolean form, contradicts the hook contract, so every reader +rejects it the same way, whatever the config's other options. An adapter +that reads the pool from its own options, as Angular does, checks it here. + +## Parameters + +### config + +`object` + +## Returns + +```ts +{ + createPooledLiveQuery: (query, __namedParameters) => + | Collection, any> + | undefined; + getPooledQueryIdentity: (query) => string | undefined; +} +``` + +### createPooledLiveQuery() + +```ts +createPooledLiveQuery: (query, __namedParameters) => + | Collection, any> + | undefined; +``` + +A pooled view for a query a partition can serve, or undefined. The view is +typed as the Collection it stands in for. + +#### Parameters + +##### query + +[`BaseQueryBuilder`](../classes/BaseQueryBuilder.md) + +##### \_\_namedParameters + +###### gcTime? + +`number` = `5_000` + +#### Returns + + \| [`Collection`](../interfaces/Collection.md)\<`any`, `any`, `any`, `StandardSchemaV1`\<`unknown`, `unknown`\>, `any`\> + \| `undefined` + +### getPooledQueryIdentity() + +```ts +getPooledQueryIdentity: (query) => string | undefined; +``` + +The identity of a query a partition can serve with no residual conjunct: +its source and its `eq` fields and literals, which determine its rows. +Undefined for any other query, which keeps the full structural identity. + +#### Parameters + +##### query + +[`BaseQueryBuilder`](../classes/BaseQueryBuilder.md) + +#### Returns + +`string` \| `undefined` + +`undefined` diff --git a/docs/reference/functions/count.md b/docs/reference/functions/count.md index e7d6334203..bf29d462b9 100644 --- a/docs/reference/functions/count.md +++ b/docs/reference/functions/count.md @@ -7,7 +7,7 @@ title: count function count(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:660](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L660) +Defined in: [packages/db/src/query/builder/functions.ts:659](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L659) ## Parameters diff --git a/docs/reference/functions/createLiveQuerySourceIdClaims.md b/docs/reference/functions/createLiveQuerySourceIdClaims.md index b08eb16cab..784cd91146 100644 --- a/docs/reference/functions/createLiveQuerySourceIdClaims.md +++ b/docs/reference/functions/createLiveQuerySourceIdClaims.md @@ -7,7 +7,7 @@ title: createLiveQuerySourceIdClaims function createLiveQuerySourceIdClaims(): LiveQuerySourceIdClaims; ``` -Defined in: [packages/db/src/live-query-adapter.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L92) +Defined in: [packages/db/src/live-query-adapter.ts:94](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L94) ## Returns diff --git a/docs/reference/functions/divide.md b/docs/reference/functions/divide.md index 3089398a6b..5ebebab1dd 100644 --- a/docs/reference/functions/divide.md +++ b/docs/reference/functions/divide.md @@ -7,7 +7,7 @@ title: divide function divide(left, right): DivideReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:648](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L648) +Defined in: [packages/db/src/query/builder/functions.ts:647](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L647) ## Type Parameters diff --git a/docs/reference/functions/eq.md b/docs/reference/functions/eq.md index 2c9aec380a..bf6cb6a09b 100644 --- a/docs/reference/functions/eq.md +++ b/docs/reference/functions/eq.md @@ -9,7 +9,7 @@ title: eq function eq(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:144](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L144) +Defined in: [packages/db/src/query/builder/functions.ts:141](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L141) ### Type Parameters @@ -37,7 +37,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:144](https://github.com/ function eq(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:148](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L148) +Defined in: [packages/db/src/query/builder/functions.ts:145](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L145) ### Type Parameters @@ -65,7 +65,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:148](https://github.com/ function eq(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:152](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L152) +Defined in: [packages/db/src/query/builder/functions.ts:149](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L149) ### Type Parameters diff --git a/docs/reference/functions/getLiveQueryHash.md b/docs/reference/functions/getLiveQueryHash.md index 28afe0c078..e7dd7ed242 100644 --- a/docs/reference/functions/getLiveQueryHash.md +++ b/docs/reference/functions/getLiveQueryHash.md @@ -7,7 +7,7 @@ title: getLiveQueryHash function getLiveQueryHash(preparedValue, queryKey?): string; ``` -Defined in: [packages/db/src/live-query-options.ts:215](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L215) +Defined in: [packages/db/src/live-query-options.ts:281](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L281) ## Parameters diff --git a/docs/reference/functions/getLiveQueryKey.md b/docs/reference/functions/getLiveQueryKey.md index 78059f9f51..fb6498f09f 100644 --- a/docs/reference/functions/getLiveQueryKey.md +++ b/docs/reference/functions/getLiveQueryKey.md @@ -7,7 +7,7 @@ title: getLiveQueryKey function getLiveQueryKey(input): LiveQueryKey | undefined; ``` -Defined in: [packages/db/src/live-query-adapter.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L49) +Defined in: [packages/db/src/live-query-adapter.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L51) The `queryKey` of a config-object query input, if it supplies one. An empty key is absent, as it is for `getLiveQueryHash`, so a hook and a `DbClient` diff --git a/docs/reference/functions/getPreparedLiveQueryIdentity.md b/docs/reference/functions/getPreparedLiveQueryIdentity.md index d1056ac751..78091a0efe 100644 --- a/docs/reference/functions/getPreparedLiveQueryIdentity.md +++ b/docs/reference/functions/getPreparedLiveQueryIdentity.md @@ -4,10 +4,10 @@ title: getPreparedLiveQueryIdentity --- ```ts -function getPreparedLiveQueryIdentity(value): unknown; +function getPreparedLiveQueryIdentity(value, pool?): unknown; ``` -Defined in: [packages/db/src/live-query-options.ts:187](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L187) +Defined in: [packages/db/src/live-query-options.ts:250](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L250) ## Parameters @@ -15,6 +15,18 @@ Defined in: [packages/db/src/live-query-options.ts:187](https://github.com/TanSt `unknown` +### pool? + +#### createPooledLiveQuery + +(`query`, `__namedParameters`) => + \| [`Collection`](../interfaces/Collection.md)\<`any`, `any`, `any`, `StandardSchemaV1`\<`unknown`, `unknown`\>, `any`\> + \| `undefined` + +#### getPooledQueryIdentity + +(`query`) => `string` \| `undefined` + ## Returns `unknown` diff --git a/docs/reference/functions/getPreparedLiveQuerySources.md b/docs/reference/functions/getPreparedLiveQuerySources.md index e19f484b05..090654d39a 100644 --- a/docs/reference/functions/getPreparedLiveQuerySources.md +++ b/docs/reference/functions/getPreparedLiveQuerySources.md @@ -7,7 +7,7 @@ title: getPreparedLiveQuerySources function getPreparedLiveQuerySources(value, onUnboundDescriptor?): Collection, string | number, UtilsRecord, StandardSchemaV1, Record>[]; ``` -Defined in: [packages/db/src/live-query-options.ts:164](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L164) +Defined in: [packages/db/src/live-query-options.ts:227](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L227) Concrete source objects in query-position order for a prepared value. diff --git a/docs/reference/functions/getSourceObjectToken.md b/docs/reference/functions/getSourceObjectToken.md index 5d38601999..1c76f1eb18 100644 --- a/docs/reference/functions/getSourceObjectToken.md +++ b/docs/reference/functions/getSourceObjectToken.md @@ -7,7 +7,7 @@ title: getSourceObjectToken function getSourceObjectToken(source): number; ``` -Defined in: [packages/db/src/live-query-adapter.ts:74](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L74) +Defined in: [packages/db/src/live-query-adapter.ts:76](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L76) A process-unique number for a source object, without retaining it. diff --git a/docs/reference/functions/groupKeys.md b/docs/reference/functions/groupKeys.md new file mode 100644 index 0000000000..cd36b220f8 --- /dev/null +++ b/docs/reference/functions/groupKeys.md @@ -0,0 +1,26 @@ +--- +id: groupKeys +title: groupKeys +--- + +```ts +function groupKeys(callback): GroupKeys; +``` + +Defined in: [packages/db/src/query/builder/group-keys.ts:14](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/group-keys.ts#L14) + +## Type Parameters + +### TContext + +`TContext` *extends* [`Context`](../interfaces/Context.md) = `any` + +## Parameters + +### callback + +[`OperatorCallback`](../type-aliases/OperatorCallback.md)\<`TContext`, [`GroupByCallback`](../type-aliases/GroupByCallback.md)\<`TContext`\>\> + +## Returns + +[`GroupKeys`](../interfaces/GroupKeys.md)\<`TContext`\> diff --git a/docs/reference/functions/gt.md b/docs/reference/functions/gt.md index e897c23f02..a49bb3bb73 100644 --- a/docs/reference/functions/gt.md +++ b/docs/reference/functions/gt.md @@ -9,7 +9,7 @@ title: gt function gt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L157) +Defined in: [packages/db/src/query/builder/functions.ts:154](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L154) ### Type Parameters @@ -37,7 +37,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:157](https://github.com/ function gt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:161](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L161) +Defined in: [packages/db/src/query/builder/functions.ts:158](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L158) ### Type Parameters @@ -65,7 +65,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:161](https://github.com/ function gt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:165](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L165) +Defined in: [packages/db/src/query/builder/functions.ts:162](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L162) ### Type Parameters diff --git a/docs/reference/functions/gte.md b/docs/reference/functions/gte.md index 9c62f339dd..56d40d5158 100644 --- a/docs/reference/functions/gte.md +++ b/docs/reference/functions/gte.md @@ -9,7 +9,7 @@ title: gte function gte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:170](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L170) +Defined in: [packages/db/src/query/builder/functions.ts:167](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L167) ### Type Parameters @@ -37,7 +37,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:170](https://github.com/ function gte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:174](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L174) +Defined in: [packages/db/src/query/builder/functions.ts:171](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L171) ### Type Parameters @@ -65,7 +65,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:174](https://github.com/ function gte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:178](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L178) +Defined in: [packages/db/src/query/builder/functions.ts:175](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L175) ### Type Parameters diff --git a/docs/reference/functions/identifyLiveQuery.md b/docs/reference/functions/identifyLiveQuery.md index 37bb2e87a8..eae8b23a31 100644 --- a/docs/reference/functions/identifyLiveQuery.md +++ b/docs/reference/functions/identifyLiveQuery.md @@ -10,7 +10,7 @@ function identifyLiveQuery( identify): TIdentity; ``` -Defined in: [packages/db/src/live-query-adapter.ts:136](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L136) +Defined in: [packages/db/src/live-query-adapter.ts:138](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L138) Identify a prepared query for a binding that compares query identity. The identity throws `UnhashableQueryIRError` for IR without a stable identity, diff --git a/docs/reference/functions/ilike.md b/docs/reference/functions/ilike.md index a23f1329c3..724e48d7b3 100644 --- a/docs/reference/functions/ilike.md +++ b/docs/reference/functions/ilike.md @@ -7,7 +7,7 @@ title: ilike function ilike(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:281](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L281) +Defined in: [packages/db/src/query/builder/functions.ts:278](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L278) ## Parameters diff --git a/docs/reference/functions/inArray.md b/docs/reference/functions/inArray.md index 1c43b1bffe..c7d1f27535 100644 --- a/docs/reference/functions/inArray.md +++ b/docs/reference/functions/inArray.md @@ -7,7 +7,7 @@ title: inArray function inArray(value, array): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:266](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L266) +Defined in: [packages/db/src/query/builder/functions.ts:263](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L263) ## Parameters diff --git a/docs/reference/functions/include.md b/docs/reference/functions/include.md new file mode 100644 index 0000000000..aa79e84936 --- /dev/null +++ b/docs/reference/functions/include.md @@ -0,0 +1,40 @@ +--- +id: include +title: include +--- + +```ts +function include(query): IncludeWrapper; +``` + +Defined in: [packages/db/src/query/builder/include-functions.ts:74](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/include-functions.ts#L74) + +Includes a correlated subquery as a live child Collection on each parent +row. Use `toArray()` or `materialize()` for inline values instead. + +## Type Parameters + +### TContext + +`TContext` *extends* [`Context`](../interfaces/Context.md) + +## Parameters + +### query + +[`QueryBuilder`](../type-aliases/QueryBuilder.md)\<`TContext`\> + +## Returns + +`IncludeWrapper`\<`TContext`\> + +## Example + +```ts +select(({ p }) => ({ + ...p, + issues: include( + q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + ), +})) +``` diff --git a/docs/reference/functions/isNull.md b/docs/reference/functions/isNull.md index 55127b6509..bf463bf3e1 100644 --- a/docs/reference/functions/isNull.md +++ b/docs/reference/functions/isNull.md @@ -7,7 +7,7 @@ title: isNull function isNull(value): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:262](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L262) +Defined in: [packages/db/src/query/builder/functions.ts:259](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L259) ## Parameters diff --git a/docs/reference/functions/isUndefined.md b/docs/reference/functions/isUndefined.md index 71b4aa88da..8373acbad6 100644 --- a/docs/reference/functions/isUndefined.md +++ b/docs/reference/functions/isUndefined.md @@ -7,7 +7,7 @@ title: isUndefined function isUndefined(value): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:258](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L258) +Defined in: [packages/db/src/query/builder/functions.ts:255](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L255) ## Parameters diff --git a/docs/reference/functions/length.md b/docs/reference/functions/length.md index 71c924bb5f..91e11a5e03 100644 --- a/docs/reference/functions/length.md +++ b/docs/reference/functions/length.md @@ -7,7 +7,7 @@ title: length function length(arg): NumericFunctionReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:302](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L302) +Defined in: [packages/db/src/query/builder/functions.ts:299](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L299) ## Type Parameters diff --git a/docs/reference/functions/like.md b/docs/reference/functions/like.md index 94ec7396e1..9cb1feb472 100644 --- a/docs/reference/functions/like.md +++ b/docs/reference/functions/like.md @@ -7,7 +7,7 @@ title: like function like(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:273](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L273) +Defined in: [packages/db/src/query/builder/functions.ts:270](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L270) ## Parameters diff --git a/docs/reference/functions/lower.md b/docs/reference/functions/lower.md index f2e1653fb6..d7cd7d45f4 100644 --- a/docs/reference/functions/lower.md +++ b/docs/reference/functions/lower.md @@ -7,7 +7,7 @@ title: lower function lower(arg): StringFunctionReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:296](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L296) +Defined in: [packages/db/src/query/builder/functions.ts:293](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L293) ## Type Parameters diff --git a/docs/reference/functions/lt.md b/docs/reference/functions/lt.md index 682ec84fad..5f7ccdcb35 100644 --- a/docs/reference/functions/lt.md +++ b/docs/reference/functions/lt.md @@ -9,7 +9,7 @@ title: lt function lt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:183](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L183) +Defined in: [packages/db/src/query/builder/functions.ts:180](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L180) ### Type Parameters @@ -37,7 +37,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:183](https://github.com/ function lt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:187](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L187) +Defined in: [packages/db/src/query/builder/functions.ts:184](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L184) ### Type Parameters @@ -65,7 +65,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:187](https://github.com/ function lt(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:191](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L191) +Defined in: [packages/db/src/query/builder/functions.ts:188](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L188) ### Type Parameters diff --git a/docs/reference/functions/lte.md b/docs/reference/functions/lte.md index 77065b21c4..642306d6a3 100644 --- a/docs/reference/functions/lte.md +++ b/docs/reference/functions/lte.md @@ -9,7 +9,7 @@ title: lte function lte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L196) +Defined in: [packages/db/src/query/builder/functions.ts:193](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L193) ### Type Parameters @@ -37,7 +37,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:196](https://github.com/ function lte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:200](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L200) +Defined in: [packages/db/src/query/builder/functions.ts:197](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L197) ### Type Parameters @@ -65,7 +65,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:200](https://github.com/ function lte(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:204](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L204) +Defined in: [packages/db/src/query/builder/functions.ts:201](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L201) ### Type Parameters diff --git a/docs/reference/functions/materialize.md b/docs/reference/functions/materialize.md index 0bcde08a7d..d505420a4f 100644 --- a/docs/reference/functions/materialize.md +++ b/docs/reference/functions/materialize.md @@ -7,7 +7,7 @@ title: materialize function materialize(query): MaterializeWrapper, TContext extends SingleResult ? true : false>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:882](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L882) +Defined in: [packages/db/src/query/builder/include-functions.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/include-functions.ts#L51) Materialize an includes subquery into a plain value on the parent row. diff --git a/docs/reference/functions/max.md b/docs/reference/functions/max.md index 2f2ee4578b..7de95c31b8 100644 --- a/docs/reference/functions/max.md +++ b/docs/reference/functions/max.md @@ -9,7 +9,7 @@ title: max function max(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:695](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L695) +Defined in: [packages/db/src/query/builder/functions.ts:694](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L694) ### Type Parameters @@ -33,7 +33,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:695](https://github.com/ function max(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:696](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L696) +Defined in: [packages/db/src/query/builder/functions.ts:695](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L695) ### Type Parameters @@ -57,7 +57,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:696](https://github.com/ function max(arg): Aggregate>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:697](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L697) +Defined in: [packages/db/src/query/builder/functions.ts:696](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L696) ### Type Parameters diff --git a/docs/reference/functions/min.md b/docs/reference/functions/min.md index f707c512fe..ba8050e06d 100644 --- a/docs/reference/functions/min.md +++ b/docs/reference/functions/min.md @@ -9,7 +9,7 @@ title: min function min(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:686](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L686) +Defined in: [packages/db/src/query/builder/functions.ts:685](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L685) ### Type Parameters @@ -33,7 +33,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:686](https://github.com/ function min(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:687](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L687) +Defined in: [packages/db/src/query/builder/functions.ts:686](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L686) ### Type Parameters @@ -57,7 +57,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:687](https://github.com/ function min(arg): Aggregate>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:688](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L688) +Defined in: [packages/db/src/query/builder/functions.ts:687](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L687) ### Type Parameters diff --git a/docs/reference/functions/multiply.md b/docs/reference/functions/multiply.md index ac9cf6c98b..6408bcdd27 100644 --- a/docs/reference/functions/multiply.md +++ b/docs/reference/functions/multiply.md @@ -7,7 +7,7 @@ title: multiply function multiply(left, right): BinaryNumericReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:638](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L638) +Defined in: [packages/db/src/query/builder/functions.ts:637](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L637) ## Type Parameters diff --git a/docs/reference/functions/not.md b/docs/reference/functions/not.md index d1a7683025..1100b8f958 100644 --- a/docs/reference/functions/not.md +++ b/docs/reference/functions/not.md @@ -7,7 +7,7 @@ title: not function not(value): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:253](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L253) +Defined in: [packages/db/src/query/builder/functions.ts:250](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L250) ## Parameters diff --git a/docs/reference/functions/or.md b/docs/reference/functions/or.md index fa499aa89f..65206480c0 100644 --- a/docs/reference/functions/or.md +++ b/docs/reference/functions/or.md @@ -9,7 +9,7 @@ title: or function or(left, right): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:232](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L232) +Defined in: [packages/db/src/query/builder/functions.ts:229](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L229) ### Parameters @@ -34,7 +34,7 @@ function or( rest): BasicExpression; ``` -Defined in: [packages/db/src/query/builder/functions.ts:236](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L236) +Defined in: [packages/db/src/query/builder/functions.ts:233](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L233) ### Parameters diff --git a/docs/reference/functions/prepareLiveQueryValue.md b/docs/reference/functions/prepareLiveQueryValue.md index 1c4db57b15..5bfc132aa6 100644 --- a/docs/reference/functions/prepareLiveQueryValue.md +++ b/docs/reference/functions/prepareLiveQueryValue.md @@ -10,7 +10,7 @@ function prepareLiveQueryValue( deferredCollections?): unknown; ``` -Defined in: [packages/db/src/live-query-options.ts:111](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L111) +Defined in: [packages/db/src/live-query-options.ts:169](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L169) ## Parameters diff --git a/docs/reference/functions/rejectLiveQueryInput.md b/docs/reference/functions/rejectLiveQueryInput.md index fb6e3f1447..94a4356524 100644 --- a/docs/reference/functions/rejectLiveQueryInput.md +++ b/docs/reference/functions/rejectLiveQueryInput.md @@ -7,14 +7,15 @@ title: rejectLiveQueryInput function rejectLiveQueryInput(collections, rejection): never; ``` -Defined in: [packages/db/src/live-query-options.ts:45](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L45) +Defined in: [packages/db/src/live-query-options.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L66) Release the sync starts a rejected query input deferred, then rethrow its rejection. A framework hook calls this around its own render work, so a query the hook rejects leaves no source deferred and a later reader can start it. A render that throws after the hook returns, such as in a sibling hook, is not a rejection of this input: like any render that never commits, -its deferral stays until a `preload()` starts the source. As for a +it releases its deferral through `releaseDeferredOnReclaim` when garbage +collection reclaims its live query. As for a `DbClient` preload, the rejection wins over a source whose resumed start fails; that source records its own failure. diff --git a/docs/reference/functions/releaseDeferredOnReclaim.md b/docs/reference/functions/releaseDeferredOnReclaim.md new file mode 100644 index 0000000000..b6f0de76ce --- /dev/null +++ b/docs/reference/functions/releaseDeferredOnReclaim.md @@ -0,0 +1,37 @@ +--- +id: releaseDeferredOnReclaim +title: releaseDeferredOnReclaim +--- + +```ts +function releaseDeferredOnReclaim(liveQuery, deferredCollections): () => void | undefined; +``` + +Defined in: [packages/db/src/live-query-options.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L92) + +Release a render's deferred sync starts when the live query it built is +reclaimed. A render that commits releases them first, when it subscribes. +A render that never commits, because a sibling threw or it suspended, leaves +its live query unsubscribed until garbage collection cleans it up; that +cleanup releases the starts, so a direct reader's demand, made before or +after, then starts the sources. A live query that garbage collection never +reclaims, because its config disables GC or does not start its sync, +releases after the unsubscribed GC floor instead. Releases only the starts +deferred so far, not ones a later render of the same hook defers. Pass only +a live query the render built, and call the returned function when the +render commits, so a committed hook keeps no listener, timer, or copied +sources. + +## Parameters + +### liveQuery + +`Pick`\<[`Collection`](../interfaces/Collection.md)\<`any`, `any`, `any`\>, `"once"` \| `"config"` \| `"status"`\> + +### deferredCollections + +[`DeferredLiveQueryCollections`](../type-aliases/DeferredLiveQueryCollections.md) + +## Returns + +() => `void` \| `undefined` diff --git a/docs/reference/functions/resolveLiveQueryValue.md b/docs/reference/functions/resolveLiveQueryValue.md index 13e83377bf..dcfd987302 100644 --- a/docs/reference/functions/resolveLiveQueryValue.md +++ b/docs/reference/functions/resolveLiveQueryValue.md @@ -9,7 +9,7 @@ function resolveLiveQueryValue(value, __namedParameters): | null; ``` -Defined in: [packages/db/src/live-query-options.ts:251](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L251) +Defined in: [packages/db/src/live-query-options.ts:318](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L318) Resolve an adapter's query value to what its observer watches: `null` for a disabled query, an existing Collection with sync started, or a live query. @@ -30,7 +30,13 @@ instead of its own live-query Collection. #### pool? -`boolean` = `true` + \| `false` + \| \{ + `createPooledLiveQuery`: (`query`, `__namedParameters`) => + \| [`Collection`](../interfaces/Collection.md)\<`any`, `any`, `any`, `StandardSchemaV1`\<`unknown`, `unknown`\>, `any`\> + \| `undefined`; + `getPooledQueryIdentity`: (`query`) => `string` \| `undefined`; +\} #### startSync? diff --git a/docs/reference/functions/resumeDeferredLiveQueryCollections.md b/docs/reference/functions/resumeDeferredLiveQueryCollections.md index 62e39a0b27..178419b040 100644 --- a/docs/reference/functions/resumeDeferredLiveQueryCollections.md +++ b/docs/reference/functions/resumeDeferredLiveQueryCollections.md @@ -7,7 +7,7 @@ title: resumeDeferredLiveQueryCollections function resumeDeferredLiveQueryCollections(collections): void; ``` -Defined in: [packages/db/src/live-query-options.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L58) +Defined in: [packages/db/src/live-query-options.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L116) Release every deferred source, then report the first startup failure. diff --git a/docs/reference/functions/subtract.md b/docs/reference/functions/subtract.md index 166f88eacf..07d4ea7604 100644 --- a/docs/reference/functions/subtract.md +++ b/docs/reference/functions/subtract.md @@ -7,7 +7,7 @@ title: subtract function subtract(left, right): BinaryNumericReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:628](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L628) +Defined in: [packages/db/src/query/builder/functions.ts:627](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L627) ## Type Parameters diff --git a/docs/reference/functions/sum.md b/docs/reference/functions/sum.md index 3ae3595f8f..bdffa67f26 100644 --- a/docs/reference/functions/sum.md +++ b/docs/reference/functions/sum.md @@ -9,7 +9,7 @@ title: sum function sum(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:675](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L675) +Defined in: [packages/db/src/query/builder/functions.ts:674](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L674) ### Type Parameters @@ -33,7 +33,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:675](https://github.com/ function sum(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:676](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L676) +Defined in: [packages/db/src/query/builder/functions.ts:675](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L675) ### Type Parameters @@ -57,7 +57,7 @@ Defined in: [packages/db/src/query/builder/functions.ts:676](https://github.com/ function sum(arg): Aggregate; ``` -Defined in: [packages/db/src/query/builder/functions.ts:679](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L679) +Defined in: [packages/db/src/query/builder/functions.ts:678](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L678) ### Type Parameters diff --git a/docs/reference/functions/toArray.md b/docs/reference/functions/toArray.md index 009f0d310d..c1e08d6d18 100644 --- a/docs/reference/functions/toArray.md +++ b/docs/reference/functions/toArray.md @@ -7,7 +7,7 @@ title: toArray function toArray(query): ToArrayWrapper>; ``` -Defined in: [packages/db/src/query/builder/functions.ts:805](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L805) +Defined in: [packages/db/src/query/builder/include-functions.ts:15](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/include-functions.ts#L15) ## Type Parameters diff --git a/docs/reference/functions/upper.md b/docs/reference/functions/upper.md index cd06a71664..5064c1f6dc 100644 --- a/docs/reference/functions/upper.md +++ b/docs/reference/functions/upper.md @@ -7,7 +7,7 @@ title: upper function upper(arg): StringFunctionReturnType; ``` -Defined in: [packages/db/src/query/builder/functions.ts:290](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L290) +Defined in: [packages/db/src/query/builder/functions.ts:287](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L287) ## Type Parameters diff --git a/docs/reference/index.md b/docs/reference/index.md index 3dd1474dbd..da78b354f4 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -40,7 +40,9 @@ title: "@tanstack/db" - [FnSelectWithGroupByError](classes/FnSelectWithGroupByError.md) - [GetKeyRequiredError](classes/GetKeyRequiredError.md) - [GroupByError](classes/GroupByError.md) +- [GroupingOperatorRequiredError](classes/GroupingOperatorRequiredError.md) - [HavingRequiresGroupByError](classes/HavingRequiresGroupByError.md) +- [IncludeOperatorRequiredError](classes/IncludeOperatorRequiredError.md) - [InvalidCollectionStatusTransitionError](classes/InvalidCollectionStatusTransitionError.md) - [InvalidJoinCondition](classes/InvalidJoinCondition.md) - [InvalidJoinConditionLeftSourceError](classes/InvalidJoinConditionLeftSourceError.md) @@ -48,6 +50,7 @@ title: "@tanstack/db" - [InvalidJoinConditionSameSourceError](classes/InvalidJoinConditionSameSourceError.md) - [InvalidJoinConditionSourceMismatchError](classes/InvalidJoinConditionSourceMismatchError.md) - [InvalidKeyError](classes/InvalidKeyError.md) +- [InvalidPoolOptionError](classes/InvalidPoolOptionError.md) - [InvalidSchemaError](classes/InvalidSchemaError.md) - [InvalidSourceError](classes/InvalidSourceError.md) - [InvalidSourceTypeError](classes/InvalidSourceTypeError.md) @@ -149,6 +152,7 @@ title: "@tanstack/db" - [Effect](interfaces/Effect.md) - [EffectConfig](interfaces/EffectConfig.md) - [EffectContext](interfaces/EffectContext.md) +- [GroupKeys](interfaces/GroupKeys.md) - [IndexDevModeConfig](interfaces/IndexDevModeConfig.md) - [IndexedDBCollectionConfig](interfaces/IndexedDBCollectionConfig.md) - [IndexedDBCollectionUtils](interfaces/IndexedDBCollectionUtils.md) @@ -243,6 +247,8 @@ title: "@tanstack/db" - [GetResult](type-aliases/GetResult.md) - [GetStorageSizeFn](type-aliases/GetStorageSizeFn.md) - [GroupByCallback](type-aliases/GroupByCallback.md) +- [Grouping](type-aliases/Grouping.md) +- [IncludesRuntime](type-aliases/IncludesRuntime.md) - [IndexConstructor](type-aliases/IndexConstructor.md) - [IndexOperation](type-aliases/IndexOperation.md) - [IndexOperation](type-aliases/IndexOperation-1.md) @@ -259,6 +265,7 @@ title: "@tanstack/db" - [KeyedNamespacedRow](type-aliases/KeyedNamespacedRow.md) - [KeyedStream](type-aliases/KeyedStream.md) - [LiveQueryCollectionUtils](type-aliases/LiveQueryCollectionUtils.md) +- [LiveQueryHookConfig](type-aliases/LiveQueryHookConfig.md) - [LiveQueryKey](type-aliases/LiveQueryKey.md) - [LiveQueryObserverListener](type-aliases/LiveQueryObserverListener.md) - [LiveQueryOptions](type-aliases/LiveQueryOptions.md) @@ -279,10 +286,12 @@ title: "@tanstack/db" - [NonEmptyArray](type-aliases/NonEmptyArray.md) - [NonSingleResult](type-aliases/NonSingleResult.md) - [OperationType](type-aliases/OperationType.md) +- [OperatorCallback](type-aliases/OperatorCallback.md) - [OperatorName](type-aliases/OperatorName.md) - [OptimisticChangeMessage](type-aliases/OptimisticChangeMessage.md) - [OrderByCallback](type-aliases/OrderByCallback.md) - [PersistedReadinessSnapshot](type-aliases/PersistedReadinessSnapshot.md) +- [PooledLiveQueries](type-aliases/PooledLiveQueries.md) - [Prettify](type-aliases/Prettify.md) - [QueryBuilder](type-aliases/QueryBuilder.md) - [QueryIdentity](type-aliases/QueryIdentity.md) @@ -331,8 +340,11 @@ title: "@tanstack/db" ## Variables - [collectionOptionsClaim](variables/collectionOptionsClaim.md) +- [groupingCompiler](variables/groupingCompiler.md) +- [includesRuntime](variables/includesRuntime.md) - [operators](variables/operators.md) - [PERSISTED\_READINESS](variables/PERSISTED_READINESS.md) +- [pooledLiveQueries](variables/pooledLiveQueries.md) - [Query](variables/Query.md) - [SYNC\_PERSISTENCE\_PROTOCOL](variables/SYNC_PERSISTENCE_PROTOCOL.md) - [SYNC\_PERSISTENCE\_VERSION](variables/SYNC_PERSISTENCE_VERSION.md) @@ -355,6 +367,7 @@ title: "@tanstack/db" - [compileQuery](functions/compileQuery.md) - [compileSingleRowExpression](functions/compileSingleRowExpression.md) - [concat](functions/concat.md) +- [configPool](functions/configPool.md) - [configureIndexDevMode](functions/configureIndexDevMode.md) - [count](functions/count.md) - [createArrayChangeProxy](functions/createArrayChangeProxy.md) @@ -402,12 +415,14 @@ title: "@tanstack/db" - [getStableQueryBuilderHash](functions/getStableQueryBuilderHash.md) - [getStableQueryIRHash](functions/getStableQueryIRHash.md) - [getStableValueHash](functions/getStableValueHash.md) +- [groupKeys](functions/groupKeys.md) - [gt](functions/gt.md) - [gte](functions/gte.md) - [hasVirtualProps](functions/hasVirtualProps.md) - [identifyLiveQuery](functions/identifyLiveQuery.md) - [ilike](functions/ilike.md) - [inArray](functions/inArray.md) +- [include](functions/include.md) - [indexedDBCollectionOptions](functions/indexedDBCollectionOptions.md) - [isCollection](functions/isCollection.md) - [isCollectionOptions](functions/isCollectionOptions.md) @@ -441,6 +456,7 @@ title: "@tanstack/db" - [queueStrategy](functions/queueStrategy.md) - [readLiveQueryWindowPageShape](functions/readLiveQueryWindowPageShape.md) - [rejectLiveQueryInput](functions/rejectLiveQueryInput.md) +- [releaseDeferredOnReclaim](functions/releaseDeferredOnReclaim.md) - [resetWarnings](functions/resetWarnings.md) - [resolveLiveQueryValue](functions/resolveLiveQueryValue.md) - [resumeDeferredLiveQueryCollections](functions/resumeDeferredLiveQueryCollections.md) diff --git a/docs/reference/interfaces/Context.md b/docs/reference/interfaces/Context.md index 124e3187ef..7c5bb19145 100644 --- a/docs/reference/interfaces/Context.md +++ b/docs/reference/interfaces/Context.md @@ -3,7 +3,7 @@ id: Context title: Context --- -Defined in: [packages/db/src/query/builder/types.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L47) +Defined in: [packages/db/src/query/builder/types.ts:48](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L48) Context - The central state container for query builder operations @@ -35,7 +35,7 @@ The context evolves through the query builder chain: baseSchema: ContextSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:49](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L49) +Defined in: [packages/db/src/query/builder/types.ts:50](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L50) *** @@ -45,7 +45,7 @@ Defined in: [packages/db/src/query/builder/types.ts:49](https://github.com/TanSt fromSourceName: string; ``` -Defined in: [packages/db/src/query/builder/types.ts:55](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L55) +Defined in: [packages/db/src/query/builder/types.ts:56](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L56) *** @@ -55,7 +55,7 @@ Defined in: [packages/db/src/query/builder/types.ts:55](https://github.com/TanSt optional fromSourceNames: readonly string[]; ``` -Defined in: [packages/db/src/query/builder/types.ts:57](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L57) +Defined in: [packages/db/src/query/builder/types.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L58) *** @@ -65,7 +65,7 @@ Defined in: [packages/db/src/query/builder/types.ts:57](https://github.com/TanSt optional hasJoins: boolean; ``` -Defined in: [packages/db/src/query/builder/types.ts:61](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L61) +Defined in: [packages/db/src/query/builder/types.ts:62](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L62) *** @@ -75,7 +75,7 @@ Defined in: [packages/db/src/query/builder/types.ts:61](https://github.com/TanSt optional hasResult: true; ``` -Defined in: [packages/db/src/query/builder/types.ts:70](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L70) +Defined in: [packages/db/src/query/builder/types.ts:71](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L71) *** @@ -85,7 +85,7 @@ Defined in: [packages/db/src/query/builder/types.ts:70](https://github.com/TanSt optional hasUnionFrom: true; ``` -Defined in: [packages/db/src/query/builder/types.ts:59](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L59) +Defined in: [packages/db/src/query/builder/types.ts:60](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L60) *** @@ -95,7 +95,7 @@ Defined in: [packages/db/src/query/builder/types.ts:59](https://github.com/TanSt optional joinTypes: Record; ``` -Defined in: [packages/db/src/query/builder/types.ts:63](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L63) +Defined in: [packages/db/src/query/builder/types.ts:64](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L64) *** @@ -105,7 +105,7 @@ Defined in: [packages/db/src/query/builder/types.ts:63](https://github.com/TanSt optional refsSchema: ContextSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:53](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L53) +Defined in: [packages/db/src/query/builder/types.ts:54](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L54) *** @@ -115,7 +115,7 @@ Defined in: [packages/db/src/query/builder/types.ts:53](https://github.com/TanSt optional result: any; ``` -Defined in: [packages/db/src/query/builder/types.ts:68](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L68) +Defined in: [packages/db/src/query/builder/types.ts:69](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L69) *** @@ -125,7 +125,7 @@ Defined in: [packages/db/src/query/builder/types.ts:68](https://github.com/TanSt schema: ContextSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L51) +Defined in: [packages/db/src/query/builder/types.ts:52](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L52) *** @@ -135,4 +135,4 @@ Defined in: [packages/db/src/query/builder/types.ts:51](https://github.com/TanSt optional singleResult: boolean; ``` -Defined in: [packages/db/src/query/builder/types.ts:72](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L72) +Defined in: [packages/db/src/query/builder/types.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L73) diff --git a/docs/reference/interfaces/GroupKeys.md b/docs/reference/interfaces/GroupKeys.md new file mode 100644 index 0000000000..e0c55ac254 --- /dev/null +++ b/docs/reference/interfaces/GroupKeys.md @@ -0,0 +1,132 @@ +--- +id: GroupKeys +title: GroupKeys +--- + +Defined in: [packages/db/src/query/builder/group-keys.ts:9](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/group-keys.ts#L9) + +Group keys for `query.groupBy(...)` when the query has no aggregate. +Aggregates such as `count` bring the grouping compiler themselves. + +## Type Parameters + +### TContext + +`TContext` *extends* [`Context`](Context.md) + +## Properties + +### callback + +```ts +callback: GroupByCallback; +``` + +Defined in: [packages/db/src/query/builder/group-keys.ts:10](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/group-keys.ts#L10) + +*** + +### grouping + +```ts +grouping: object; +``` + +Defined in: [packages/db/src/query/builder/group-keys.ts:11](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/group-keys.ts#L11) + +#### processGroupBy() + +```ts +processGroupBy: (pipeline, groupByClause, valueIdentity, isSourceRef, havingClauses?, selectClause?, fnHavingClauses?, aggregateCollectionId?, mainSource?, sanitizeCallbackRows) => NamespacedAndKeyedStream; +``` + +Processes the GROUP BY clause with optional HAVING and SELECT +Works with the new $selected structure from early SELECT processing + +##### Parameters + +###### pipeline + +[`NamespacedAndKeyedStream`](../type-aliases/NamespacedAndKeyedStream.md) + +###### groupByClause + +[`GroupBy`](../@tanstack/namespaces/IR/type-aliases/GroupBy.md) + +###### valueIdentity + +`ValueIdentity` + +###### isSourceRef + +(`ref`) => `boolean` + +Whether a ref reads a field of the query's own rows. + +###### havingClauses? + +[`Where`](../@tanstack/namespaces/IR/type-aliases/Where.md)[] + +###### selectClause? + +[`Select`](../@tanstack/namespaces/IR/type-aliases/Select.md) + +###### fnHavingClauses? + +(`row`) => `any`[] + +###### aggregateCollectionId? + +`string` + +###### mainSource? + +`string` + +###### sanitizeCallbackRows? + +`boolean` = `false` + +##### Returns + +[`NamespacedAndKeyedStream`](../type-aliases/NamespacedAndKeyedStream.md) + +#### replaceAggregatesByRefs() + +```ts +replaceAggregatesByRefs: (havingExpr, selectClause, resultAlias) => BasicExpression; +``` + +Transforms expressions to replace aggregate functions with references to computed values. + +For aggregate expressions, finds matching aggregates in the SELECT clause and replaces them +with PropRef([resultAlias, alias]) to reference the computed aggregate value. + +Ref expressions (table columns and $selected fields) and value expressions are passed through unchanged. +Function expressions are recursively transformed. + +##### Parameters + +###### havingExpr + +The expression to transform (can be aggregate, ref, func, or val) + +[`Aggregate`](../@tanstack/namespaces/IR/classes/Aggregate.md)\<`any`\> | [`BasicExpression`](../@tanstack/namespaces/IR/type-aliases/BasicExpression.md) + +###### selectClause + +[`Select`](../@tanstack/namespaces/IR/type-aliases/Select.md) + +The SELECT clause containing aliases and aggregate definitions + +###### resultAlias + +`string` = `...` + +The namespace alias for SELECT results (default: '$selected') + +##### Returns + +[`BasicExpression`](../@tanstack/namespaces/IR/type-aliases/BasicExpression.md) + +A transformed BasicExpression that references computed values instead of raw expressions diff --git a/docs/reference/interfaces/LiveQueryCollectionConfig.md b/docs/reference/interfaces/LiveQueryCollectionConfig.md index 039753fffe..3db8c67a48 100644 --- a/docs/reference/interfaces/LiveQueryCollectionConfig.md +++ b/docs/reference/interfaces/LiveQueryCollectionConfig.md @@ -3,7 +3,7 @@ id: LiveQueryCollectionConfig title: LiveQueryCollectionConfig --- -Defined in: [packages/db/src/query/live/types.ts:65](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L65) +Defined in: [packages/db/src/query/live/types.ts:66](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L66) Configuration interface for live query collection options @@ -47,7 +47,7 @@ const config: LiveQueryCollectionConfig = { optional defaultStringCollation: StringCollationConfig; ``` -Defined in: [packages/db/src/query/live/types.ts:121](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L121) +Defined in: [packages/db/src/query/live/types.ts:122](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L122) Optional compare options for string sorting. If provided, these will be used instead of inheriting from the FROM collection. @@ -60,7 +60,7 @@ If provided, these will be used instead of inheriting from the FROM collection. optional gcTime: number; ``` -Defined in: [packages/db/src/query/live/types.ts:110](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L110) +Defined in: [packages/db/src/query/live/types.ts:111](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L111) GC time for the collection @@ -72,7 +72,7 @@ GC time for the collection optional getKey: (item) => string | number; ``` -Defined in: [packages/db/src/query/live/types.ts:88](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L88) +Defined in: [packages/db/src/query/live/types.ts:89](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L89) Function to extract the key from result items If not provided, defaults to using the key from the D2 stream @@ -95,7 +95,7 @@ If not provided, defaults to using the key from the D2 stream optional id: string; ``` -Defined in: [packages/db/src/query/live/types.ts:73](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L73) +Defined in: [packages/db/src/query/live/types.ts:74](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L74) Unique identifier for the collection If not provided, defaults to `live-query-${number}` with auto-incrementing number @@ -110,7 +110,7 @@ optional onDelete: | DeleteMutationFn; ``` -Defined in: [packages/db/src/query/live/types.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L100) +Defined in: [packages/db/src/query/live/types.ts:101](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L101) *** @@ -122,7 +122,7 @@ optional onInsert: | InsertMutationFn; ``` -Defined in: [packages/db/src/query/live/types.ts:98](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L98) +Defined in: [packages/db/src/query/live/types.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L99) Optional mutation handlers @@ -136,7 +136,7 @@ optional onUpdate: | UpdateMutationFn; ``` -Defined in: [packages/db/src/query/live/types.ts:99](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L99) +Defined in: [packages/db/src/query/live/types.ts:100](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L100) *** @@ -148,7 +148,7 @@ query: | QueryBuilder & RootObjectResultConstraint; ``` -Defined in: [packages/db/src/query/live/types.ts:78](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L78) +Defined in: [packages/db/src/query/live/types.ts:79](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L79) Query builder function that defines the live query @@ -160,7 +160,7 @@ Query builder function that defines the live query optional schema: undefined; ``` -Defined in: [packages/db/src/query/live/types.ts:93](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L93) +Defined in: [packages/db/src/query/live/types.ts:94](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L94) Optional schema for validation @@ -172,7 +172,7 @@ Optional schema for validation optional singleResult: true; ``` -Defined in: [packages/db/src/query/live/types.ts:115](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L115) +Defined in: [packages/db/src/query/live/types.ts:116](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L116) If enabled the collection will return a single object instead of an array @@ -184,6 +184,6 @@ If enabled the collection will return a single object instead of an array optional startSync: boolean; ``` -Defined in: [packages/db/src/query/live/types.ts:105](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L105) +Defined in: [packages/db/src/query/live/types.ts:106](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L106) Start sync / the query immediately diff --git a/docs/reference/interfaces/LiveQueryIdentityScope.md b/docs/reference/interfaces/LiveQueryIdentityScope.md index 1cbd51232e..37b641cd52 100644 --- a/docs/reference/interfaces/LiveQueryIdentityScope.md +++ b/docs/reference/interfaces/LiveQueryIdentityScope.md @@ -3,7 +3,7 @@ id: LiveQueryIdentityScope title: LiveQueryIdentityScope --- -Defined in: [packages/db/src/live-query-adapter.ts:120](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L120) +Defined in: [packages/db/src/live-query-adapter.ts:122](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L122) Where a binding that compares query identity checks a prepared query. @@ -15,7 +15,7 @@ Where a binding that compares query identity checks a prepared query. bindingName: string; ``` -Defined in: [packages/db/src/live-query-adapter.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L124) +Defined in: [packages/db/src/live-query-adapter.ts:126](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L126) *** @@ -25,7 +25,7 @@ Defined in: [packages/db/src/live-query-adapter.ts:124](https://github.com/TanSt claims: LiveQuerySourceIdClaims; ``` -Defined in: [packages/db/src/live-query-adapter.ts:122](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L122) +Defined in: [packages/db/src/live-query-adapter.ts:124](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L124) This binding's source Collection ID claims. @@ -37,7 +37,7 @@ This binding's source Collection ID claims. client: DbClient | undefined; ``` -Defined in: [packages/db/src/live-query-adapter.ts:123](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L123) +Defined in: [packages/db/src/live-query-adapter.ts:125](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L125) *** @@ -47,7 +47,7 @@ Defined in: [packages/db/src/live-query-adapter.ts:123](https://github.com/TanSt optional onUnboundDescriptor: (alias) => never; ``` -Defined in: [packages/db/src/live-query-adapter.ts:126](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L126) +Defined in: [packages/db/src/live-query-adapter.ts:128](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L128) Report a descriptor source that no client bound. diff --git a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md index 694de11907..dccdbf6237 100644 --- a/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md +++ b/docs/reference/type-aliases/ApplyJoinOptionalityToMergedSchema.md @@ -7,7 +7,7 @@ title: ApplyJoinOptionalityToMergedSchema type ApplyJoinOptionalityToMergedSchema = { [K in keyof TExistingSchema]: K extends TFromSourceNames ? TJoinType extends "right" | "full" ? TExistingSchema[K] | undefined : TExistingSchema[K] : TExistingSchema[K] } & { [K in keyof TNewSchema]: TJoinType extends "left" | "full" ? TNewSchema[K] | undefined : TNewSchema[K] }; ``` -Defined in: [packages/db/src/query/builder/types.ts:1037](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1037) +Defined in: [packages/db/src/query/builder/types.ts:1075](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1075) ApplyJoinOptionalityToMergedSchema - Applies optionality rules when merging schemas diff --git a/docs/reference/type-aliases/ContextFromSource.md b/docs/reference/type-aliases/ContextFromSource.md index 7331163899..538bb54705 100644 --- a/docs/reference/type-aliases/ContextFromSource.md +++ b/docs/reference/type-aliases/ContextFromSource.md @@ -7,7 +7,7 @@ title: ContextFromSource type ContextFromSource = object; ``` -Defined in: [packages/db/src/query/builder/types.ts:154](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L154) +Defined in: [packages/db/src/query/builder/types.ts:155](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L155) ## Type Parameters @@ -23,7 +23,7 @@ Defined in: [packages/db/src/query/builder/types.ts:154](https://github.com/TanS baseSchema: SchemaFromSource; ``` -Defined in: [packages/db/src/query/builder/types.ts:155](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L155) +Defined in: [packages/db/src/query/builder/types.ts:156](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L156) *** @@ -33,7 +33,7 @@ Defined in: [packages/db/src/query/builder/types.ts:155](https://github.com/TanS fromSourceName: keyof TSource & string; ``` -Defined in: [packages/db/src/query/builder/types.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L157) +Defined in: [packages/db/src/query/builder/types.ts:158](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L158) *** @@ -43,7 +43,7 @@ Defined in: [packages/db/src/query/builder/types.ts:157](https://github.com/TanS hasJoins: false; ``` -Defined in: [packages/db/src/query/builder/types.ts:158](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L158) +Defined in: [packages/db/src/query/builder/types.ts:159](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L159) *** @@ -53,4 +53,4 @@ Defined in: [packages/db/src/query/builder/types.ts:158](https://github.com/TanS schema: SchemaFromSource; ``` -Defined in: [packages/db/src/query/builder/types.ts:156](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L156) +Defined in: [packages/db/src/query/builder/types.ts:157](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L157) diff --git a/docs/reference/type-aliases/ContextFromUnionBranches.md b/docs/reference/type-aliases/ContextFromUnionBranches.md index 84c7931b1c..72dd0a7515 100644 --- a/docs/reference/type-aliases/ContextFromUnionBranches.md +++ b/docs/reference/type-aliases/ContextFromUnionBranches.md @@ -7,7 +7,7 @@ title: ContextFromUnionBranches type ContextFromUnionBranches = object; ``` -Defined in: [packages/db/src/query/builder/types.ts:188](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L188) +Defined in: [packages/db/src/query/builder/types.ts:189](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L189) ## Type Parameters @@ -23,7 +23,7 @@ Defined in: [packages/db/src/query/builder/types.ts:188](https://github.com/TanS [BranchUnionRefs]: UnionBranchResult; ``` -Defined in: [packages/db/src/query/builder/types.ts:198](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L198) +Defined in: [packages/db/src/query/builder/types.ts:199](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L199) *** @@ -33,7 +33,7 @@ Defined in: [packages/db/src/query/builder/types.ts:198](https://github.com/TanS baseSchema: UnionBranchSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:191](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L191) +Defined in: [packages/db/src/query/builder/types.ts:192](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L192) *** @@ -43,7 +43,7 @@ Defined in: [packages/db/src/query/builder/types.ts:191](https://github.com/TanS fromSourceName: keyof UnionBranchSchema & string; ``` -Defined in: [packages/db/src/query/builder/types.ts:194](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L194) +Defined in: [packages/db/src/query/builder/types.ts:195](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L195) *** @@ -53,7 +53,7 @@ Defined in: [packages/db/src/query/builder/types.ts:194](https://github.com/TanS hasJoins: false; ``` -Defined in: [packages/db/src/query/builder/types.ts:195](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L195) +Defined in: [packages/db/src/query/builder/types.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L196) *** @@ -63,7 +63,7 @@ Defined in: [packages/db/src/query/builder/types.ts:195](https://github.com/TanS hasResult: true; ``` -Defined in: [packages/db/src/query/builder/types.ts:197](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L197) +Defined in: [packages/db/src/query/builder/types.ts:198](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L198) *** @@ -73,7 +73,7 @@ Defined in: [packages/db/src/query/builder/types.ts:197](https://github.com/TanS refsSchema: UnionBranchSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:193](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L193) +Defined in: [packages/db/src/query/builder/types.ts:194](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L194) *** @@ -83,7 +83,7 @@ Defined in: [packages/db/src/query/builder/types.ts:193](https://github.com/TanS result: PrettifyIfPlainObject>; ``` -Defined in: [packages/db/src/query/builder/types.ts:196](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L196) +Defined in: [packages/db/src/query/builder/types.ts:197](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L197) *** @@ -93,4 +93,4 @@ Defined in: [packages/db/src/query/builder/types.ts:196](https://github.com/TanS schema: UnionBranchSchema; ``` -Defined in: [packages/db/src/query/builder/types.ts:192](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L192) +Defined in: [packages/db/src/query/builder/types.ts:193](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L193) diff --git a/docs/reference/type-aliases/ContextFromUnionSource.md b/docs/reference/type-aliases/ContextFromUnionSource.md index 34c1c1f710..50b1cd223f 100644 --- a/docs/reference/type-aliases/ContextFromUnionSource.md +++ b/docs/reference/type-aliases/ContextFromUnionSource.md @@ -7,7 +7,7 @@ title: ContextFromUnionSource type ContextFromUnionSource = IsUnion extends true ? object : ContextFromSource; ``` -Defined in: [packages/db/src/query/builder/types.ts:161](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L161) +Defined in: [packages/db/src/query/builder/types.ts:162](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L162) ## Type Parameters diff --git a/docs/reference/type-aliases/ContextSchema.md b/docs/reference/type-aliases/ContextSchema.md index 7e0592623c..b71ea52f8c 100644 --- a/docs/reference/type-aliases/ContextSchema.md +++ b/docs/reference/type-aliases/ContextSchema.md @@ -7,7 +7,7 @@ title: ContextSchema type ContextSchema = Record; ``` -Defined in: [packages/db/src/query/builder/types.ts:84](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L84) +Defined in: [packages/db/src/query/builder/types.ts:85](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L85) ContextSchema - The shape of available tables/collections in a query context diff --git a/docs/reference/type-aliases/DeferredLiveQueryCollections.md b/docs/reference/type-aliases/DeferredLiveQueryCollections.md index 5e3895f3a2..f8ef7ce12e 100644 --- a/docs/reference/type-aliases/DeferredLiveQueryCollections.md +++ b/docs/reference/type-aliases/DeferredLiveQueryCollections.md @@ -7,4 +7,4 @@ title: DeferredLiveQueryCollections type DeferredLiveQueryCollections = Set>; ``` -Defined in: [packages/db/src/live-query-options.ts:31](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L31) +Defined in: [packages/db/src/live-query-options.ts:51](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L51) diff --git a/docs/reference/type-aliases/ExtractContext.md b/docs/reference/type-aliases/ExtractContext.md index 2ae5f6d96a..7e7cfbf233 100644 --- a/docs/reference/type-aliases/ExtractContext.md +++ b/docs/reference/type-aliases/ExtractContext.md @@ -7,7 +7,7 @@ title: ExtractContext type ExtractContext = T extends BaseQueryBuilder ? TContext : T extends QueryBuilder ? TContext : never; ``` -Defined in: [packages/db/src/query/builder/index.ts:1802](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1802) +Defined in: [packages/db/src/query/builder/index.ts:1882](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1882) ## Type Parameters diff --git a/docs/reference/type-aliases/FunctionalHavingRow.md b/docs/reference/type-aliases/FunctionalHavingRow.md index 08594c36ad..9fd73458a5 100644 --- a/docs/reference/type-aliases/FunctionalHavingRow.md +++ b/docs/reference/type-aliases/FunctionalHavingRow.md @@ -7,7 +7,7 @@ title: FunctionalHavingRow type FunctionalHavingRow = TContext["schema"] & TContext["hasResult"] extends true ? object : object; ``` -Defined in: [packages/db/src/query/builder/types.ts:662](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L662) +Defined in: [packages/db/src/query/builder/types.ts:700](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L700) FunctionalHavingRow - Type for the row parameter in functional having callbacks diff --git a/docs/reference/type-aliases/GetResult.md b/docs/reference/type-aliases/GetResult.md index 0e82d3f63b..14e0751144 100644 --- a/docs/reference/type-aliases/GetResult.md +++ b/docs/reference/type-aliases/GetResult.md @@ -7,7 +7,7 @@ title: GetResult type GetResult = Prettify>; ``` -Defined in: [packages/db/src/query/builder/types.ts:1171](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1171) +Defined in: [packages/db/src/query/builder/types.ts:1209](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1209) ## Type Parameters diff --git a/docs/reference/type-aliases/GroupByCallback.md b/docs/reference/type-aliases/GroupByCallback.md index fd4a00f164..856824c727 100644 --- a/docs/reference/type-aliases/GroupByCallback.md +++ b/docs/reference/type-aliases/GroupByCallback.md @@ -7,7 +7,7 @@ title: GroupByCallback type GroupByCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:625](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L625) +Defined in: [packages/db/src/query/builder/types.ts:652](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L652) GroupByCallback - Type for groupBy clause callback functions diff --git a/docs/reference/type-aliases/Grouping.md b/docs/reference/type-aliases/Grouping.md new file mode 100644 index 0000000000..62dbf25a35 --- /dev/null +++ b/docs/reference/type-aliases/Grouping.md @@ -0,0 +1,10 @@ +--- +id: Grouping +title: Grouping +--- + +```ts +type Grouping = typeof groupingCompiler; +``` + +Defined in: [packages/db/src/query/compiler/grouping.ts:9](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/grouping.ts#L9) diff --git a/docs/reference/type-aliases/IncludesRuntime.md b/docs/reference/type-aliases/IncludesRuntime.md new file mode 100644 index 0000000000..59d5273c31 --- /dev/null +++ b/docs/reference/type-aliases/IncludesRuntime.md @@ -0,0 +1,10 @@ +--- +id: IncludesRuntime +title: IncludesRuntime +--- + +```ts +type IncludesRuntime = typeof includesRuntime; +``` + +Defined in: [packages/db/src/query/live/includes-runtime.ts:15](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/includes-runtime.ts#L15) diff --git a/docs/reference/type-aliases/InferCollectionType.md b/docs/reference/type-aliases/InferCollectionType.md index 118b077e7b..4a835008bd 100644 --- a/docs/reference/type-aliases/InferCollectionType.md +++ b/docs/reference/type-aliases/InferCollectionType.md @@ -7,7 +7,7 @@ title: InferCollectionType type InferCollectionType = T extends CollectionImpl ? WithVirtualProps : T extends CollectionOptionsIdentity ? WithVirtualProps : never; ``` -Defined in: [packages/db/src/query/builder/types.ts:108](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L108) +Defined in: [packages/db/src/query/builder/types.ts:109](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L109) InferCollectionType - Extracts the TypeScript type from a CollectionImpl diff --git a/docs/reference/type-aliases/InferResultType.md b/docs/reference/type-aliases/InferResultType.md index 60c2fcb1be..0da3a0028d 100644 --- a/docs/reference/type-aliases/InferResultType.md +++ b/docs/reference/type-aliases/InferResultType.md @@ -7,7 +7,7 @@ title: InferResultType type InferResultType = TContext extends SingleResult ? GetResult | undefined : GetResult[]; ``` -Defined in: [packages/db/src/query/builder/types.ts:1063](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1063) +Defined in: [packages/db/src/query/builder/types.ts:1101](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1101) Utility type to infer the query result size (single row or an array) diff --git a/docs/reference/type-aliases/InitialQueryBuilder.md b/docs/reference/type-aliases/InitialQueryBuilder.md index ea48982c4d..6610c07989 100644 --- a/docs/reference/type-aliases/InitialQueryBuilder.md +++ b/docs/reference/type-aliases/InitialQueryBuilder.md @@ -7,4 +7,4 @@ title: InitialQueryBuilder type InitialQueryBuilder = Pick, "from" | "unionAll">; ``` -Defined in: [packages/db/src/query/builder/index.ts:1785](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1785) +Defined in: [packages/db/src/query/builder/index.ts:1865](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1865) diff --git a/docs/reference/type-aliases/JoinOnCallback.md b/docs/reference/type-aliases/JoinOnCallback.md index 5aa8093c06..63bc4e963b 100644 --- a/docs/reference/type-aliases/JoinOnCallback.md +++ b/docs/reference/type-aliases/JoinOnCallback.md @@ -7,7 +7,7 @@ title: JoinOnCallback type JoinOnCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:641](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L641) +Defined in: [packages/db/src/query/builder/types.ts:679](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L679) JoinOnCallback - Type for join condition callback functions diff --git a/docs/reference/type-aliases/LiveQueryCollectionUtils.md b/docs/reference/type-aliases/LiveQueryCollectionUtils.md index 0c6c5236ea..96883c3423 100644 --- a/docs/reference/type-aliases/LiveQueryCollectionUtils.md +++ b/docs/reference/type-aliases/LiveQueryCollectionUtils.md @@ -7,7 +7,7 @@ title: LiveQueryCollectionUtils type LiveQueryCollectionUtils = UtilsRecord & object; ``` -Defined in: [packages/db/src/query/live/collection-config-builder.ts:58](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/collection-config-builder.ts#L58) +Defined in: [packages/db/src/query/live/collection-config-builder.ts:60](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/collection-config-builder.ts#L60) ## Type Declaration diff --git a/docs/reference/type-aliases/LiveQueryHookConfig.md b/docs/reference/type-aliases/LiveQueryHookConfig.md new file mode 100644 index 0000000000..33030b84d9 --- /dev/null +++ b/docs/reference/type-aliases/LiveQueryHookConfig.md @@ -0,0 +1,33 @@ +--- +id: LiveQueryHookConfig +title: LiveQueryHookConfig +--- + +```ts +type LiveQueryHookConfig = LiveQueryCollectionConfig & object; +``` + +Defined in: [packages/db/src/query/live/types.ts:131](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/types.ts#L131) + +A live query config that a framework hook accepts. Pass `pooledLiveQueries` +as `pool` to serve the query from a shared equality partition when its +shape allows. A config with any option other than `query`, `gcTime`, and +`pool` compiles its own Collection. + +## Type Declaration + +### pool? + +```ts +optional pool: PooledLiveQueries; +``` + +## Type Parameters + +### TContext + +`TContext` *extends* [`Context`](../interfaces/Context.md) + +### TResult + +`TResult` *extends* `object` = `RootQueryResult`\<`TContext`\> diff --git a/docs/reference/type-aliases/LiveQueryKey.md b/docs/reference/type-aliases/LiveQueryKey.md index e287f35b36..a009cd6db1 100644 --- a/docs/reference/type-aliases/LiveQueryKey.md +++ b/docs/reference/type-aliases/LiveQueryKey.md @@ -7,4 +7,4 @@ title: LiveQueryKey type LiveQueryKey = ReadonlyArray; ``` -Defined in: [packages/db/src/live-query-options.ts:25](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L25) +Defined in: [packages/db/src/live-query-options.ts:45](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L45) diff --git a/docs/reference/type-aliases/LiveQueryOptions.md b/docs/reference/type-aliases/LiveQueryOptions.md index c96a4c1f89..9a0d23ebe6 100644 --- a/docs/reference/type-aliases/LiveQueryOptions.md +++ b/docs/reference/type-aliases/LiveQueryOptions.md @@ -7,7 +7,7 @@ title: LiveQueryOptions type LiveQueryOptions = LiveQueryCollectionConfig & object; ``` -Defined in: [packages/db/src/live-query-options.ts:27](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L27) +Defined in: [packages/db/src/live-query-options.ts:47](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-options.ts#L47) ## Type Declaration diff --git a/docs/reference/type-aliases/LiveQuerySourceIdClaims.md b/docs/reference/type-aliases/LiveQuerySourceIdClaims.md index 086949da7f..df636c04bd 100644 --- a/docs/reference/type-aliases/LiveQuerySourceIdClaims.md +++ b/docs/reference/type-aliases/LiveQuerySourceIdClaims.md @@ -7,7 +7,7 @@ title: LiveQuerySourceIdClaims type LiveQuerySourceIdClaims = WeakMap>; ``` -Defined in: [packages/db/src/live-query-adapter.ts:90](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L90) +Defined in: [packages/db/src/live-query-adapter.ts:92](https://github.com/TanStack/db/blob/main/packages/db/src/live-query-adapter.ts#L92) The source object each Collection ID named while one binding is mounted, per client scope. diff --git a/docs/reference/type-aliases/MergeContextForJoinCallback.md b/docs/reference/type-aliases/MergeContextForJoinCallback.md index 35a01bf8f7..0804a5cc76 100644 --- a/docs/reference/type-aliases/MergeContextForJoinCallback.md +++ b/docs/reference/type-aliases/MergeContextForJoinCallback.md @@ -7,7 +7,7 @@ title: MergeContextForJoinCallback type MergeContextForJoinCallback = Omit & object; ``` -Defined in: [packages/db/src/query/builder/types.ts:1328](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1328) +Defined in: [packages/db/src/query/builder/types.ts:1366](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1366) MergeContextForJoinCallback - Special context for join condition callbacks diff --git a/docs/reference/type-aliases/MergeContextWithJoinType.md b/docs/reference/type-aliases/MergeContextWithJoinType.md index 77505e1816..3b10361c7b 100644 --- a/docs/reference/type-aliases/MergeContextWithJoinType.md +++ b/docs/reference/type-aliases/MergeContextWithJoinType.md @@ -7,7 +7,7 @@ title: MergeContextWithJoinType type MergeContextWithJoinType = Omit & object; ``` -Defined in: [packages/db/src/query/builder/types.ts:983](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L983) +Defined in: [packages/db/src/query/builder/types.ts:1021](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1021) MergeContextWithJoinType - Creates a new context after a join operation diff --git a/docs/reference/type-aliases/OperatorCallback.md b/docs/reference/type-aliases/OperatorCallback.md new file mode 100644 index 0000000000..2fdee056d3 --- /dev/null +++ b/docs/reference/type-aliases/OperatorCallback.md @@ -0,0 +1,25 @@ +--- +id: OperatorCallback +title: OperatorCallback +--- + +```ts +type OperatorCallback = 0 extends 1 & TContext ? (refs) => any : TCallback; +``` + +Defined in: [packages/db/src/query/builder/types.ts:662](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L662) + +A callback that an operator value receives before the query supplies its +context. Without noImplicitAny, TypeScript skips that inference and the +context is `any`; the callback is then untyped, as other unannotated +callbacks are in that mode. + +## Type Parameters + +### TContext + +`TContext` *extends* [`Context`](../interfaces/Context.md) + +### TCallback + +`TCallback` diff --git a/docs/reference/type-aliases/OperatorName.md b/docs/reference/type-aliases/OperatorName.md index cfafd17391..444e6a6df4 100644 --- a/docs/reference/type-aliases/OperatorName.md +++ b/docs/reference/type-aliases/OperatorName.md @@ -7,4 +7,4 @@ title: OperatorName type OperatorName = typeof operators[number]; ``` -Defined in: [packages/db/src/query/builder/functions.ts:763](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L763) +Defined in: [packages/db/src/query/builder/functions.ts:762](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L762) diff --git a/docs/reference/type-aliases/OrderByCallback.md b/docs/reference/type-aliases/OrderByCallback.md index 0210ce829c..18c787bbd6 100644 --- a/docs/reference/type-aliases/OrderByCallback.md +++ b/docs/reference/type-aliases/OrderByCallback.md @@ -7,7 +7,7 @@ title: OrderByCallback type OrderByCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:589](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L589) +Defined in: [packages/db/src/query/builder/types.ts:616](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L616) OrderByCallback - Type for orderBy clause callback functions diff --git a/docs/reference/type-aliases/PooledLiveQueries.md b/docs/reference/type-aliases/PooledLiveQueries.md new file mode 100644 index 0000000000..3159935577 --- /dev/null +++ b/docs/reference/type-aliases/PooledLiveQueries.md @@ -0,0 +1,10 @@ +--- +id: PooledLiveQueries +title: PooledLiveQueries +--- + +```ts +type PooledLiveQueries = typeof pooledLiveQueries; +``` + +Defined in: [packages/db/src/query/pooled-live-query.ts:733](https://github.com/TanStack/db/blob/main/packages/db/src/query/pooled-live-query.ts#L733) diff --git a/docs/reference/type-aliases/Prettify.md b/docs/reference/type-aliases/Prettify.md index 9999dd635b..9e287ecf31 100644 --- a/docs/reference/type-aliases/Prettify.md +++ b/docs/reference/type-aliases/Prettify.md @@ -7,7 +7,7 @@ title: Prettify type Prettify = { [K in keyof T]: T[K] } & object; ``` -Defined in: [packages/db/src/query/builder/types.ts:1370](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1370) +Defined in: [packages/db/src/query/builder/types.ts:1408](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1408) Prettify - Utility type for clean IDE display diff --git a/docs/reference/type-aliases/QueryBuilder.md b/docs/reference/type-aliases/QueryBuilder.md index 789467f2e0..fbfefb5baa 100644 --- a/docs/reference/type-aliases/QueryBuilder.md +++ b/docs/reference/type-aliases/QueryBuilder.md @@ -7,7 +7,7 @@ title: QueryBuilder type QueryBuilder = Omit, "from" | "unionAll" | "_getQuery">; ``` -Defined in: [packages/db/src/query/builder/index.ts:1792](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1792) +Defined in: [packages/db/src/query/builder/index.ts:1872](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1872) ## Type Parameters diff --git a/docs/reference/type-aliases/QueryResult.md b/docs/reference/type-aliases/QueryResult.md index 68bb6da2c5..087d8d5d4d 100644 --- a/docs/reference/type-aliases/QueryResult.md +++ b/docs/reference/type-aliases/QueryResult.md @@ -7,7 +7,7 @@ title: QueryResult type QueryResult = GetResult>; ``` -Defined in: [packages/db/src/query/builder/index.ts:1810](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1810) +Defined in: [packages/db/src/query/builder/index.ts:1890](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1890) ## Type Parameters diff --git a/docs/reference/type-aliases/Ref.md b/docs/reference/type-aliases/Ref.md index 77e88812f2..215ea634f3 100644 --- a/docs/reference/type-aliases/Ref.md +++ b/docs/reference/type-aliases/Ref.md @@ -7,7 +7,7 @@ title: Ref type Ref = T extends unknown ? RefBranch : never; ``` -Defined in: [packages/db/src/query/builder/types.ts:899](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L899) +Defined in: [packages/db/src/query/builder/types.ts:937](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L937) Ref - The user-facing ref interface for the query builder diff --git a/docs/reference/type-aliases/RefsForContext.md b/docs/reference/type-aliases/RefsForContext.md index b3c59189e0..bbc594376b 100644 --- a/docs/reference/type-aliases/RefsForContext.md +++ b/docs/reference/type-aliases/RefsForContext.md @@ -7,7 +7,7 @@ title: RefsForContext type RefsForContext = { [K in Exclude>, keyof JoinedRefsForContext | keyof BranchUnionResultRefs>]: RefForContextSchemaValue, K>, IsNullableContextKey> } & TContext["hasResult"] extends true ? object : object & BranchUnionResultRefs & JoinedRefsForContext; ``` -Defined in: [packages/db/src/query/builder/types.ts:761](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L761) +Defined in: [packages/db/src/query/builder/types.ts:799](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L799) ## Type Parameters diff --git a/docs/reference/type-aliases/ResultTypeFromSelect.md b/docs/reference/type-aliases/ResultTypeFromSelect.md index 00f2132fc4..afc4261124 100644 --- a/docs/reference/type-aliases/ResultTypeFromSelect.md +++ b/docs/reference/type-aliases/ResultTypeFromSelect.md @@ -7,7 +7,7 @@ title: ResultTypeFromSelect type ResultTypeFromSelect = IsAny extends true ? any : IsTrueRef extends true ? ExtractDirectSelectRef : WithoutRefBrand extends true ? ExtractExpressionType : TSelectObject[K] extends ToArrayWrapper ? T[] : TSelectObject[K] extends ConcatToArrayWrapper ? string : TSelectObject[K] extends MaterializeWrapper ? IsSingle extends true ? (...) | (...) : (...)[] : (...)[(...)] extends { __brand: ...; _result?: ... } ? ResultTypeFromCaseWhen<(...)> : (...) extends (...) ? (...) : (...) }>>; ``` -Defined in: [packages/db/src/query/builder/types.ts:413](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L413) +Defined in: [packages/db/src/query/builder/types.ts:440](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L440) ResultTypeFromSelect - Infers the result type from a select object diff --git a/docs/reference/type-aliases/SchemaFromSource.md b/docs/reference/type-aliases/SchemaFromSource.md index 64f52563e8..2fff2d490f 100644 --- a/docs/reference/type-aliases/SchemaFromSource.md +++ b/docs/reference/type-aliases/SchemaFromSource.md @@ -7,7 +7,7 @@ title: SchemaFromSource type SchemaFromSource = Prettify<{ [K in keyof T]: T[K] extends CollectionImpl ? InferCollectionType : T[K] extends CollectionOptionsIdentity ? InferCollectionType : T[K] extends QueryBuilder ? GetRawResult : never }>; ``` -Defined in: [packages/db/src/query/builder/types.ts:131](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L131) +Defined in: [packages/db/src/query/builder/types.ts:132](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L132) SchemaFromSource - Converts a Source definition into a ContextSchema diff --git a/docs/reference/type-aliases/SelectObject.md b/docs/reference/type-aliases/SelectObject.md index 1cd3cfc774..3acca6c988 100644 --- a/docs/reference/type-aliases/SelectObject.md +++ b/docs/reference/type-aliases/SelectObject.md @@ -7,7 +7,7 @@ title: SelectObject type SelectObject = T; ``` -Defined in: [packages/db/src/query/builder/types.ts:303](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L303) +Defined in: [packages/db/src/query/builder/types.ts:304](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L304) SelectObject - Wrapper type for select clause objects diff --git a/docs/reference/type-aliases/SingleSource.md b/docs/reference/type-aliases/SingleSource.md index 846101c262..4d67707522 100644 --- a/docs/reference/type-aliases/SingleSource.md +++ b/docs/reference/type-aliases/SingleSource.md @@ -7,7 +7,7 @@ title: SingleSource type SingleSource = IsUnion extends true ? never : TSource; ``` -Defined in: [packages/db/src/query/builder/types.ts:151](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L151) +Defined in: [packages/db/src/query/builder/types.ts:152](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L152) ## Type Parameters diff --git a/docs/reference/type-aliases/Source.md b/docs/reference/type-aliases/Source.md index 5b3d8d1594..16190f9745 100644 --- a/docs/reference/type-aliases/Source.md +++ b/docs/reference/type-aliases/Source.md @@ -7,7 +7,7 @@ title: Source type Source = object; ``` -Defined in: [packages/db/src/query/builder/types.ts:95](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L95) +Defined in: [packages/db/src/query/builder/types.ts:96](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L96) Source - Input definition for query builder `from()` and `unionAll()` clauses @@ -21,7 +21,7 @@ Example: `{ users: usersCollection }` ```ts [alias: string]: - | CollectionOptionsIdentity | QueryBuilder + | CollectionOptionsIdentity | CollectionImpl ``` diff --git a/docs/reference/type-aliases/SourceClauseContext.md b/docs/reference/type-aliases/SourceClauseContext.md index d1d13a0415..564128dc60 100644 --- a/docs/reference/type-aliases/SourceClauseContext.md +++ b/docs/reference/type-aliases/SourceClauseContext.md @@ -7,4 +7,4 @@ title: SourceClauseContext type SourceClauseContext = "from clause" | "unionAll clause" | "join clause"; ``` -Defined in: [packages/db/src/errors.ts:613](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L613) +Defined in: [packages/db/src/errors.ts:623](https://github.com/TanStack/db/blob/main/packages/db/src/errors.ts#L623) diff --git a/docs/reference/type-aliases/WhereCallback.md b/docs/reference/type-aliases/WhereCallback.md index 0f5a10e44f..f383b83bd1 100644 --- a/docs/reference/type-aliases/WhereCallback.md +++ b/docs/reference/type-aliases/WhereCallback.md @@ -7,7 +7,7 @@ title: WhereCallback type WhereCallback = (refs) => any; ``` -Defined in: [packages/db/src/query/builder/types.ts:218](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L218) +Defined in: [packages/db/src/query/builder/types.ts:219](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L219) WhereCallback - Type for where/having clause callback functions diff --git a/docs/reference/type-aliases/WithResult.md b/docs/reference/type-aliases/WithResult.md index 687f0b2fcc..20efbc013c 100644 --- a/docs/reference/type-aliases/WithResult.md +++ b/docs/reference/type-aliases/WithResult.md @@ -7,7 +7,7 @@ title: WithResult type WithResult = Prettify & Pick & object>; ``` -Defined in: [packages/db/src/query/builder/types.ts:1359](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1359) +Defined in: [packages/db/src/query/builder/types.ts:1397](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/types.ts#L1397) WithResult - Updates a context with a new result type after select() diff --git a/docs/reference/variables/Query.md b/docs/reference/variables/Query.md index df58acceb8..5048c18820 100644 --- a/docs/reference/variables/Query.md +++ b/docs/reference/variables/Query.md @@ -7,4 +7,4 @@ title: Query const Query: InitialQueryBuilderConstructor = BaseQueryBuilder; ``` -Defined in: [packages/db/src/query/builder/index.ts:1799](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1799) +Defined in: [packages/db/src/query/builder/index.ts:1879](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/index.ts#L1879) diff --git a/docs/reference/variables/groupingCompiler.md b/docs/reference/variables/groupingCompiler.md new file mode 100644 index 0000000000..3746539478 --- /dev/null +++ b/docs/reference/variables/groupingCompiler.md @@ -0,0 +1,112 @@ +--- +id: groupingCompiler +title: groupingCompiler +--- + +```ts +const groupingCompiler: object; +``` + +Defined in: [packages/db/src/query/compiler/grouping.ts:7](https://github.com/TanStack/db/blob/main/packages/db/src/query/compiler/grouping.ts#L7) + +The grouping compiler. Aggregate values and `groupKeys` carry it onto the +query IR, so apps that never group or aggregate do not bundle it. + +## Type Declaration + +### processGroupBy() + +```ts +processGroupBy: (pipeline, groupByClause, valueIdentity, isSourceRef, havingClauses?, selectClause?, fnHavingClauses?, aggregateCollectionId?, mainSource?, sanitizeCallbackRows) => NamespacedAndKeyedStream; +``` + +Processes the GROUP BY clause with optional HAVING and SELECT +Works with the new $selected structure from early SELECT processing + +#### Parameters + +##### pipeline + +[`NamespacedAndKeyedStream`](../type-aliases/NamespacedAndKeyedStream.md) + +##### groupByClause + +[`GroupBy`](../@tanstack/namespaces/IR/type-aliases/GroupBy.md) + +##### valueIdentity + +`ValueIdentity` + +##### isSourceRef + +(`ref`) => `boolean` + +Whether a ref reads a field of the query's own rows. + +##### havingClauses? + +[`Where`](../@tanstack/namespaces/IR/type-aliases/Where.md)[] + +##### selectClause? + +[`Select`](../@tanstack/namespaces/IR/type-aliases/Select.md) + +##### fnHavingClauses? + +(`row`) => `any`[] + +##### aggregateCollectionId? + +`string` + +##### mainSource? + +`string` + +##### sanitizeCallbackRows? + +`boolean` = `false` + +#### Returns + +[`NamespacedAndKeyedStream`](../type-aliases/NamespacedAndKeyedStream.md) + +### replaceAggregatesByRefs() + +```ts +replaceAggregatesByRefs: (havingExpr, selectClause, resultAlias) => BasicExpression; +``` + +Transforms expressions to replace aggregate functions with references to computed values. + +For aggregate expressions, finds matching aggregates in the SELECT clause and replaces them +with PropRef([resultAlias, alias]) to reference the computed aggregate value. + +Ref expressions (table columns and $selected fields) and value expressions are passed through unchanged. +Function expressions are recursively transformed. + +#### Parameters + +##### havingExpr + +The expression to transform (can be aggregate, ref, func, or val) + +[`Aggregate`](../@tanstack/namespaces/IR/classes/Aggregate.md)\<`any`\> | [`BasicExpression`](../@tanstack/namespaces/IR/type-aliases/BasicExpression.md) + +##### selectClause + +[`Select`](../@tanstack/namespaces/IR/type-aliases/Select.md) + +The SELECT clause containing aliases and aggregate definitions + +##### resultAlias + +`string` = `...` + +The namespace alias for SELECT results (default: '$selected') + +#### Returns + +[`BasicExpression`](../@tanstack/namespaces/IR/type-aliases/BasicExpression.md) + +A transformed BasicExpression that references computed values instead of raw expressions diff --git a/docs/reference/variables/includesRuntime.md b/docs/reference/variables/includesRuntime.md new file mode 100644 index 0000000000..2c9b8c113d --- /dev/null +++ b/docs/reference/variables/includesRuntime.md @@ -0,0 +1,55 @@ +--- +id: includesRuntime +title: includesRuntime +--- + +```ts +const includesRuntime: object; +``` + +Defined in: [packages/db/src/query/live/includes-runtime.ts:8](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/includes-runtime.ts#L8) + +The include materializer. `include()`, `toArray()`, and `materialize()` +carry it onto the query IR, so other apps do not bundle it. + +## Type Declaration + +### createBucketFacades() + +```ts +createBucketFacades: (...args) => BucketFacadeAdapter; +``` + +#### Parameters + +##### args + +...\[`string`, `BucketFacadeCompilation`[], (`count`) => `void`\] + +#### Returns + +`BucketFacadeAdapter` + +### materializeCompilation() + +```ts +materializeCompilation: (compilation, getRootKey?) => MaterializedRootCompilation; +``` + +Compiles inline includes into the same D2 graph as their parent relation. +Collection-valued includes become inert bucket references. The public facade +adapter resolves those references after the graph reaches quiescence. + +#### Parameters + +##### compilation + +`CompilationResult` + +##### getRootKey? + +(`row`) => `unknown` + +#### Returns + +`MaterializedRootCompilation` diff --git a/docs/reference/variables/operators.md b/docs/reference/variables/operators.md index a1b51fbdda..ebe26fa5b7 100644 --- a/docs/reference/variables/operators.md +++ b/docs/reference/variables/operators.md @@ -7,6 +7,6 @@ title: operators const operators: readonly ["eq", "gt", "gte", "lt", "lte", "in", "like", "ilike", "and", "or", "not", "isNull", "isUndefined", "upper", "lower", "length", "concat", "add", "subtract", "multiply", "divide", "coalesce", "caseWhen", "count", "avg", "sum", "min", "max"]; ``` -Defined in: [packages/db/src/query/builder/functions.ts:725](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L725) +Defined in: [packages/db/src/query/builder/functions.ts:724](https://github.com/TanStack/db/blob/main/packages/db/src/query/builder/functions.ts#L724) All supported operator names in TanStack DB expressions diff --git a/docs/reference/variables/pooledLiveQueries.md b/docs/reference/variables/pooledLiveQueries.md new file mode 100644 index 0000000000..98fba00307 --- /dev/null +++ b/docs/reference/variables/pooledLiveQueries.md @@ -0,0 +1,64 @@ +--- +id: pooledLiveQueries +title: pooledLiveQueries +--- + +```ts +const pooledLiveQueries: object; +``` + +Defined in: [packages/db/src/query/pooled-live-query.ts:728](https://github.com/TanStack/db/blob/main/packages/db/src/query/pooled-live-query.ts#L728) + +Pooled live queries for framework hooks. Pass it as `pool` in a hook's +query config to serve equality-filtered queries of one shape from a shared +partition. Apps that do not pass it do not bundle pooling. + +## Type Declaration + +### createPooledLiveQuery() + +```ts +createPooledLiveQuery: (query, __namedParameters) => + | Collection, any> + | undefined; +``` + +A pooled view for a query a partition can serve, or undefined. The view is +typed as the Collection it stands in for. + +#### Parameters + +##### query + +[`BaseQueryBuilder`](../classes/BaseQueryBuilder.md) + +##### \_\_namedParameters + +###### gcTime? + +`number` = `5_000` + +#### Returns + + \| [`Collection`](../interfaces/Collection.md)\<`any`, `any`, `any`, `StandardSchemaV1`\<`unknown`, `unknown`\>, `any`\> + \| `undefined` + +### getPooledQueryIdentity() + +```ts +getPooledQueryIdentity: (query) => string | undefined; +``` + +The identity of a query a partition can serve with no residual conjunct: +its source and its `eq` fields and literals, which determine its rows. +Undefined for any other query, which keeps the full structural identity. + +#### Parameters + +##### query + +[`BaseQueryBuilder`](../classes/BaseQueryBuilder.md) + +#### Returns + +`string` \| `undefined` diff --git a/examples/react-native/shopping-list/src/components/ListsScreen.tsx b/examples/react-native/shopping-list/src/components/ListsScreen.tsx index d46b277644..bd8c4dcf14 100644 --- a/examples/react-native/shopping-list/src/components/ListsScreen.tsx +++ b/examples/react-native/shopping-list/src/components/ListsScreen.tsx @@ -10,7 +10,7 @@ import { View, } from 'react-native' import { useRouter } from 'expo-router' -import { count, eq, useLiveQuery } from '@tanstack/react-db' +import { count, eq, include, useLiveQuery } from '@tanstack/react-db' import { itemsCollection, listsCollection } from '../db/collections' import { useShopping } from '../db/ShoppingContext' @@ -116,26 +116,32 @@ export function ListsScreen() { name: list.name, createdAt: list.createdAt, $synced: list.$synced, - totalItems: q - .from({ item: itemsCollection }) - .where(({ item }) => eq(item.listId, list.id)) - .select(({ item }) => ({ n: count(item.id) })), - uncheckedPreview: q - .from({ item: itemsCollection }) - .where(({ item }) => eq(item.listId, list.id)) - .where(({ item }) => eq(item.checked, false)) - .select(({ item }) => ({ - id: item.id, - text: item.text, - createdAt: item.createdAt, - })) - .orderBy(({ item }) => item.createdAt, `asc`) - .limit(3), - checkedItems: q - .from({ item: itemsCollection }) - .where(({ item }) => eq(item.listId, list.id)) - .where(({ item }) => eq(item.checked, true)) - .select(({ item }) => ({ n: count(item.id) })), + totalItems: include( + q + .from({ item: itemsCollection }) + .where(({ item }) => eq(item.listId, list.id)) + .select(({ item }) => ({ n: count(item.id) })), + ), + uncheckedPreview: include( + q + .from({ item: itemsCollection }) + .where(({ item }) => eq(item.listId, list.id)) + .where(({ item }) => eq(item.checked, false)) + .select(({ item }) => ({ + id: item.id, + text: item.text, + createdAt: item.createdAt, + })) + .orderBy(({ item }) => item.createdAt, `asc`) + .limit(3), + ), + checkedItems: include( + q + .from({ item: itemsCollection }) + .where(({ item }) => eq(item.listId, list.id)) + .where(({ item }) => eq(item.checked, true)) + .select(({ item }) => ({ n: count(item.id) })), + ), })) .orderBy(({ list }) => list.createdAt, `desc`), }) diff --git a/examples/react/projects/README.md b/examples/react/projects/README.md index 502a2aa961..7b1d62065b 100644 --- a/examples/react/projects/README.md +++ b/examples/react/projects/README.md @@ -242,20 +242,21 @@ const AddTodo = () => { Use live queries to read data reactively across collections: ```tsx -import { useLiveQuery } from '@tanstack/react-db' +import { eq, useLiveQuery } from '@tanstack/react-db' const Todos = () => { // Read data using live queries with cross-collection joins const { data: todos } = useLiveQuery((query) => query .from({ t: todoCollection }) - .join({ - type: 'inner', - from: { l: listCollection }, - on: [`@l.id`, `=`, `@t.list_id`], - }) - .where('@l.active', '=', true) - .select('@t.id', '@t.text', '@t.status', '@l.name') + .innerJoin({ l: listCollection }, ({ t, l }) => eq(l.id, t.list_id)) + .where(({ l }) => eq(l.active, true)) + .select(({ t, l }) => ({ + id: t.id, + text: t.text, + status: t.status, + name: l.name, + })) ) return ( diff --git a/package.json b/package.json index 85af542017..76c98d2260 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "size:compare": "node scripts/size-compare.mjs", "typecheck:tests": "pnpm --filter @tanstack/db-ivm --filter @tanstack/offline-transactions typecheck", "test:docs": "node scripts/verify-links.ts", + "test:doc-snippets": "node scripts/check-doc-snippets.mjs", "test:sherif": "sherif -i zod -p offline-transactions-react-native -p shopping-list-react-native", "generate-docs": "node scripts/generate-docs.ts" }, diff --git a/packages/angular-db/skills/angular-db/SKILL.md b/packages/angular-db/skills/angular-db/SKILL.md index 8464571ca5..e7fdc38ff5 100644 --- a/packages/angular-db/skills/angular-db/SKILL.md +++ b/packages/angular-db/skills/angular-db/SKILL.md @@ -188,7 +188,7 @@ Angular 16 structural directives: ## Includes (Hierarchical Data) -When a query uses includes (subqueries in `select`), each child field is a live `Collection` by default. Subscribe to it with `injectLiveQuery` in a child component: +When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `injectLiveQuery` in a child component: ```typescript @Component({ @@ -209,10 +209,12 @@ export class ProjectListComponent { q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issuesCollection }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ id: i.id, title: i.title })), + issues: include( + q + .from({ i: issuesCollection }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), })), ) } diff --git a/packages/angular-db/src/index.ts b/packages/angular-db/src/index.ts index 4f6b0f1c6d..981cef4b12 100644 --- a/packages/angular-db/src/index.ts +++ b/packages/angular-db/src/index.ts @@ -8,6 +8,7 @@ import { } from '@angular/core' import { BaseQueryBuilder, + configPool, createLiveQueryObserver, getPublicCollection, isCollection, @@ -21,10 +22,11 @@ import type { GetResult, InferResultType, InitialQueryBuilder, - LiveQueryCollectionConfig, + LiveQueryHookConfig, LiveQueryObserver, LiveQueryPersistedStatus, NonSingleResult, + PooledLiveQueries, QueryBuilder, SingleResult, } from '@tanstack/db' @@ -121,6 +123,8 @@ export function injectLiveQuery< TParams extends any, >(options: { params: () => TParams + /** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */ + pool?: PooledLiveQueries query: (args: { params: TParams q: InitialQueryBuilder @@ -131,6 +135,8 @@ export function injectLiveQuery< TParams extends any, >(options: { params: () => TParams + /** Pass `pooledLiveQueries` to share one partition among equal-shape queries. */ + pool?: PooledLiveQueries query: (args: { params: TParams q: InitialQueryBuilder @@ -145,7 +151,7 @@ export function injectLiveQuery( ) => QueryBuilder | undefined | null, ): InjectConditionalLiveQueryResult export function injectLiveQuery( - config: LiveQueryCollectionConfig, + config: LiveQueryHookConfig, ): InjectLiveQueryResult // Pre-created collection without singleResult export function injectLiveQuery< @@ -194,19 +200,17 @@ export function injectLiveQuery(opts: any) { typeof opts.params === `function` if (isReactiveQueryOptions) { - const { params, query } = opts - const currentParams = params() - - // Check if query function returns null/undefined (disabled query) - const queryBuilder = new BaseQueryBuilder() as InitialQueryBuilder - const result = query({ params: currentParams, q: queryBuilder }) - - if (result === undefined || result === null) { - // Disabled query - return null - return null - } - - return resolveLiveQueryValue(result, { gcTime: DEFAULT_GC_TIME_MS }) + // The pool is checked first, so a disabled query rejects it too. + const pool = configPool(opts) + // A query function returning null or undefined disables the query. + const result = opts.query({ + params: opts.params(), + q: new BaseQueryBuilder() as InitialQueryBuilder, + }) + return resolveLiveQueryValue(result, { + gcTime: DEFAULT_GC_TIME_MS, + pool, + }) } // Handle LiveQueryCollectionConfig objects. Default startSync/gcTime to diff --git a/packages/angular-db/tests/conformance.test.ts b/packages/angular-db/tests/conformance.test.ts index 1475e9580e..7cdea9f275 100644 --- a/packages/angular-db/tests/conformance.test.ts +++ b/packages/angular-db/tests/conformance.test.ts @@ -9,11 +9,12 @@ * * All registered laws must pass; the driver has no whole-test waivers. */ -import { describe, expect, it } from 'vitest' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { Component, DestroyRef, EnvironmentInjector, + ErrorHandler, createEnvironmentInjector, effect, inject, @@ -29,6 +30,7 @@ import { createLiveQueryCollection, createOptimisticAction, eq, + groupKeys, gt, sum, } from '@tanstack/db' @@ -42,6 +44,7 @@ import { createPublicationLog } from '../../db/tests/conformance/publication-log import { makeLaggingSource } from '../../db/tests/conformance/lagging-source' import { expectResultSurface } from '../../db/tests/conformance/result-laws' import { withScopeSetup } from '../../db/tests/conformance/scope-setup' +import type { PooledLiveQueries } from '@tanstack/db' import type { ConformanceResult, ControllableHandle, @@ -147,10 +150,53 @@ async function settle() { await new Promise((resolve) => setTimeout(resolve, 50)) } +// Angular reports an error that a binding's root effect throws, such as a +// rejected query input, to the application ErrorHandler. The driver records +// them, and a handle rethrows the first one reported since it mounted on its +// next read or flush: that is the rejection the contract observes. Mounts that +// fail run one at a time, so this attributes each error to its mount. Every +// recorded error must be claimed by a read before its test ends, so an error +// that no scenario expects still fails its test. +const reported: Array<{ error: unknown; claimed: boolean }> = [] +const reportedSince = new WeakMap() + +beforeEach(() => { + reported.length = 0 + TestBed.resetTestingModule() + TestBed.configureTestingModule({ + rethrowApplicationErrors: false, + providers: [ + { + provide: ErrorHandler, + useValue: { + handleError: (error: unknown) => + reported.push({ error, claimed: false }), + }, + }, + ], + }) +}) + +afterEach(() => { + expect( + reported.filter(({ claimed }) => !claimed).map(({ error }) => error), + `errors reported to ErrorHandler that no read observed`, + ).toEqual([]) +}) + +function rethrowReported(result: any): void { + const entry = reported.slice(reportedSince.get(result) ?? 0)[0] + if (entry) { + entry.claimed = true + throw entry.error + } +} + function makeHandle(result: any, destroy: () => void): LiveQueryHandle { return { collection: () => result.collection(), current(): ConformanceResult { + rethrowReported(result) return expectResultSurface({ data: result.data(), state: result.state(), @@ -164,10 +210,14 @@ function makeHandle(result: any, destroy: () => void): LiveQueryHandle { isEnabled: result.status() !== `disabled`, }) }, - flush: settle, + async flush() { + await settle() + rethrowReported(result) + }, async apply(fn: () => void) { fn() await settle() + rethrowReported(result) }, unmount() { destroy() @@ -177,6 +227,7 @@ function makeHandle(result: any, destroy: () => void): LiveQueryHandle { function inCtx(fn: () => any): { result: any; destroy: () => void } { const parent = TestBed.inject(EnvironmentInjector) + const since = reported.length const injector = createEnvironmentInjector([], parent) let result: any withScopeSetup( @@ -186,6 +237,7 @@ function inCtx(fn: () => any): { result: any; destroy: () => void } { }), () => injector.destroy(), ) + if (result && typeof result === `object`) reportedSince.set(result, since) return { result, destroy: () => injector.destroy() } } @@ -199,7 +251,10 @@ function mountCollection(collection: any) { return makeHandle(result, destroy) } -function mountConfig(build: QueryBuild, opts?: { gcTime?: number }) { +function mountConfig( + build: QueryBuild, + opts?: { gcTime?: number; pool?: PooledLiveQueries }, +) { const { result, destroy } = inCtx(() => injectLiveQuery({ query: build, ...opts }), ) @@ -214,6 +269,7 @@ function mountDisabled() { function mountControllable

( build: (q: any, param: P) => any, initial: P, + opts?: { pool?: PooledLiveQueries }, ): ControllableHandle

{ const param = signal

(initial) const published = createPublicationLog() @@ -221,10 +277,11 @@ function mountControllable

( const query = injectLiveQuery({ params: () => ({ value: param() }), query: ({ params, q }: any) => build(q, params.value), + pool: opts?.pool, }) // Angular renders the value each effect run reads. effect(() => { - published.record(query.data()) + published.record(query.data(), query.state()) }) return query }) @@ -232,12 +289,13 @@ function mountControllable

( return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(next: P) { param.set(next) // The first paint after the change: the framework has flushed its // reactive work, but no later task has run. TestBed.tick() - published.record(result.data()) + published.record(result.data(), result.state()) await settle() }, } @@ -248,7 +306,7 @@ const angularDriver: LiveQueryDriver = { // Reactive reads decide when the query compiles again. comparesQueryIdentity: false, disabledRepresentation: `empty-reactive`, - ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + ops: { eq, gt, count, sum, coalesce, groupKeys, createOptimisticAction }, makeSource, makeDeferredSource, makeLaggingSource: () => diff --git a/packages/angular-db/tests/inject-live-query.test.ts b/packages/angular-db/tests/inject-live-query.test.ts index 1c90a76025..d8343c6c92 100644 --- a/packages/angular-db/tests/inject-live-query.test.ts +++ b/packages/angular-db/tests/inject-live-query.test.ts @@ -6,6 +6,7 @@ import { createLiveQueryCollection, eq, gt, + pooledLiveQueries, } from '@tanstack/db' import { injectLiveQuery } from '../src/index' import { mockSyncCollectionOptions } from '../../db/tests/utils' @@ -613,6 +614,48 @@ describe(`injectLiveQuery`, () => { }) }) + it(`pools a reactive params query only when it passes pooledLiveQueries`, async () => { + await TestBed.runInInjectionContext(async () => { + const source = (id: string) => + createCollection( + mockSyncCollectionOptions({ + id, + getKey: (person: Person) => person.id, + initialData: initialPersons, + }), + ) + const query = + (collection: Collection) => + ({ params, q }: { params: { team: number }; q: any }) => + q + .from({ persons: collection }) + .where(({ persons }: any) => eq(persons.team, `team${params.team}`)) + const pooledSource = source(`reactive-params-pooled-angular`) + const compiledSource = source(`reactive-params-compiled-angular`) + const results = [1, 2].flatMap((team) => [ + injectLiveQuery({ + params: () => ({ team }), + query: query(pooledSource), + pool: pooledLiveQueries, + }), + injectLiveQuery({ + params: () => ({ team }), + query: query(compiledSource), + }), + ]) + + await waitForAngularUpdate() + for (const result of results) result.data() + + // Pooled queries of one shape share the partition's one subscription. + expect(pooledSource.subscriberCount).toBe(1) + expect(compiledSource.subscriberCount).toBe(2) + const ids = (index: number) => + (results[index]!.data() as Array).map((row) => row.id) + expect(ids(0)).toEqual(ids(1)) + }) + }) + it(`accepts reactive params with query function`, async () => { await TestBed.runInInjectionContext(async () => { const collection = createCollection( diff --git a/packages/db-collection-e2e/README.md b/packages/db-collection-e2e/README.md index ffb74ddc1e..1bd5a1044b 100644 --- a/packages/db-collection-e2e/README.md +++ b/packages/db-collection-e2e/README.md @@ -330,8 +330,10 @@ it('should join Users and Posts', async () => { const query = createLiveQueryCollection((q) => q .from({ user: usersCollection }) - .join({ post: postsCollection }, ({ user, post }) => - eq(user.id, post.userId), + .join( + leftJoin({ post: postsCollection }, ({ user, post }) => + eq(user.id, post.userId), + ), ) .select(({ user, post }) => ({ id: post.id, diff --git a/packages/db-collection-e2e/src/suites/joins-oracle.suite.ts b/packages/db-collection-e2e/src/suites/joins-oracle.suite.ts index 004ea4acf7..4958bb894d 100644 --- a/packages/db-collection-e2e/src/suites/joins-oracle.suite.ts +++ b/packages/db-collection-e2e/src/suites/joins-oracle.suite.ts @@ -3,10 +3,10 @@ * * Tests multi-collection joins with various syncMode combinations */ - import { randomUUID } from 'node:crypto' -import { describe, expect, it, vi } from 'vitest' import { createLiveQueryCollection, eq, gt, isNull } from '@tanstack/db' + +import { describe, expect, it, vi } from 'vitest' import { waitFor, waitForQueryData } from '../utils/helpers' import type { Comment, diff --git a/packages/db-sqlite-persistence-core/tests/persisted-readiness-oracle.test.ts b/packages/db-sqlite-persistence-core/tests/persisted-readiness-oracle.test.ts index 19157c9c2e..6753248b99 100644 --- a/packages/db-sqlite-persistence-core/tests/persisted-readiness-oracle.test.ts +++ b/packages/db-sqlite-persistence-core/tests/persisted-readiness-oracle.test.ts @@ -1,4 +1,3 @@ -import { describe, expect, it, vi } from 'vitest' import { DbClient, PERSISTED_READINESS, @@ -9,6 +8,7 @@ import { getPersistedReadinessSource, waitForPersistedInitialRender, } from '@tanstack/db' +import { describe, expect, it, vi } from 'vitest' import { persistedCollectionOptions } from '../src/index.js' import type { PersistenceAdapter } from '../src/index.js' diff --git a/packages/db/mangle-cache.json b/packages/db/mangle-cache.json index acc9210bbf..7069541802 100644 --- a/packages/db/mangle-cache.json +++ b/packages/db/mangle-cache.json @@ -415,5 +415,6 @@ "cancelRepairRetry": "cG", "coverSource": "fN", "windowRequestGeneration": "av", - "latestWindowRequest": "gE" + "latestWindowRequest": "gE", + "includesRuntimeCache": "gT" } diff --git a/packages/db/skills/db-core/live-queries/SKILL.md b/packages/db/skills/db-core/live-queries/SKILL.md index 78b2a9395c..dcadb2d7a5 100644 --- a/packages/db/skills/db-core/live-queries/SKILL.md +++ b/packages/db/skills/db-core/live-queries/SKILL.md @@ -118,7 +118,7 @@ non-null value. Join conditions accept `eq()` or a nonempty, possibly nested `and()` of equalities (equality only -- IVM constraint). Default join type is `left`. Convenience methods: `leftJoin`, `rightJoin`, `innerJoin`, `fullJoin`. ```ts -import { eq } from '@tanstack/db' +import { createLiveQueryCollection, eq } from '@tanstack/db' const userPosts = createLiveQueryCollection((q) => q @@ -145,7 +145,7 @@ q.from({ user: usersCollection }) ### 3. Aggregation with groupBy + having -Use `groupBy` to group rows, then aggregate in `select`. Filter groups with `having`. The `$selected` namespace lets `having` and `orderBy` reference fields defined in `select`. +Use `groupBy` to group rows, then aggregate in `select`. Filter groups with `having`. The `$selected` namespace lets `having` and `orderBy` reference fields defined in `select`. A `groupBy` with no aggregate wraps its keys in `groupKeys()`: `.groupBy(groupKeys(({ order }) => order.status))`; without it the query throws `GroupingOperatorRequiredError`. A `groupKeys()` built outside `.groupBy()` takes the context as a type argument, `groupKeys>(...)`; without it the callback is untyped. ```ts import { count, sum, gt } from '@tanstack/db' @@ -235,29 +235,31 @@ Embed a correlated subquery inside `select()` to produce hierarchical (nested) data. The subquery must contain a `where` with an `eq()` that correlates a parent field with a child field. -### Collection includes (default) +### Collection includes with include() Return a child `Collection` on each parent row: ```ts -import { eq, createLiveQueryCollection } from '@tanstack/db' +import { createLiveQueryCollection, eq, include } from '@tanstack/db' const projectsWithIssues = createLiveQueryCollection((q) => q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issuesCollection }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issuesCollection }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) // Each row's `issues` is a live Collection -for (const project of projectsWithIssues) { +for (const project of projectsWithIssues.toArray) { console.log(project.name, project.issues.toArray) } ``` @@ -342,7 +344,7 @@ const messagesWithContent = createLiveQueryCollection((q) => Like `toArray()`, it must be a top-level value in `select()` and cannot be nested inside `coalesce()`, `eq()`, or another expression. - `concat(toArray())` requires a **scalar** `select` to concatenate into a string. -- Collection includes (bare subquery) require an **object** `select`. +- Collection includes (`include(subquery)`) require an **object** `select`. A bare subquery in `select` throws `IncludeOperatorRequiredError`. - Includes subqueries are compiled into the same incremental pipeline as the parent query -- they are not separate live queries. ## One-Shot Queries with queryOnce diff --git a/packages/db/src/errors.ts b/packages/db/src/errors.ts index 221be72d63..6f6b002db6 100644 --- a/packages/db/src/errors.ts +++ b/packages/db/src/errors.ts @@ -610,6 +610,16 @@ export class InvalidSourceError extends QueryBuilderError { } } +export class IncludeOperatorRequiredError extends QueryBuilderError { + constructor(field: string) { + super( + devBuild() && process.env.NODE_ENV !== `production` + ? `A subquery in select() needs include(), toArray(), or materialize(). Wrap the ${field} field: ${field}: include(q.from({ i: issues }).where(...)). Import include from @tanstack/db. Query IR built by hand passes includesRuntime to each IR.IncludesSubquery.` + : codedMessage(248, { field }), + ) + } +} + export type SourceClauseContext = `from clause` | `unionAll clause` | `join clause` @@ -716,6 +726,16 @@ export class FnSelectWithGroupByError extends QueryCompilationError { } } +export class GroupingOperatorRequiredError extends QueryCompilationError { + constructor() { + super( + devBuild() && process.env.NODE_ENV !== `production` + ? `groupBy() without an aggregate needs groupKeys(), such as .groupBy(groupKeys(({ todo }) => todo.status)). Import groupKeys from @tanstack/db. A query with an aggregate such as count() needs nothing extra. Query IR built by hand passes groupingCompiler to each IR.Aggregate.` + : codedMessage(247), + ) + } +} + export class UnsupportedFnSelectResultError extends QueryCompilationError { constructor(valueDescription: string) { super( @@ -1194,3 +1214,14 @@ export class SourceCollectionIdReuseError extends TanStackDBError { this.name = `SourceCollectionIdReuseError` } } + +export class InvalidPoolOptionError extends TanStackDBError { + constructor() { + super( + devBuild() && process.env.NODE_ENV !== `production` + ? `A live query's pool option takes pooledLiveQueries from @tanstack/db, or nothing. Pooling is opt-in: pass pool: pooledLiveQueries to share one partition among equality-filtered queries.` + : codedMessage(251), + ) + this.name = `InvalidPoolOptionError` + } +} diff --git a/packages/db/src/live-query-adapter.ts b/packages/db/src/live-query-adapter.ts index 9fffe4bbd2..f4a827b1c6 100644 --- a/packages/db/src/live-query-adapter.ts +++ b/packages/db/src/live-query-adapter.ts @@ -22,6 +22,7 @@ export * from './live-query-primitives.js' export * from './live-query-observer.js' export * from './live-query-window-binding.js' export { + configPool, getLiveQueryHash, getPreparedLiveQueryIdentity, getPreparedLiveQuerySources, diff --git a/packages/db/src/live-query-observer.ts b/packages/db/src/live-query-observer.ts index c907d089ed..006fcc798c 100644 --- a/packages/db/src/live-query-observer.ts +++ b/packages/db/src/live-query-observer.ts @@ -5,7 +5,7 @@ import { isSingleResultCollection, } from './live-query-primitives.js' import { getBuilderFromConfig } from './query/live/collection-registry.js' -import { getPooledLiveQuerySource } from './query/pooled-live-query.js' +import { getPooledLiveQuerySource } from './query/pooled-source.js' import { getPersistedReadinessSource } from './persisted-readiness.js' import type { Collection } from './collection/index.js' import type { DbClient, DehydratedLiveQueryResult } from './client.js' diff --git a/packages/db/src/live-query-options.ts b/packages/db/src/live-query-options.ts index cbdc2dba58..d538320f7a 100644 --- a/packages/db/src/live-query-options.ts +++ b/packages/db/src/live-query-options.ts @@ -6,26 +6,42 @@ import { UNSUBSCRIBED_GC_FLOOR_MS, } from './collection/lifecycle.js' import { createLiveQueryCollection } from './query/live-query-collection.js' -import { - createPooledLiveQuery, - getPooledQueryIdentity, -} from './query/pooled-live-query.js' import { getStableQueryBuilderHash, getStableValueHash, } from './query/ir-stable-identity.js' import { getStringCollationIdentity } from './query/runtime-reference-identity.js' import { codedMessage, devBuild } from './error-message.js' +import { InvalidPoolOptionError } from './errors.js' import type { Collection, CollectionImpl } from './collection/index.js' import type { CollectionOptionsIdentity } from './collection-options.js' import type { CollectionOptions, DbClient } from './client.js' +import type { PooledLiveQueries } from './query/pooled-live-query.js' import type { Context, InitialQueryBuilder, LiveQueryCollectionConfig, + LiveQueryHookConfig, QueryBuilder, } from './query/index.js' +/** + * A hook config's pool: `pooledLiveQueries` or nothing. Any other value, such + * as the removed boolean form, contradicts the hook contract, so every reader + * rejects it the same way, whatever the config's other options. An adapter + * that reads the pool from its own options, as Angular does, checks it here. + */ +export function configPool(config: object): PooledLiveQueries | undefined { + const { pool } = config as { pool?: unknown } + if ( + pool !== undefined && + typeof (pool as Partial | null) + ?.createPooledLiveQuery !== `function` + ) + throw new InvalidPoolOptionError() + return pool as PooledLiveQueries | undefined +} + export type LiveQueryKey = ReadonlyArray export type LiveQueryOptions = LiveQueryCollectionConfig & { @@ -114,7 +130,7 @@ export function resumeDeferredLiveQueryCollections( } type PreparedLiveQueryConfigInput = Omit< - LiveQueryCollectionConfig, + LiveQueryHookConfig, `query` > & { query: @@ -176,10 +192,12 @@ export function prepareLiveQueryValue( !(value instanceof BaseQueryBuilder) && `query` in value ) { + const pool = configPool(value) const { query, queryKey: _queryKey, client: _client, + pool: _pool, ...config } = value as PreparedLiveQueryConfigInput @@ -195,6 +213,9 @@ export function prepareLiveQueryValue( return { ...config, + // A DbClient query never pools, so its identity must not depend on + // pool: a server preload and a client hook must hash alike. + ...(pool && !dbClient ? { pool } : {}), query: preparedQuery, } } @@ -226,12 +247,15 @@ export function getPreparedLiveQuerySources( return isCollection(query) ? [query] : [] } -export function getPreparedLiveQueryIdentity(value: unknown): unknown { +export function getPreparedLiveQueryIdentity( + value: unknown, + pool?: PooledLiveQueries, +): unknown { if (isCollection(value)) return [`collection`, value.id] if (value instanceof BaseQueryBuilder) { // A pooled query's fields and literals identify its rows without // canonicalizing its whole IR on every render. - const pooled = getPooledQueryIdentity(value) + const pooled = pool?.getPooledQueryIdentity(value) return pooled === undefined ? [`query`, getStableQueryBuilderHash(value)] : [`pooled`, pooled] @@ -240,7 +264,7 @@ export function getPreparedLiveQueryIdentity(value: unknown): unknown { const config = value as LiveQueryCollectionConfig return [ `config`, - getPreparedLiveQueryIdentity(config.query), + getPreparedLiveQueryIdentity(config.query, configPool(config)), [`getKey`, config.getKey], [`schema`, config.schema], [`singleResult`, config.singleResult === true], @@ -272,6 +296,7 @@ export function getLiveQueryHash( function poolConfig( config: LiveQueryCollectionConfig, gcTime: number | undefined, + pool: PooledLiveQueries, ): Collection | undefined { if ( !(config.query instanceof BaseQueryBuilder) || @@ -279,7 +304,7 @@ function poolConfig( ) { return undefined } - return createPooledLiveQuery(config.query, { + return pool.createPooledLiveQuery(config.query, { gcTime: config.gcTime ?? gcTime, }) } @@ -294,9 +319,16 @@ export function resolveLiveQueryValue( value: unknown, { gcTime, - pool = true, + pool, startSync = true, - }: { gcTime?: number; pool?: boolean; startSync?: boolean } = {}, + }: { + gcTime?: number + // `false` keeps a hook that cannot pool, such as a Suspense or DbClient + // query, from pooling even when its config passes `pooledLiveQueries`. + // A pool here has passed `configPool`. + pool?: PooledLiveQueries | false + startSync?: boolean + } = {}, ): Collection | null { if (value === undefined || value === null) return null if (isCollection(value)) { @@ -305,14 +337,24 @@ export function resolveLiveQueryValue( } if (value instanceof BaseQueryBuilder) { return ( - (pool ? createPooledLiveQuery(value, { gcTime }) : undefined) ?? + (pool ? pool.createPooledLiveQuery(value, { gcTime }) : undefined) ?? createLiveQueryCollection({ query: value, startSync, gcTime }) ) } if (typeof value === `object`) { - const config = value as LiveQueryCollectionConfig + const requested = configPool(value) + const { pool: _pool, ...given } = value as LiveQueryHookConfig + const pooling = pool === false ? undefined : (requested ?? pool) + // A pooled view needs the built query. Build a query function once and + // compile the same builder if the partition cannot serve it. + const built = + pooling && typeof given.query === `function` + ? given.query(createInitialQueryBuilder()) + : undefined + const config = + built instanceof BaseQueryBuilder ? { ...given, query: built } : given return ( - (pool ? poolConfig(config, gcTime) : undefined) ?? + (pooling ? poolConfig(config, gcTime, pooling) : undefined) ?? createLiveQueryCollection({ ...config, startSync: config.startSync ?? startSync, diff --git a/packages/db/src/query/builder/clone-query.ts b/packages/db/src/query/builder/clone-query.ts index 750f8c5ba0..b29a0c42aa 100644 --- a/packages/db/src/query/builder/clone-query.ts +++ b/packages/db/src/query/builder/clone-query.ts @@ -138,6 +138,7 @@ function cloneSelectValueForPlacement( value.correlationField, value.childCorrelationField, value.fieldName, + value.runtime, value.parentFilters, value.parentProjection, value.materialization, diff --git a/packages/db/src/query/builder/functions.ts b/packages/db/src/query/builder/functions.ts index c25c0bf772..ab24ebc89e 100644 --- a/packages/db/src/query/builder/functions.ts +++ b/packages/db/src/query/builder/functions.ts @@ -1,17 +1,13 @@ import { Aggregate, Func, isBasicOrAggregateExpression } from '../ir' import { codedMessage, devBuild } from '../../error-message.js' +import { grouping } from '../compiler/grouping.js' import { isRefProxy, toExpression } from './ref-proxy.js' import { registerWrapper } from './wrapper-identity.js' import type { BasicExpression } from '../ir' +import type { IncludesRuntime } from '../live/includes-runtime.js' import type { RefProxy } from './ref-proxy.js' -import type { SingleResult } from '../../types.js' -import type { - Context, - GetInlineResult, - RefLeaf, - StringifiableScalar, -} from './types.js' -import type { QueryBuilder } from './index.js' +import type { Context, RefLeaf, StringifiableScalar } from './types.js' +import type { BaseQueryBuilder, QueryBuilder } from './index.js' type StringRef = RefLeaf | RefLeaf | RefLeaf @@ -46,10 +42,11 @@ type ExpressionLike = export type CaseWhenValue = | ExpressionLike - | QueryBuilder + | IncludeWrapper | ToArrayWrapper | ConcatToArrayWrapper - | Record + // A select object. A query builder is not one: wrap it in include(). + | (Record & { _bindCollectionSources?: never }) type ExtractCaseWhenValue = T extends CaseWhenWrapper ? TResult : T @@ -324,7 +321,7 @@ export function concat( : codedMessage(110), ) } - return new ConcatToArrayWrapper(toArrayArg.query) + return new ConcatToArrayWrapper(toArrayArg.query, toArrayArg.runtime) } return new Func( @@ -399,9 +396,11 @@ export function coalesce]>( * ```ts * caseWhen(gt(user.age, 18), { * ...user, - * posts: q - * .from({ post: postsCollection }) - * .where(({ post }) => eq(post.userId, user.id)), + * posts: include( + * q + * .from({ post: postsCollection }) + * .where(({ post }) => eq(post.userId, user.id)), + * ), * }) * ``` */ @@ -658,7 +657,7 @@ export function divide( // Aggregates export function count(arg: ExpressionLike): Aggregate { - return new Aggregate(`count`, [toExpression(arg)]) + return new Aggregate(`count`, [toExpression(arg)], grouping) } export function avg(arg: T): Aggregate @@ -669,7 +668,7 @@ export function avg( arg: NumericAggregateArgument, ): Aggregate export function avg(arg: ExpressionLike): Aggregate { - return new Aggregate(`avg`, [toExpression(arg)]) + return new Aggregate(`avg`, [toExpression(arg)], grouping) } export function sum(arg: T): Aggregate @@ -680,7 +679,7 @@ export function sum( arg: NumericAggregateArgument, ): Aggregate export function sum(arg: ExpressionLike): Aggregate { - return new Aggregate(`sum`, [toExpression(arg)]) + return new Aggregate(`sum`, [toExpression(arg)], grouping) } export function min(arg: T): Aggregate @@ -689,7 +688,7 @@ export function min( arg: OrderableAggregateArgument, ): Aggregate> export function min(arg: ExpressionLike): Aggregate { - return new Aggregate(`min`, [toExpression(arg)]) + return new Aggregate(`min`, [toExpression(arg)], grouping) } export function max(arg: T): Aggregate @@ -698,7 +697,7 @@ export function max( arg: OrderableAggregateArgument, ): Aggregate> export function max(arg: ExpressionLike): Aggregate { - return new Aggregate(`max`, [toExpression(arg)]) + return new Aggregate(`max`, [toExpression(arg)], grouping) } /** @@ -766,7 +765,10 @@ export class ToArrayWrapper<_T = unknown> { readonly __brand = `ToArrayWrapper` as const declare readonly _type: `toArray` declare readonly _result: _T - constructor(public readonly query: QueryBuilder) { + constructor( + public readonly query: QueryBuilder, + public readonly runtime: IncludesRuntime, + ) { registerWrapper(this, `toArray()`) } } @@ -775,7 +777,10 @@ export class ConcatToArrayWrapper<_T = unknown> { readonly __brand = `ConcatToArrayWrapper` as const declare readonly _type: `concatToArray` declare readonly _result: _T - constructor(public readonly query: QueryBuilder) { + constructor( + public readonly query: QueryBuilder, + public readonly runtime: IncludesRuntime, + ) { registerWrapper(this, `concat(toArray())`) } } @@ -797,17 +802,14 @@ export class MaterializeWrapper< declare readonly _type: `materialize` declare readonly _result: _T declare readonly _isSingle: _IsSingle - constructor(public readonly query: QueryBuilder) { + constructor( + public readonly query: QueryBuilder, + public readonly runtime: IncludesRuntime, + ) { registerWrapper(this, `materialize()`) } } -export function toArray( - query: QueryBuilder, -): ToArrayWrapper> { - return new ToArrayWrapper(query) -} - function caseWhenHasOnlyExpressionValues(args: Array): boolean { const valueIndexes = getCaseWhenValueIndexes(args.length) return valueIndexes.every((index) => isExpressionValue(args[index])) @@ -849,41 +851,13 @@ function isConditionValue(value: CaseWhenValue | undefined): boolean { return isExpressionValue(value) && !Array.isArray(value) } -/** - * Materialize an includes subquery into a plain value on the parent row. - * - * - For multi-row subqueries, the parent receives an `Array` snapshot - * (equivalent to `toArray()`). - * - For `findOne()` subqueries, the parent receives a single `T | undefined` - * value — `undefined` when no child matches. - * - * The snapshot updates reactively: parent rows re-emit when the underlying - * children change. - * - * @example - * ```ts - * // Multi-row: produces Array on each project - * select(({ p }) => ({ - * ...p, - * issues: materialize( - * q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), - * ), - * })) - * - * // Singleton: produces Author | undefined on each post - * select(({ p }) => ({ - * ...p, - * author: materialize( - * q.from({ a: authors }).where(({ a }) => eq(a.id, p.authorId)).findOne(), - * ), - * })) - * ``` - */ -export function materialize( - query: QueryBuilder, -): MaterializeWrapper< - GetInlineResult, - TContext extends SingleResult ? true : false -> { - return new MaterializeWrapper(query) +export class IncludeWrapper<_TContext extends Context = any> { + readonly __brand = `IncludeWrapper` as const + declare readonly _context: _TContext + constructor( + public readonly query: BaseQueryBuilder, + public readonly runtime: IncludesRuntime, + ) { + registerWrapper(this, `include()`) + } } diff --git a/packages/db/src/query/builder/group-keys.ts b/packages/db/src/query/builder/group-keys.ts new file mode 100644 index 0000000000..f45df0b983 --- /dev/null +++ b/packages/db/src/query/builder/group-keys.ts @@ -0,0 +1,18 @@ +import { grouping } from '../compiler/grouping.js' +import type { Grouping } from '../compiler/grouping.js' +import type { Context, GroupByCallback, OperatorCallback } from './types.js' + +/** + * Group keys for `query.groupBy(...)` when the query has no aggregate. + * Aggregates such as `count` bring the grouping compiler themselves. + */ +export interface GroupKeys { + callback: GroupByCallback + grouping: Grouping +} + +export function groupKeys( + callback: OperatorCallback>, +): GroupKeys { + return { callback, grouping } +} diff --git a/packages/db/src/query/builder/include-functions.ts b/packages/db/src/query/builder/include-functions.ts new file mode 100644 index 0000000000..07e227812f --- /dev/null +++ b/packages/db/src/query/builder/include-functions.ts @@ -0,0 +1,82 @@ +import { includesRuntime } from '../live/includes-runtime.js' +import { + IncludeWrapper, + MaterializeWrapper, + ToArrayWrapper, +} from './functions.js' +import type { SingleResult } from '../../types.js' +import type { BaseQueryBuilder, QueryBuilder } from './index.js' +import type { Context, GetInlineResult } from './types.js' + +// The include functions carry the include materializer. They live apart from +// the expression functions, which core modules import, so only apps that +// include subqueries bundle it. + +export function toArray( + query: QueryBuilder, +): ToArrayWrapper> { + return new ToArrayWrapper(query, includesRuntime) +} + +/** + * Materialize an includes subquery into a plain value on the parent row. + * + * - For multi-row subqueries, the parent receives an `Array` snapshot + * (equivalent to `toArray()`). + * - For `findOne()` subqueries, the parent receives a single `T | undefined` + * value — `undefined` when no child matches. + * + * The snapshot updates reactively: parent rows re-emit when the underlying + * children change. + * + * @example + * ```ts + * // Multi-row: produces Array on each project + * select(({ p }) => ({ + * ...p, + * issues: materialize( + * q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + * ), + * })) + * + * // Singleton: produces Author | undefined on each post + * select(({ p }) => ({ + * ...p, + * author: materialize( + * q.from({ a: authors }).where(({ a }) => eq(a.id, p.authorId)).findOne(), + * ), + * })) + * ``` + */ +export function materialize( + query: QueryBuilder, +): MaterializeWrapper< + GetInlineResult, + TContext extends SingleResult ? true : false +> { + return new MaterializeWrapper(query, includesRuntime) +} + +/** + * Includes a correlated subquery as a live child Collection on each parent + * row. Use `toArray()` or `materialize()` for inline values instead. + * + * @example + * ```ts + * select(({ p }) => ({ + * ...p, + * issues: include( + * q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + * ), + * })) + * ``` + */ +export function include( + query: QueryBuilder, +): IncludeWrapper { + // Every QueryBuilder is a BaseQueryBuilder; the public type hides internals. + return new IncludeWrapper( + query as unknown as BaseQueryBuilder, + includesRuntime, + ) +} diff --git a/packages/db/src/query/builder/index.ts b/packages/db/src/query/builder/index.ts index 992192a539..62dbbfec0f 100644 --- a/packages/db/src/query/builder/index.ts +++ b/packages/db/src/query/builder/index.ts @@ -18,6 +18,7 @@ import { isExpressionLike, } from '../ir.js' import { + IncludeOperatorRequiredError, InvalidSourceError, InvalidSourceTypeError, InvalidWhereExpressionError, @@ -38,10 +39,13 @@ import { import { CaseWhenWrapper, ConcatToArrayWrapper, + IncludeWrapper, MaterializeWrapper, ToArrayWrapper, } from './functions.js' import type { CollectionResolver } from './clone-query.js' +import type { GroupKeys } from './group-keys.js' +import type { IncludesRuntime } from '../live/includes-runtime.js' import type { SourceClauseContext } from '../../errors.js' import type { NamespacedRow, SingleResult } from '../../types.js' import type { @@ -80,6 +84,7 @@ import type { Source, WhereCallback, WithResult, + WithoutBareSubquery, } from './types.js' const UNION_ALL_SOURCE_CONTEXT = `unionAll clause` satisfies SourceClauseContext @@ -634,7 +639,9 @@ export class BaseQueryBuilder { ) => NonScalarSelectObject, ): QueryBuilder>> select( - callback: (refs: RefsForContext) => TSelectValue, + callback: ( + refs: RefsForContext, + ) => WithoutBareSubquery, ): QueryBuilder>> select( callback: ( @@ -754,7 +761,9 @@ export class BaseQueryBuilder { /** * Group rows by one or more columns for aggregation * - * @param callback - A function that receives table references and returns the field(s) to group by + * @param keys - A function that receives table references and returns the + * field(s) to group by. A query with no aggregate wraps it in `groupKeys()`; + * aggregates such as `count()` bring the grouping code themselves. * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING) * * @example @@ -777,9 +786,18 @@ export class BaseQueryBuilder { * category: sales.category, * totalSales: sum(sales.amount) * })) + * + * // Group without an aggregate + * query + * .from({ orders: ordersCollection }) + * .groupBy(groupKeys(({orders}) => orders.status)) + * .select(({orders}) => ({ status: orders.status })) * ``` */ - groupBy(callback: GroupByCallback): QueryBuilder { + groupBy( + keys: GroupByCallback | GroupKeys, + ): QueryBuilder { + const callback = typeof keys === `function` ? keys : keys.callback const aliases = this._getCurrentAliases() const refProxy = createRefProxy( aliases, @@ -796,6 +814,7 @@ export class BaseQueryBuilder { return this._clone({ ...this.query, groupBy: [...existingGroupBy, ...newExpressions], + ...(typeof keys === `function` ? {} : { grouping: keys.grouping }), }) as any } @@ -1107,7 +1126,7 @@ function buildNestedSelect( ) : toExpr(obj) } - if (obj instanceof BaseQueryBuilder) { + if (obj instanceof BaseQueryBuilder || obj instanceof IncludeWrapper) { if (!fieldName) { throw new Error( devBuild() && process.env.NODE_ENV !== `production` @@ -1115,7 +1134,22 @@ function buildNestedSelect( : codedMessage(114), ) } - return buildIncludesSubquery(obj, fieldName, parentScope, `collection`) + if (obj instanceof BaseQueryBuilder) + throw new IncludeOperatorRequiredError(fieldName) + if (!(obj.query instanceof BaseQueryBuilder)) { + throw new Error( + devBuild() && process.env.NODE_ENV !== `production` + ? `include() must wrap a subquery builder` + : codedMessage(249), + ) + } + return buildIncludesSubquery( + obj.query, + fieldName, + parentScope, + `collection`, + obj.runtime, + ) } if (obj instanceof ToArrayWrapper) { if (!(obj.query instanceof BaseQueryBuilder)) { @@ -1132,7 +1166,13 @@ function buildNestedSelect( : codedMessage(116), ) } - return buildIncludesSubquery(obj.query, fieldName, parentScope, `array`) + return buildIncludesSubquery( + obj.query, + fieldName, + parentScope, + `array`, + obj.runtime, + ) } if (obj instanceof ConcatToArrayWrapper) { if (!(obj.query instanceof BaseQueryBuilder)) { @@ -1149,7 +1189,13 @@ function buildNestedSelect( : codedMessage(118), ) } - return buildIncludesSubquery(obj.query, fieldName, parentScope, `concat`) + return buildIncludesSubquery( + obj.query, + fieldName, + parentScope, + `concat`, + obj.runtime, + ) } if (obj instanceof CaseWhenWrapper) { return buildConditionalSelect(obj, parentScope, fieldName) @@ -1166,8 +1212,22 @@ function buildNestedSelect( out[`${base}__${++spreadIndex}`] = v continue } - if (v instanceof BaseQueryBuilder) { - out[k] = buildIncludesSubquery(v, k, parentScope, `collection`) + if (v instanceof BaseQueryBuilder) throw new IncludeOperatorRequiredError(k) + if (v instanceof IncludeWrapper) { + if (!(v.query instanceof BaseQueryBuilder)) { + throw new Error( + devBuild() && process.env.NODE_ENV !== `production` + ? `include() must wrap a subquery builder` + : codedMessage(250), + ) + } + out[k] = buildIncludesSubquery( + v.query, + k, + parentScope, + `collection`, + v.runtime, + ) continue } if (v instanceof ToArrayWrapper) { @@ -1178,7 +1238,13 @@ function buildNestedSelect( : codedMessage(119), ) } - out[k] = buildIncludesSubquery(v.query, k, parentScope, `array`) + out[k] = buildIncludesSubquery( + v.query, + k, + parentScope, + `array`, + v.runtime, + ) continue } if (v instanceof ConcatToArrayWrapper) { @@ -1189,7 +1255,13 @@ function buildNestedSelect( : codedMessage(120), ) } - out[k] = buildIncludesSubquery(v.query, k, parentScope, `concat`) + out[k] = buildIncludesSubquery( + v.query, + k, + parentScope, + `concat`, + v.runtime, + ) continue } if (v instanceof MaterializeWrapper) { @@ -1204,7 +1276,13 @@ function buildNestedSelect( const materialization: IncludesMaterialization = childQuery.singleResult ? `singleton` : `array` - out[k] = buildIncludesSubquery(v.query, k, parentScope, materialization) + out[k] = buildIncludesSubquery( + v.query, + k, + parentScope, + materialization, + v.runtime, + ) continue } if (v instanceof CaseWhenWrapper) { @@ -1486,6 +1564,7 @@ function buildIncludesSubquery( fieldName: string, parentScope: ParentScope, materialization: IncludesMaterialization, + runtime: IncludesRuntime, ): IncludesSubquery { const sourceQuery = childBuilder._getQuery() if (reusesAncestorBinding(sourceQuery, parentScope.bindingIds)) { @@ -1683,6 +1762,7 @@ function buildIncludesSubquery( parentRef, childRef, fieldName, + runtime, parentFilters.length > 0 ? parentFilters : undefined, parentProjection, materialization, diff --git a/packages/db/src/query/builder/types.ts b/packages/db/src/query/builder/types.ts index 41a89c1179..b92af21d6f 100644 --- a/packages/db/src/query/builder/types.ts +++ b/packages/db/src/query/builder/types.ts @@ -17,6 +17,7 @@ import type { import type { CaseWhenWrapper, ConcatToArrayWrapper, + IncludeWrapper, MaterializeWrapper, ToArrayWrapper, } from './functions.js' @@ -273,7 +274,7 @@ type SelectValue = | ConcatToArrayWrapper // concat(toArray(...)) wrapped subquery | CaseWhenWrapper // conditional projection | MaterializeWrapper // materialize() wrapped subquery (Array or T | undefined) - | QueryBuilder // includes subquery (produces a child Collection) + | IncludeWrapper // include() wrapped subquery (produces a child Collection) // Recursive shape for select objects allowing nested projections type SelectShape = { [key: string]: SelectValue | SelectShape } @@ -313,10 +314,36 @@ type IsScalarSelectLike = T extends BasicExpression | Aggregate ? false : true : false +// A select value must not hold a bare subquery: it needs include(), toArray(), +// or materialize(). The open ref and user-value shapes cannot express this, so +// both select overloads check it. The check stops at wrappers and expressions, +// which hold builders of their own, and a few levels deep. +type ContainsQueryBuilder< + T, + TDepth extends Array = [], +> = TDepth[`length`] extends 4 + ? false + : IsAny extends true + ? false + : T extends QueryBuilder + ? true + : T extends BasicExpression | Aggregate | { readonly __brand: string } + ? false + : T extends object + ? true extends { + [K in keyof T]-?: ContainsQueryBuilder + }[keyof T] + ? true + : false + : false + +export type WithoutBareSubquery = + ContainsQueryBuilder extends true ? never : T + export type NonScalarSelectObject = T extends SelectObject ? IsScalarSelectLike extends true ? never - : T + : WithoutBareSubquery : never export type ResultTypeFromSelectValue = @@ -338,7 +365,7 @@ export type ResultTypeFromSelectValue = readonly _result?: infer T } ? ResultTypeFromCaseWhen - : TSelectValue extends QueryBuilder + : TSelectValue extends IncludeWrapper ? Collection> : TSelectValue extends AnyRef ? ExtractRef @@ -438,8 +465,8 @@ export type ResultTypeFromSelect = readonly _result?: infer T } ? ResultTypeFromCaseWhen - : // includes subquery (bare QueryBuilder) — produces a child Collection - TSelectObject[K] extends QueryBuilder< + : // include() subquery — produces a child Collection + TSelectObject[K] extends IncludeWrapper< infer TChildContext > ? Collection> @@ -626,6 +653,17 @@ export type GroupByCallback = ( refs: RefsForContext, ) => any +/** + * A callback that an operator value receives before the query supplies its + * context. Without noImplicitAny, TypeScript skips that inference and the + * context is `any`; the callback is then untyped, as other unannotated + * callbacks are in that mode. + */ +export type OperatorCallback< + TContext extends Context, + TCallback, +> = 0 extends 1 & TContext ? (refs: any) => any : TCallback + /** * JoinOnCallback - Type for join condition callback functions * diff --git a/packages/db/src/query/compiler/find-aggregate.ts b/packages/db/src/query/compiler/find-aggregate.ts new file mode 100644 index 0000000000..0df3036f17 --- /dev/null +++ b/packages/db/src/query/compiler/find-aggregate.ts @@ -0,0 +1,23 @@ +import { Aggregate, ConditionalSelect, Func, isExpressionLike } from '../ir.js' + +/** + * Returns the first aggregate in a select, having, or orderBy value. It does + * not enter include subqueries, which compile with their own aggregates. + * A plain object is a nested select; its fields may use any names. + */ +export function findAggregate(value: unknown): Aggregate | undefined { + if (value === null || typeof value !== `object`) return undefined + if (Array.isArray(value)) { + for (const item of value) { + const aggregate = findAggregate(item) + if (aggregate) return aggregate + } + return undefined + } + if (value instanceof Aggregate) return value + if (value instanceof Func) return findAggregate(value.args) + if (value instanceof ConditionalSelect) + return findAggregate([value.branches, value.defaultValue]) + if (isExpressionLike(value)) return undefined + return findAggregate(Object.values(value)) +} diff --git a/packages/db/src/query/compiler/group-by.ts b/packages/db/src/query/compiler/group-by.ts index 528dd30d17..d41bfcfbcf 100644 --- a/packages/db/src/query/compiler/group-by.ts +++ b/packages/db/src/query/compiler/group-by.ts @@ -287,8 +287,6 @@ function getRowVirtualMetadata(row: NamespacedRow): RowVirtualMetadata { } } -const { sum, count, avg, min, max } = groupByOperators - /** * Processes the GROUP BY clause with optional HAVING and SELECT * Works with the new $selected structure from early SELECT processing @@ -732,6 +730,10 @@ function getAggregateFunction(aggExpr: Aggregate) { return compiledExpr(namespacedRow) } + // Read the operators here: a top-level destructure counts as a side + // effect and would keep them in bundles that never aggregate. + const { sum, count, avg, min, max } = groupByOperators + // Return the appropriate aggregate function switch (aggExpr.name.toLowerCase()) { case `sum`: @@ -847,47 +849,6 @@ function evaluateWrappedAggregates( } } -/** - * Checks whether an expression contains an aggregate anywhere in its tree. - * Returns true for a top-level Aggregate, or a Func whose args (recursively) - * contain an Aggregate. Safely returns false for nested Select objects. - */ -export function containsAggregate( - expr: BasicExpression | Aggregate | Select | { type: string }, -): boolean { - if (isConditionalSelect(expr)) { - const branchHasAggregate = expr.branches.some( - (branch) => - containsAggregate(branch.condition) || containsAggregate(branch.value), - ) - - return ( - branchHasAggregate || - (expr.defaultValue !== undefined && containsAggregate(expr.defaultValue)) - ) - } - - if (isNestedSelectObject(expr)) { - return Object.values(expr).some((value) => - containsAggregate(value as BasicExpression | Aggregate | Select), - ) - } - - if (!isExpressionLike(expr)) { - return false - } - - if (expr.type === `agg`) { - return true - } - if (expr.type === `func` && `args` in expr) { - return (expr.args as Array).some( - (arg: BasicExpression | Aggregate) => containsAggregate(arg), - ) - } - return false -} - /** * Walks an expression tree containing nested aggregates. * Each Aggregate node is extracted, assigned a synthetic alias (__agg_N), diff --git a/packages/db/src/query/compiler/grouping.ts b/packages/db/src/query/compiler/grouping.ts new file mode 100644 index 0000000000..1e4280ed56 --- /dev/null +++ b/packages/db/src/query/compiler/grouping.ts @@ -0,0 +1,9 @@ +import { processGroupBy, replaceAggregatesByRefs } from './group-by.js' + +/** + * The grouping compiler. Aggregate values and `groupKeys` carry it onto the + * query IR, so apps that never group or aggregate do not bundle it. + */ +export const grouping = { processGroupBy, replaceAggregatesByRefs } + +export type Grouping = typeof grouping diff --git a/packages/db/src/query/compiler/index.ts b/packages/db/src/query/compiler/index.ts index 7fa3d8deac..3e69e8b70c 100644 --- a/packages/db/src/query/compiler/index.ts +++ b/packages/db/src/query/compiler/index.ts @@ -11,7 +11,6 @@ import { import { isPlainObject } from '../../utils/type-guards.js' import { getOrCreate } from '../../utils/get-or-create.js' import { optimizeQuery } from '../optimizer.js' -import { materializeCompilation } from '../live/materialized-pipeline.js' import { createParentContext, createValueIdentity, @@ -23,7 +22,9 @@ import { CollectionInputNotFoundError, DistinctRequiresSelectError, FnSelectWithGroupByError, + GroupingOperatorRequiredError, HavingRequiresGroupByError, + IncludeOperatorRequiredError, LimitOffsetRequireOrderByError, NonAggregateExpressionNotInGroupByError, QueryCompilationError, @@ -39,6 +40,7 @@ import { isRefProxy } from '../builder/ref-proxy-identity.js' import { CaseWhenWrapper, ConcatToArrayWrapper, + IncludeWrapper, MaterializeWrapper, ToArrayWrapper, } from '../builder/functions.js' @@ -67,7 +69,7 @@ import { toBooleanPredicate, } from './evaluators.js' import { processJoins, registerLazyDemandPlan } from './joins.js' -import { containsAggregate, processGroupBy } from './group-by.js' +import { findAggregate } from './find-aggregate.js' import { getLazyLoadTargets } from './lazy-targets.js' import { processOrderBy } from './order-by.js' import { crossJoinParentRoutes } from './parent-routes.js' @@ -86,6 +88,7 @@ import { } from './route-metadata.js' import { processSelect } from './select.js' import type { ValueIdentity } from '../equality-value-identity.js' +import type { IncludesRuntime } from '../live/includes-runtime.js' import type { CollectionSubscription } from '../../collection/subscription.js' import type { OrderByOptimizationInfo } from './order-by.js' import type { @@ -118,6 +121,7 @@ function getUnsupportedFnSelectResultDescription( value: unknown, seen: Set = new Set(), ): string | undefined { + if (value instanceof IncludeWrapper) return `include()` if (value instanceof BaseQueryBuilder) return `a child query builder` if (value instanceof ToArrayWrapper) return `toArray()` if (value instanceof ConcatToArrayWrapper) return `concat(toArray())` @@ -320,6 +324,8 @@ export interface IncludesCompilationResult { materialization: IncludesMaterialization /** Internal field used to unwrap scalar child selects */ scalarField?: string + /** The materializer the include value brought */ + runtime: IncludesRuntime } /** @@ -721,8 +727,10 @@ function compileQueryInternal( }) } } - const selectHasAggregates = - query.select !== undefined && containsAggregate(query.select) + // One walker finds aggregates; the aggregate it finds carries the grouping + // compiler. + const selectAggregate = findAggregate(query.select) + const selectHasAggregates = selectAggregate !== undefined // A windowed query's or a findOne's includes compile after its top-K and // route only from the rows it shows (law 12); others compile here. const hasOrderBy = (query.orderBy?.length ?? 0) > 0 @@ -743,6 +751,10 @@ function compileQueryInternal( query = { ...query, select: { ...query.select } } } for (const { key, path, subquery, guards } of includesEntries) { + // Query IR built by hand must carry the runtime its include needs. + const runtime = subquery.runtime as Partial | undefined + if (typeof runtime?.materializeCompilation !== `function`) + throw new IncludeOperatorRequiredError(key) const fieldName = getUniqueIncludesRoutingKey(key, includesRoutingFns) // Branch parent pipeline: map to [correlationValue, parentContext] // When parentProjection exists, project referenced parent fields; otherwise null (zero overhead) @@ -927,6 +939,7 @@ function compileQueryInternal( parentProjection: subquery.parentProjection, materialization: subquery.materialization, scalarField: subquery.scalarField, + runtime: subquery.runtime, }) } if (parentHasTopK) deferredIncludes.push(compileInclude) @@ -993,15 +1006,16 @@ function compileQueryInternal( ], ), ) as ResultStream - const materializedInput = materializeCompilation({ - pipeline: inputPipeline, - includes: includesResults, - valueIdentity, - collectionId: mainCollectionId, - sourceWhereClauses, - aliasToCollectionId, - aliasRemapping, - }) + const materializedInput = + includesResults[0]!.runtime.materializeCompilation({ + pipeline: inputPipeline, + includes: includesResults, + valueIdentity, + collectionId: mainCollectionId, + sourceWhereClauses, + aliasToCollectionId, + aliasRemapping, + }) pipeline = materializedInput.pipeline.pipe( map(([key, [value]]) => { const row = { ...value } @@ -1108,24 +1122,24 @@ function compileQueryInternal( unionRows && ref.bindingId === undefined && ref.path[0] !== `$selected` ) } - if (query.groupBy && query.groupBy.length > 0) { - pipeline = processGroupBy( - pipeline, - query.groupBy, - valueIdentity, - isSourceRef, + // An aggregate value or groupKeys brings the grouping compiler. A groupBy, + // or an aggregate anywhere, needs it; query IR built by hand may lack it. + // The query compiles every aggregate with the compiler any one brings. + const aggregate = + selectAggregate ?? + findAggregate([ query.having, - query.select, - query.fnHaving, - mainCollectionId, - groupByMainSource, - sourceCarriesInternalRouteState || includesRoutingFns.length > 0, - ) - } else if (selectHasAggregates) { - // SELECT contains aggregates but no GROUP BY: implicit single-group aggregation - pipeline = processGroupBy( + query.orderBy?.map((clause) => clause.expression), + ]) + const grouping = query.grouping ?? aggregate?.grouping + const grouped = (query.groupBy?.length ?? 0) > 0 + if ((grouped || aggregate) && !grouping) + throw new GroupingOperatorRequiredError() + // SELECT aggregates without GROUP BY make one implicit group. + if (grouping && (grouped || selectAggregate)) { + pipeline = grouping.processGroupBy( pipeline, - [], // Empty group by means single group + query.groupBy ?? [], valueIdentity, isSourceRef, query.having, @@ -1260,6 +1274,7 @@ function compileQueryInternal( // With no explicit order, top-K breaks ties by the public row key. query.orderBy ?? [], query.select || {}, + grouping, collections[mainCollectionId]!, optimizableOrderByCollections, (windowFn) => (ownWindowFn = windowFn), diff --git a/packages/db/src/query/compiler/order-by.ts b/packages/db/src/query/compiler/order-by.ts index 96ac88ed12..518a0fddad 100644 --- a/packages/db/src/query/compiler/order-by.ts +++ b/packages/db/src/query/compiler/order-by.ts @@ -20,9 +20,9 @@ import { hasVirtualPropPath } from '../../virtual-props.js' import { devBuild } from '../../error-message.js' import { compileExpression } from './evaluators.js' import { getSourceAliasesFromExpression } from './expressions.js' -import { replaceAggregatesByRefs } from './group-by.js' import type { CompareOptions } from '../builder/types.js' import type { WindowOptions } from './types.js' +import type { Grouping } from './grouping.js' import type { CompiledSingleRowExpression } from './evaluators.js' import type { OrderBy, OrderByClause, QueryIR, Select } from '../ir.js' import type { @@ -124,6 +124,8 @@ export function processOrderBy( pipeline: NamespacedAndKeyedStream, orderByClause: Array, selectClause: Select, + // The query's grouping compiler, if it has one, rewrites aggregates. + grouping: Grouping | undefined, collection: Collection, optimizableOrderByCollections: Record, setWindowFn: (windowFn: (options: WindowOptions) => void) => void, @@ -132,12 +134,15 @@ export function processOrderBy( groupKeyFn?: (key: unknown, value: unknown) => unknown, ): IStreamBuilder> { const compiledOrderBy = orderByClause.map((clause) => { - // Aggregate references read the SELECT result under $selected. - const clauseWithoutAggregates = replaceAggregatesByRefs( - clause.expression, - selectClause, - `$selected`, - ) + // Aggregate references read the SELECT result under $selected. A query + // without the grouping compiler has no aggregates. + const clauseWithoutAggregates = grouping + ? grouping.replaceAggregatesByRefs( + clause.expression, + selectClause, + `$selected`, + ) + : clause.expression return { compiledExpression: compileExpression(clauseWithoutAggregates), diff --git a/packages/db/src/query/index.ts b/packages/db/src/query/index.ts index 3c104b813c..5e7e0a1f2b 100644 --- a/packages/db/src/query/index.ts +++ b/packages/db/src/query/index.ts @@ -35,6 +35,27 @@ export { type Prettify, } from './builder/index.js' +// Pooled live queries for framework hooks +export { + pooledLiveQueries, + type PooledLiveQueries, +} from './pooled-live-query.js' + +// Grouping without aggregates +export { groupKeys, type GroupKeys } from './builder/group-keys.js' +export type { OperatorCallback } from './builder/types.js' + +// Operator code for query IR built by hand: `IR.Aggregate` takes the grouping +// compiler, and `IR.IncludesSubquery` the include runtime. +export { + grouping as groupingCompiler, + type Grouping, +} from './compiler/grouping.js' +export { + includesRuntime, + type IncludesRuntime, +} from './live/includes-runtime.js' + // Expression functions exports export { // Operators @@ -68,10 +89,8 @@ export { sum, min, max, - // Includes helpers - toArray, - materialize, } from './builder/functions.js' +export { include, materialize, toArray } from './builder/include-functions.js' // Ref proxy utilities export type { Ref } from './builder/types.js' @@ -93,7 +112,10 @@ export { // One-shot query execution export { queryOnce, type QueryOnceConfig } from './query-once.js' -export { type LiveQueryCollectionConfig } from './live/types.js' +export { + type LiveQueryCollectionConfig, + type LiveQueryHookConfig, +} from './live/types.js' export { type LiveQueryCollectionUtils } from './live/collection-config-builder.js' export { UnhashableQueryIRError, diff --git a/packages/db/src/query/ir-stable-identity.ts b/packages/db/src/query/ir-stable-identity.ts index bf121fc797..94c2f05dca 100644 --- a/packages/db/src/query/ir-stable-identity.ts +++ b/packages/db/src/query/ir-stable-identity.ts @@ -290,6 +290,11 @@ function canonicalizeQueryInScope( ) } + // Operator code decides whether a query compiles, so it is part of the + // query's identity. Only groupKeys() and hand-built IR without operator + // code mark it; other identities stay as they were. + if (query.grouping) result.grouping = true + if (query.groupBy) { result.groupBy = query.groupBy.map((expression, index) => canonicalizeExpression( @@ -730,6 +735,8 @@ function canonicalizeExpression( if (expression.type === `agg`) { return { type: `agg`, + // The types require operator code; IR built by hand may lack it. + ...((expression.grouping as unknown) ? {} : { withoutOperator: true }), name: expression.name, args: expression.args.map((arg, index) => canonicalizeExpression( @@ -778,6 +785,7 @@ function canonicalizeExpression( const childScope = createAliasScope(expression.query, scope) const result: Record = { type: `includesSubquery`, + ...((expression.runtime as unknown) ? {} : { withoutOperator: true }), query: canonicalizeQueryInScope( expression.query, `${path}.query`, diff --git a/packages/db/src/query/ir.ts b/packages/db/src/query/ir.ts index a1e9a67ea4..f3134a217d 100644 --- a/packages/db/src/query/ir.ts +++ b/packages/db/src/query/ir.ts @@ -10,12 +10,17 @@ import type { Collection, CollectionImpl } from '../collection/index.js' import type { CollectionOptionsIdentity } from '../collection-options.js' import type { NamespacedRow } from '../types' +import type { Grouping } from './compiler/grouping.js' +import type { IncludesRuntime } from './live/includes-runtime.js' + export interface QueryIR { from: From select?: Select join?: Join where?: Array groupBy?: GroupBy + // groupKeys supplies the grouping compiler when no aggregate does. + grouping?: Grouping having?: Array orderBy?: OrderBy limit?: Limit @@ -262,6 +267,8 @@ export class Aggregate extends BaseExpression { constructor( public name: string, // such as count, avg, sum, min, max, etc. public args: Array, + // The aggregate function brings the grouping compiler with it. + public grouping: Grouping, ) { super() } @@ -284,6 +291,8 @@ export class IncludesSubquery extends BaseExpression { public correlationField: PropRef, // Parent-side ref (e.g., project.id) public childCorrelationField: PropRef, // Child-side ref (e.g., issue.projectId) public fieldName: string, // Result field name (e.g., "issues") + // The include value brings the materializer that publishes it. + public runtime: IncludesRuntime, public parentFilters?: Array, // WHERE clauses referencing parent aliases (applied post-join) public parentProjection?: Array, // Parent field refs used anywhere in the child plan public materialization: IncludesMaterialization = `collection`, diff --git a/packages/db/src/query/live/ARCHITECTURE.md b/packages/db/src/query/live/ARCHITECTURE.md index 86eae32fb9..b483afc30a 100644 --- a/packages/db/src/query/live/ARCHITECTURE.md +++ b/packages/db/src/query/live/ARCHITECTURE.md @@ -99,17 +99,27 @@ operators and a few boundary adapters: | Architectural role | Concrete implementation | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | Compile relation IDs and demand plans | `packages/db/src/query/compiler/index.ts`, `packages/db/src/query/compiler/joins.ts` | -| Reduce public keys and build routes | `packages/db/src/query/live/materialized-pipeline.ts` | +| Reduce public keys | `packages/db/src/query/live/public-key-reduction.ts` | +| Build include routes | `packages/db/src/query/live/materialized-pipeline.ts` | | Run the graph and publish root rows | `packages/db/src/query/live/collection-config-builder.ts` | | Schedule Collection and Effect graph turns | `packages/db/src/query/live/graph-scheduler.ts` | | Publish Collection-valued buckets | `packages/db/src/query/live/bucket-facade-adapter.ts` | | Start and release asynchronous demand | `packages/db/src/query/live/subset-demand-controller.ts`, `packages/db/src/collection/subscription.ts` | | Ordered provider loading and continuation | `packages/db/src/query/live/ordered-source-loader.ts` | +Operator values carry their compilers, so an app bundles only the operators it +imports. `include()`, `toArray()`, and `materialize()` carry the materializer +and the Collection-facade adapter (`includes-runtime.ts`); aggregates and +`groupKeys()` carry the grouping compiler. A query that uses an operator always +holds the value that carries it, so compilation stays synchronous. Joins are +core: the join compiler and public-key reduction (`public-key-reduction.ts`) +ship with every live query, without the include materializer. + Queries without includes keep the original compiled pipeline and do not pay for facade state. The one exception is a joined query with a custom public-key function: its possible duplicate contributors still pass through the keyed -reduction that enforces public-key congruence and multiplicity. +reduction that enforces public-key congruence and multiplicity. The include +materializer uses the same reduction for its root rows. ### Loading handoffs @@ -628,8 +638,9 @@ tie-breaker, normally the child public key. An order-only change is a bucket-value change for arrays, singletons, concatenation, and Collection layout. -A bare child query is a Collection-valued include. It exposes one stable public -Collection facade per active bucket in that edge: +`include(childQuery)` is a Collection-valued include. A bare child query in a +select throws `IncludeOperatorRequiredError`. The include exposes one stable +public Collection facade per active bucket in that edge: ```text ActiveBucket + BucketRow -> ActiveBucketRow -> BucketFacade(bucket, Collection) diff --git a/packages/db/src/query/live/collection-config-builder.ts b/packages/db/src/query/live/collection-config-builder.ts index a8bf06e336..0ed502c208 100644 --- a/packages/db/src/query/live/collection-config-builder.ts +++ b/packages/db/src/query/live/collection-config-builder.ts @@ -14,11 +14,10 @@ import { runAllCallbacks } from '../../utils/callbacks.js' import { normalizeError } from '../../utils/error.js' import { createSourceRecord } from '../../utils/source-record.js' import { codedMessage, devBuild } from '../../error-message.js' +import { reducePublicKeys } from './public-key-reduction.js' import { CollectionSubscriber } from './collection-subscriber.js' import { getCollectionBuilder } from './collection-registry.js' import { LIVE_QUERY_INTERNAL } from './internal.js' -import { materializeCompilation } from './materialized-pipeline.js' -import { BucketFacadeAdapter } from './bucket-facade-adapter.js' import { scheduleQueryGraphRun } from './graph-scheduler.js' import { hasPendingJoinedWork as hasPendingJoinedSourceWork } from './subset-demand-controller.js' import { @@ -29,6 +28,9 @@ import { sourcePermitsInitialReadiness, } from './utils.js' import type { LiveQueryInternalUtils } from './internal.js' +import type { IncludesRuntime } from './includes-runtime.js' +import type { BucketFacadeAdapter } from './bucket-facade-adapter.js' +import type { materializeCompilation } from './materialized-pipeline.js' import type { WindowOptions } from '../compiler/index.js' import type { SchedulerContextId } from '../../scheduler.js' import type { CollectionSubscription } from '../../collection/subscription.js' @@ -156,6 +158,7 @@ export class CollectionConfigBuilder< private pipelineCache: ResultStream | undefined public sourceWhereClausesCache: Map> | undefined + private includesRuntimeCache: IncludesRuntime | undefined private bucketFacadesCache: ReturnType[`facades`] | undefined @@ -860,6 +863,7 @@ export class CollectionConfigBuilder< this.pipelineCache = undefined this.sourceWhereClausesCache = undefined this.bucketFacadesCache = undefined + this.includesRuntimeCache = undefined // Reset lazy source alias state this.lazySources.clear() @@ -910,14 +914,19 @@ export class CollectionConfigBuilder< }, ) - const materialized = materializeCompilation( - compilation, - this.config.getKey, - this.hasJoins(this.query), - ) + // Includes bring the materializer. A joined query with a custom key + // reduces its public keys without it. + const includesRuntime = compilation.includes?.[0]?.runtime + const getKey = this.config.getKey + const materialized = includesRuntime + ? includesRuntime.materializeCompilation(compilation, getKey) + : getKey && this.hasJoins(this.query) + ? reducePublicKeys(compilation, getKey) + : { pipeline: compilation.pipeline, facades: undefined } this.pipelineCache = materialized.pipeline this.sourceWhereClausesCache = compilation.sourceWhereClauses this.bucketFacadesCache = materialized.facades + this.includesRuntimeCache = includesRuntime const missingSources = this.collectionSources .map((source) => source.sourceId) @@ -962,14 +971,22 @@ export class CollectionConfigBuilder< }), ) - // A query whose pipeline was not materialized publishes its rows directly - // and pays for no facade state. + // A query without includes publishes its rows directly and pays for no + // facade state. With includes, the adapter also strips private route + // state at publication, even when every include is inline. A joined + // query with a custom key is materialized for key reduction only. const facadeCompilations = this.bucketFacadesCache - const bucketFacades = facadeCompilations - ? new BucketFacadeAdapter(this.id, facadeCompilations, (count) => { - syncState.messagesCount += count - }) - : undefined + const includesRuntime = this.includesRuntimeCache + const bucketFacades = + facadeCompilations && includesRuntime + ? includesRuntime.createBucketFacades( + this.id, + facadeCompilations, + (count) => { + syncState.messagesCount += count + }, + ) + : undefined if (bucketFacades) { syncState.unsubscribeCallbacks.add(() => bucketFacades.cleanup()) } diff --git a/packages/db/src/query/live/includes-runtime.ts b/packages/db/src/query/live/includes-runtime.ts new file mode 100644 index 0000000000..9da18d47e9 --- /dev/null +++ b/packages/db/src/query/live/includes-runtime.ts @@ -0,0 +1,15 @@ +import { materializeCompilation } from './materialized-pipeline.js' +import { BucketFacadeAdapter } from './bucket-facade-adapter.js' + +/** + * The include materializer. `include()`, `toArray()`, and `materialize()` + * carry it onto the query IR, so other apps do not bundle it. + */ +export const includesRuntime = { + materializeCompilation, + createBucketFacades: ( + ...args: ConstructorParameters + ) => new BucketFacadeAdapter(...args), +} + +export type IncludesRuntime = typeof includesRuntime diff --git a/packages/db/src/query/live/materialized-pipeline.ts b/packages/db/src/query/live/materialized-pipeline.ts index 455fa391eb..c9d3345d44 100644 --- a/packages/db/src/query/live/materialized-pipeline.ts +++ b/packages/db/src/query/live/materialized-pipeline.ts @@ -7,10 +7,12 @@ import { reduce, serializeValue, } from '@tanstack/db-ivm' -import { deepEquals } from '../../utils.js' -import { getParentContextIdentity } from '../equality-value-identity.js' -import { INCLUDES_ROUTING } from '../compiler/route-metadata.js' import { codedMessage, devBuild } from '../../error-message.js' +import { + canonicalizeByPublicKey, + exposeRouting, + routeKey, +} from './public-key-reduction.js' import type { ValueIdentity } from '../equality-value-identity.js' import type { CompilationResult, @@ -19,28 +21,11 @@ import type { import type { IncludesMaterialization } from '../ir.js' import type { IStreamBuilder } from '@tanstack/db-ivm' import type { ResultStream } from '../../types.js' - -type ResultTuple = [ - value: Record, - order: string | undefined, - correlationKey?: unknown, - parentContext?: Record | null, - routing?: IncludesRouting, - publicKey?: unknown, -] - -type IncludesRouting = Record - -type IncludeRoute = { - active: boolean - correlationKey: unknown - parentContext: Record | null -} - -type CanonicalResult = { - publicKey: unknown - tuple: ResultTuple -} +import type { + IncludeRoute, + RelationScope, + ResultTuple, +} from './public-key-reduction.js' // A bucket row leads with its public key. The D2 index tells apart values // with a different leading string, number or bigint without hashing them, so @@ -83,8 +68,6 @@ export type MaterializedRootCompilation = { facades: Array | undefined } -type RelationScope = `root` | `child` - type BuiltRelations = WeakMap< CompilationResult, Partial> @@ -100,12 +83,8 @@ let nextBucketFacadeEdgeId = 0 export function materializeCompilation( compilation: CompilationResult, getRootKey?: (row: any) => unknown, - reduceJoinedPublicKeys = false, ): MaterializedRootCompilation { - if ( - !compilation.includes?.length && - !(getRootKey && reduceJoinedPublicKeys) - ) { + if (!compilation.includes?.length) { return { pipeline: compilation.pipeline, facades: undefined } } @@ -198,105 +177,6 @@ function dedupeFacades( return [...new Map(facades.map((facade) => [facade.edgeId, facade])).values()] } -function exposeRouting(pipeline: ResultStream): ResultStream { - return pipeline.pipe( - map(([key, rawTuple]) => { - const tuple = rawTuple as ResultTuple - return [ - key, - [ - tuple[0], - tuple[1], - tuple[2], - tuple[3], - tuple[0][INCLUDES_ROUTING], - tuple[4], - ], - ] - }), - ) as unknown as ResultStream -} - -function canonicalizeByPublicKey( - pipeline: ResultStream, - getKey: ((row: any) => unknown) | undefined, - scope: RelationScope, - valueIdentity: ValueIdentity, -): ResultStream { - return pipeline.pipe( - map(([internalKey, rawTuple]) => { - const tuple = rawTuple as ResultTuple - const publicKey = getKey ? getKey(tuple[0]) : (tuple[5] ?? internalKey) - const relationKey = - scope === `root` - ? serializeValue([`root`, publicKey]) - : serializeValue([ - routeKey(tuple[2], tuple[3], valueIdentity), - publicKey, - ]) - return [relationKey, { publicKey, tuple }] as [string, CanonicalResult] - }), - reduce((values: Array<[CanonicalResult, number]>) => { - const totalMultiplicity = values.reduce( - (total, [, multiplicity]) => total + multiplicity, - 0, - ) - if (totalMultiplicity === 0) return [] - if (totalMultiplicity < 0) { - throw new Error( - devBuild() && process.env.NODE_ENV !== `production` - ? `Canonical query row has negative multiplicity` - : codedMessage(154), - ) - } - - const visible = values.find(([, multiplicity]) => multiplicity > 0)?.[0] - if (!visible) { - throw new Error( - devBuild() && process.env.NODE_ENV !== `production` - ? `Canonical query row has no positive contributor` - : codedMessage(155), - ) - } - - for (const [candidate, multiplicity] of values) { - if (multiplicity <= 0) continue - assertCongruentContributors(visible, candidate) - } - - return [[visible, 1]] - }), - map(([relationKey, { publicKey, tuple }]) => [ - scope === `root` ? publicKey : relationKey, - tuple, - ]), - ) as ResultStream -} - -function assertCongruentContributors( - left: CanonicalResult, - right: CanonicalResult, -): void { - const [leftValue, leftOrder, leftCorrelation, leftContext, leftRouting] = - left.tuple - const [rightValue, rightOrder, rightCorrelation, rightContext, rightRouting] = - right.tuple - - if ( - leftOrder !== rightOrder || - !deepEquals(leftValue, rightValue) || - !deepEquals(leftCorrelation, rightCorrelation) || - !deepEquals(leftContext, rightContext) || - !deepEquals(leftRouting, rightRouting) - ) { - throw new Error( - devBuild() && process.env.NODE_ENV !== `production` - ? `Query contributors for public key ${serializeValue(left.publicKey)} are not congruent` - : codedMessage(156, { publicKey: left.publicKey }), - ) - } -} - function attachInlineInclude( parentPipeline: ResultStream, bucketRows: IStreamBuilder<[string, BucketRow]>, @@ -470,17 +350,6 @@ function getIncludeRoute( return tuple[4]?.[fieldName] } -function routeKey( - correlationKey: unknown, - parentContext: Record | null | undefined, - valueIdentity: ValueIdentity, -): string { - return serializeValue([ - valueIdentity.equality(correlationKey ?? null), - getParentContextIdentity(parentContext ?? null), - ]) -} - function compareBucketRows(left: BucketRow, right: BucketRow): number { const [leftPublicKey, , leftOrder] = left const [rightPublicKey, , rightOrder] = right diff --git a/packages/db/src/query/live/public-key-reduction.ts b/packages/db/src/query/live/public-key-reduction.ts new file mode 100644 index 0000000000..e75ce7ee01 --- /dev/null +++ b/packages/db/src/query/live/public-key-reduction.ts @@ -0,0 +1,162 @@ +import { map, reduce, serializeValue } from '@tanstack/db-ivm' +import { deepEquals } from '../../utils.js' +import { getParentContextIdentity } from '../equality-value-identity.js' +import { INCLUDES_ROUTING } from '../compiler/route-metadata.js' +import { codedMessage, devBuild } from '../../error-message.js' +import type { ValueIdentity } from '../equality-value-identity.js' +import type { CompilationResult } from '../compiler/index.js' +import type { ResultStream } from '../../types.js' + +// Public-key reduction. A joined query with a custom key needs only this +// part of materialization, so it ships in core without the include +// materializer. + +export type ResultTuple = [ + value: Record, + order: string | undefined, + correlationKey?: unknown, + parentContext?: Record | null, + routing?: IncludesRouting, + publicKey?: unknown, +] + +export type IncludesRouting = Record + +export type IncludeRoute = { + active: boolean + correlationKey: unknown + parentContext: Record | null +} + +export type CanonicalResult = { + publicKey: unknown + tuple: ResultTuple +} + +export type RelationScope = `root` | `child` + +export function exposeRouting(pipeline: ResultStream): ResultStream { + return pipeline.pipe( + map(([key, rawTuple]) => { + const tuple = rawTuple as ResultTuple + return [ + key, + [ + tuple[0], + tuple[1], + tuple[2], + tuple[3], + tuple[0][INCLUDES_ROUTING], + tuple[4], + ], + ] + }), + ) as unknown as ResultStream +} + +export function canonicalizeByPublicKey( + pipeline: ResultStream, + getKey: ((row: any) => unknown) | undefined, + scope: RelationScope, + valueIdentity: ValueIdentity, +): ResultStream { + return pipeline.pipe( + map(([internalKey, rawTuple]) => { + const tuple = rawTuple as ResultTuple + const publicKey = getKey ? getKey(tuple[0]) : (tuple[5] ?? internalKey) + const relationKey = + scope === `root` + ? serializeValue([`root`, publicKey]) + : serializeValue([ + routeKey(tuple[2], tuple[3], valueIdentity), + publicKey, + ]) + return [relationKey, { publicKey, tuple }] as [string, CanonicalResult] + }), + reduce((values: Array<[CanonicalResult, number]>) => { + const totalMultiplicity = values.reduce( + (total, [, multiplicity]) => total + multiplicity, + 0, + ) + if (totalMultiplicity === 0) return [] + if (totalMultiplicity < 0) { + throw new Error( + devBuild() && process.env.NODE_ENV !== `production` + ? `Canonical query row has negative multiplicity` + : codedMessage(154), + ) + } + + const visible = values.find(([, multiplicity]) => multiplicity > 0)?.[0] + if (!visible) { + throw new Error( + devBuild() && process.env.NODE_ENV !== `production` + ? `Canonical query row has no positive contributor` + : codedMessage(155), + ) + } + + for (const [candidate, multiplicity] of values) { + if (multiplicity <= 0) continue + assertCongruentContributors(visible, candidate) + } + + return [[visible, 1]] + }), + map(([relationKey, { publicKey, tuple }]) => [ + scope === `root` ? publicKey : relationKey, + tuple, + ]), + ) as ResultStream +} + +function assertCongruentContributors( + left: CanonicalResult, + right: CanonicalResult, +): void { + const [leftValue, leftOrder, leftCorrelation, leftContext, leftRouting] = + left.tuple + const [rightValue, rightOrder, rightCorrelation, rightContext, rightRouting] = + right.tuple + + if ( + leftOrder !== rightOrder || + !deepEquals(leftValue, rightValue) || + !deepEquals(leftCorrelation, rightCorrelation) || + !deepEquals(leftContext, rightContext) || + !deepEquals(leftRouting, rightRouting) + ) { + throw new Error( + devBuild() && process.env.NODE_ENV !== `production` + ? `Query contributors for public key ${serializeValue(left.publicKey)} are not congruent` + : codedMessage(156, { publicKey: left.publicKey }), + ) + } +} + +export function routeKey( + correlationKey: unknown, + parentContext: Record | null | undefined, + valueIdentity: ValueIdentity, +): string { + return serializeValue([ + valueIdentity.equality(correlationKey ?? null), + getParentContextIdentity(parentContext ?? null), + ]) +} + +/** Reduces a query without includes to one canonical row per public key. */ +export function reducePublicKeys( + compilation: CompilationResult, + getKey: (row: any) => unknown, +): { pipeline: ResultStream; facades: undefined } { + return { + pipeline: canonicalizeByPublicKey( + exposeRouting(compilation.pipeline), + getKey, + `root`, + compilation.valueIdentity, + ), + facades: undefined, + } +} diff --git a/packages/db/src/query/live/types.ts b/packages/db/src/query/live/types.ts index 307397698e..8d16063e88 100644 --- a/packages/db/src/query/live/types.ts +++ b/packages/db/src/query/live/types.ts @@ -1,4 +1,5 @@ import type { D2, RootStreamBuilder } from '@tanstack/db-ivm' +import type { PooledLiveQueries } from '../pooled-live-query.js' import type { CollectionConfig, ResultStream, @@ -120,3 +121,16 @@ export interface LiveQueryCollectionConfig< */ defaultStringCollation?: StringCollationConfig } + +/** + * A live query config that a framework hook accepts. Pass `pooledLiveQueries` + * as `pool` to serve the query from a shared equality partition when its + * shape allows. A config with any option other than `query`, `gcTime`, and + * `pool` compiles its own Collection. + */ +export type LiveQueryHookConfig< + TContext extends Context, + TResult extends object = RootQueryResult, +> = LiveQueryCollectionConfig & { + pool?: PooledLiveQueries +} diff --git a/packages/db/src/query/optimizer.ts b/packages/db/src/query/optimizer.ts index bb0c272583..d214a737e8 100644 --- a/packages/db/src/query/optimizer.ts +++ b/packages/db/src/query/optimizer.ts @@ -122,7 +122,7 @@ import { deepEquals } from '../utils.js' import { CannotCombineEmptyExpressionListError } from '../errors.js' -import { containsAggregate } from './compiler/group-by.js' +import { findAggregate } from './compiler/find-aggregate.js' import { Func, PropRef, @@ -1088,7 +1088,7 @@ function isSafeToPushIntoExistingSubquery( return !( query.distinct || (query.select && - (containsAggregate(query.select) || + (findAggregate(query.select) !== undefined || whereReferencesComputedSelectFields( query.select, whereClause, diff --git a/packages/db/src/query/pooled-live-query.ts b/packages/db/src/query/pooled-live-query.ts index 9f87605ff3..fa610bcd85 100644 --- a/packages/db/src/query/pooled-live-query.ts +++ b/packages/db/src/query/pooled-live-query.ts @@ -9,6 +9,7 @@ import { equalityConjunct, equalityKey, readPath } from './equality-conjunct.js' import { compileExpression, toBooleanPredicate } from './compiler/evaluators.js' import { buildCompareOptions } from './compiler/order-by.js' import { createLiveQueryCollection } from './live-query-collection.js' +import { registerPooledSource } from './pooled-source.js' import type { BasicExpression, OrderBy, QueryIR } from './ir.js' import type { BaseQueryBuilder } from './builder/index.js' import type { Collection, CollectionImpl } from '../collection/index.js' @@ -496,6 +497,7 @@ class PooledLiveQuery { private readonly passes: ((row: Row) => boolean) | undefined, ) { partition.retain(gcTime) + registerPooledSource(this, source) } get status(): CollectionStatus { @@ -627,13 +629,6 @@ class PooledLiveQuery { } } -/** The one source of an adapter's pooled view, without materializing it. */ -export function getPooledLiveQuerySource( - value: unknown, -): CollectionImpl | undefined { - return value instanceof PooledLiveQuery ? value.source : undefined -} - // The observer reads the view itself; users get the live-query Collection. const forwardToCollection: ProxyHandler = { get(view, property) { @@ -724,3 +719,15 @@ export function createPooledLiveQuery( : undefined, ) as unknown as Collection } + +/** + * Pooled live queries for framework hooks. Pass it as `pool` in a hook's + * query config to serve equality-filtered queries of one shape from a shared + * partition. Apps that do not pass it do not bundle pooling. + */ +export const pooledLiveQueries = { + createPooledLiveQuery, + getPooledQueryIdentity, +} + +export type PooledLiveQueries = typeof pooledLiveQueries diff --git a/packages/db/src/query/pooled-source.ts b/packages/db/src/query/pooled-source.ts new file mode 100644 index 0000000000..3ac427db12 --- /dev/null +++ b/packages/db/src/query/pooled-source.ts @@ -0,0 +1,24 @@ +import type { CollectionImpl } from '../collection/index.js' + +// A pooled view's source Collection. A view registers here when it is +// created, so the live-query observer finds the source without bundling +// pooling into apps that never import `pooledLiveQueries`. +const pooledSources = new WeakMap< + object, + CollectionImpl +>() + +export function registerPooledSource( + view: object, + source: CollectionImpl, +): void { + pooledSources.set(view, source) +} + +export function getPooledLiveQuerySource( + value: unknown, +): CollectionImpl | undefined { + return typeof value === `object` && value !== null + ? pooledSources.get(value) + : undefined +} diff --git a/packages/db/tests/collection-auto-index.test.ts b/packages/db/tests/collection-auto-index.test.ts index 7d004d4946..620affc7e1 100644 --- a/packages/db/tests/collection-auto-index.test.ts +++ b/packages/db/tests/collection-auto-index.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from 'vitest' +import { createLiveQueryCollection } from '../src' import { CollectionConfigurationError } from '../src/errors' import { createCollection } from '../src/collection/index.js' import { @@ -11,7 +12,6 @@ import { or, } from '../src/query/builder/functions' import { createSingleRowRefProxy } from '../src/query/builder/ref-proxy' -import { createLiveQueryCollection } from '../src' import { PropRef } from '../src/query/ir' import { BTreeIndex } from '../src/indexes/btree-index' import { diff --git a/packages/db/tests/collection-query-publication-boundaries.test.ts b/packages/db/tests/collection-query-publication-boundaries.test.ts index f5197fc4e1..e07e2edbe4 100644 --- a/packages/db/tests/collection-query-publication-boundaries.test.ts +++ b/packages/db/tests/collection-query-publication-boundaries.test.ts @@ -1,8 +1,8 @@ import { expect, it } from 'vitest' import { MultiSet } from '@tanstack/db-ivm' +import { createLiveQueryCollection, eq } from '../src/query/index.js' import { createCollection } from '../src/collection/index.js' import { createDeferred } from '../src/deferred.js' -import { createLiveQueryCollection, eq } from '../src/query/index.js' import { createTransaction } from '../src/transactions.js' import { flushPromises } from './utils.js' import type { SyncConfig } from '../src/types.js' diff --git a/packages/db/tests/collection-subscriber-duplicate-inserts.test.ts b/packages/db/tests/collection-subscriber-duplicate-inserts.test.ts index c91a976831..69c106ad18 100644 --- a/packages/db/tests/collection-subscriber-duplicate-inserts.test.ts +++ b/packages/db/tests/collection-subscriber-duplicate-inserts.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' +import { createLiveQueryCollection, eq } from '../src/query/index.js' import { createCollection } from '../src/collection/index.js' import { BTreeIndex } from '../src/indexes/btree-index.js' -import { createLiveQueryCollection, eq } from '../src/query/index.js' import { mockSyncCollectionOptions } from './utils.js' import type { ChangeMessage } from '../src/types.js' diff --git a/packages/db/tests/conformance/contract.ts b/packages/db/tests/conformance/contract.ts index 976ebf5a6d..f34d8b2f26 100644 --- a/packages/db/tests/conformance/contract.ts +++ b/packages/db/tests/conformance/contract.ts @@ -52,19 +52,23 @@ * the next `current()` read. After a rejection, no later read exposes rows: * each `current()` throws or holds none, so a framework whose memo throws once * cannot fall back to the previous query's rows. Each driver releases any - * native scope that a rejected mount acquired. In the current drivers this - * check cannot fail, and reactive drivers do not reject here. React unmounts - * a tree whose render throws, so its driver rethrows on later reads. Preact - * keeps a component whose render throws mounted, so its driver mounts the hook - * under an error boundary, as an application must to recover, and rethrows on - * later reads. A Preact component without a boundary is outside this cut. It guards a future driver whose binding stays mounted. The one such - * binding today, a `DbClient`-backed Svelte query, has its own witness in the - * Svelte descriptor release oracle, which reads each field after a rejection. + * native scope that a rejected mount acquired. A reactive driver rejects + * after mount when its new input fails to compile, as when a re-render drops + * `groupKeys()`. Vue's computed serves its previous value after it throws + * once, so vue-db catches the error in a computed that does not throw and + * clears its rows before it rethrows; before that repair this check failed + * there. React unmounts a tree whose render throws, so its driver rethrows + * on later reads. Preact keeps a component whose render throws mounted, so + * its driver mounts the hook under an error boundary, as an application must + * to recover, and rethrows on later reads. A Preact component without a + * boundary is outside this cut. A `DbClient`-backed Svelte query also stays + * mounted; the Svelte descriptor release oracle reads each of its fields + * after a rejection. * * All current scenarios must pass. A future known bug needs an independently * approved exact failure signature; a whole-test waiver could hide another bug. */ -import type { Collection } from '@tanstack/db' +import type { Collection, PooledLiveQueries } from '@tanstack/db' /** Default row shape used by the base scenarios (a "person"). */ export interface Row { @@ -130,6 +134,7 @@ export interface DbOps { count: (a: any) => any sum: (a: any) => any coalesce: (...args: Array) => any + groupKeys: (callback: (refs: any) => any) => any /** Build an optimistic action: onMutate applies optimistic state, mutationFn confirms. */ createOptimisticAction: (config: { onMutate: (variables: any) => void @@ -194,6 +199,12 @@ export interface ControllableHandle

extends LiveQueryHandle { * show reads these, not only the value after `setParam` settles. */ publications: () => ReadonlyArray + /** + * The keyed `state` map's rows at the same publication points. A binding + * that applies row changes to its map can show rows there that `data` + * never holds. + */ + statePublications: () => ReadonlyArray } /** @@ -244,10 +255,12 @@ export interface LiveQueryDriver { /** * Mount a live query whose input depends on a parameter that can change after * mount. `build` returns a query, or `null`/`undefined` to represent disabled. + * With `pool`, use the adapter's config form for a parameterized query. */ mountControllable:

( build: (q: any, param: P) => any, initial: P, + opts?: { pool?: PooledLiveQueries }, ) => ControllableHandle

/** * Mount a controllable query in the config-object form whose `queryKey` is @@ -261,10 +274,14 @@ export interface LiveQueryDriver { ) => ControllableHandle

/** Mount a pre-created collection passed directly to the hook. */ mountCollection: (collection: Collection) => LiveQueryHandle - /** Mount via the config-object input form (`{ query: build }`). */ + /** Mount via the config-object input form (`{ query: build, ...opts }`). */ mountConfig: ( build: QueryBuild, - opts?: { gcTime?: number; queryKey?: ReadonlyArray }, + opts?: { + gcTime?: number + pool?: PooledLiveQueries + queryKey?: ReadonlyArray + }, ) => LiveQueryHandle /** Mount an explicitly-disabled query (adapter's own null/undefined form). */ mountDisabled: () => LiveQueryHandle @@ -290,8 +307,10 @@ export interface LiveQueryDriver { serverSnapshot?: boolean suspense?: boolean /** - * Public sharing policy: queries filtered only by `eq` on one source share - * one source subscription per filtered field set, instead of one each. + * Public sharing policy: queries whose config passes `pooledLiveQueries` + * and that filter only by `eq` on one source share one source subscription + * per filtered field set, instead of one each. Every other query has a + * subscription of its own: pooling is opt-in per query. */ pooledEqFilters?: boolean } diff --git a/packages/db/tests/conformance/publication-log.ts b/packages/db/tests/conformance/publication-log.ts index 94fb31b9d2..891f4299df 100644 --- a/packages/db/tests/conformance/publication-log.ts +++ b/packages/db/tests/conformance/publication-log.ts @@ -10,16 +10,26 @@ */ export function createPublicationLog() { const turns: Array = [] + const stateTurns: Array> = [] let turnOpen = false return { - /** Record a published value, replacing an earlier one from this turn. */ - record(value: unknown): void { + /** + * Record a published value and the keyed state map beside it, replacing + * an earlier record from this turn. + */ + record( + value: unknown, + state: { values: () => Iterable } | undefined, + ): void { const copy = Array.isArray(value) ? [...value] : value + const rows = state ? [...state.values()] : [] if (turnOpen) { turns[turns.length - 1] = copy + stateTurns[stateTurns.length - 1] = rows return } turns.push(copy) + stateTurns.push(rows) turnOpen = true queueMicrotask(() => { turnOpen = false @@ -27,5 +37,7 @@ export function createPublicationLog() { }, /** The value published at the end of each turn, in order. */ values: (): ReadonlyArray => turns, + /** The state map's rows at the end of each turn, in order. */ + stateValues: (): ReadonlyArray => stateTurns, } } diff --git a/packages/db/tests/conformance/suite.ts b/packages/db/tests/conformance/suite.ts index 8648a67732..9a09135d27 100644 --- a/packages/db/tests/conformance/suite.ts +++ b/packages/db/tests/conformance/suite.ts @@ -34,7 +34,8 @@ * groupBy/aggregate, nested aggregates, `.includes` subqueries, findOne * cardinality, disabled + transitions, deferred readiness / eager / ready-with- * no-data, initial readiness over a lagging on-demand source, param recompilation, optimistic mutation, pre-created & config-object - * inputs, error status, and order-only moves. + * inputs, opt-in pooling in each input form, error status, and order-only + * moves. * * The query-identity scenarios (see `contract.ts`) run each history through * both identity policies. A comparing binding rejects an unhashable query @@ -52,7 +53,11 @@ * query follow the same split: rejected, or each read. */ import { describe, expect, it } from 'vitest' -import { waitForPersistedInitialRender } from '@tanstack/db' +import { + include, + pooledLiveQueries, + waitForPersistedInitialRender, +} from '@tanstack/db' import { createOracleReach } from '../oracle-reach' import { ScenarioLifetime } from './scenario-lifetime' import { ScenarioSources } from './scenario-sources' @@ -64,6 +69,7 @@ import { expectUnorderedRows, selectedRow, } from './result-laws' +import type { PooledLiveQueries } from '@tanstack/db' import type { DeferredSourceHandle, LiveQueryDriver, @@ -96,6 +102,9 @@ async function expectRetired(collection: ConformanceCollection): Promise { async function expectRejection( name: string, run: () => Promise | unknown, + // Many coded errors share their base class's name; the development + // build's message tells them apart. + message?: RegExp, ): Promise { let rejection: unknown try { @@ -103,14 +112,15 @@ async function expectRejection( } catch (error) { rejection = error } - expect(rejection, `expected ${name}`).toMatchObject({ name }) + expect(rejection, `expected ${name}`).toMatchObject( + message ? { name, message: expect.stringMatching(message) } : { name }, + ) } /** * After a rejection, every read throws or holds no rows. Two reads catch a - * memo that throws once and then serves its previous value. No current driver - * can fail this check (see `contract.ts`); it guards a driver whose binding - * stays mounted after a rejection. + * memo that throws once and then serves its previous value, as a Vue + * computed does (see `contract.ts`). */ function expectNoRowsAfterRejection(h: LiveQueryHandle): void { for (let read = 0; read < 2; read++) { @@ -193,8 +203,8 @@ export function runSuite(rawDriver: LiveQueryDriver) { sources!.track(rawDriver.makePrecreated(build, options)), makeErrorSource: () => sources!.track(rawDriver.makeErrorSource()), mount: (build) => track(rawDriver.mount(build)), - mountControllable: (build, initial) => - track(rawDriver.mountControllable(build, initial)), + mountControllable: (build, initial, options) => + track(rawDriver.mountControllable(build, initial, options)), mountCollection: (collection) => track(rawDriver.mountCollection(collection)), mountConfig: (build, options) => @@ -421,10 +431,13 @@ export function runSuite(rawDriver: LiveQueryDriver) { q .from({ items: source.collection }) .where(({ items }: any) => ops.eq(items.team, team)) - const teamA = driver.mount(query(`a`)) - const teamB = driver.mount(query(`b`)) - const olderA = driver.mount((q) => - query(`a`)(q).where(({ items }: any) => ops.eq(items.age, 35)), + // The config opts into pooling; without it each query compiles. + const pool = { pool: pooledLiveQueries } + const teamA = driver.mountConfig(query(`a`), pool) + const teamB = driver.mountConfig(query(`b`), pool) + const olderA = driver.mountConfig( + (q) => query(`a`)(q).where(({ items }: any) => ops.eq(items.age, 35)), + pool, ) for (const h of [teamA, teamB, olderA]) await h.flush() // Pooling shares one subscription between the two team queries. @@ -456,6 +469,219 @@ export function runSuite(rawDriver: LiveQueryDriver) { }, ) + // Pooling is opt-in per query. Queries that do not pass `pool` each + // subscribe to the source themselves, whatever input form they use. + scenario( + `pool-opt-in`, + `queries without pool keep a source subscription each`, + async () => { + const source = driver.makeSource(SEED) + const query = (team: string) => (q: any) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.eq(items.team, team)) + const handles = [ + driver.mount(query(`a`)), + driver.mount(query(`b`)), + driver.mountConfig(query(`a`)), + driver.mountControllable((q, team: string) => query(team)(q), `b`), + ] + for (const h of handles) await h.flush() + expect(source.collection.subscriberCount).toBe(handles.length) + expect( + handles.map((h) => + (h.current().data as Array<{ id: string }>).map((row) => row.id), + ), + ).toEqual([[`1`, `3`], [`2`], [`1`, `3`], [`2`]]) + for (const h of handles) h.unmount() + }, + ) + + // A pooled query whose parameter changes leaves its old pool group and + // joins the new one: its rows follow the parameter, and a peer that keeps + // its parameter is not disturbed. + scenario( + `pooled-param-change`, + `a pooled query follows its parameter between pool groups`, + async () => { + const source = driver.makeSource(SEED) + const pool = { pool: pooledLiveQueries } + const query = (q: any, team: string) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.eq(items.team, team)) + const moving = driver.mountControllable(query, `a`, pool) + const peer = driver.mountConfig((q) => query(q, `a`), pool) + for (const h of [moving, peer]) await h.flush() + const ids = (h: LiveQueryHandle) => + (h.current().data as Array<{ id: string }>).map((row) => row.id) + expect([ids(moving), ids(peer)]).toEqual([ + [`1`, `3`], + [`1`, `3`], + ]) + const shared = driver.features?.pooledEqFilters ? 1 : 2 + expect(source.collection.subscriberCount).toBe(shared) + + // Every value the binding publishes after a switch holds only rows of + // the new group, or none while it loads; the old group's rows never + // show again. The driver logs the first paint, so the law cannot + // pass on an empty log. + const switchTo = async ( + team: string, + rows: ReadonlyArray<{ id: string }>, + ) => { + const before = moving.publications().length + await moving.setParam(team) + const published = moving.publications().slice(before) + // The keyed state map follows the same law as `data`. + const states = moving.statePublications().slice(before) + expect(published.length).toBeGreaterThan(0) + for (const value of [...published, ...states]) + expectOnlyRowsOf(value, rows) + } + + await switchTo(`b`, [SEED[1]!]) + await peer.flush() + expect([ids(moving), ids(peer)]).toEqual([[`2`], [`1`, `3`]]) + expectKeyedRows(moving.current().state, [SEED[1]!]) + expect(source.collection.subscriberCount).toBe(shared) + + source.insert({ id: `4`, name: `Dave`, age: 40, team: `b` }) + for (const h of [moving, peer]) await h.flush() + expect([ids(moving), ids(peer)]).toEqual([ + [`2`, `4`], + [`1`, `3`], + ]) + + await switchTo(`a`, [SEED[0]!, SEED[2]!]) + await peer.flush() + expect([ids(moving), ids(peer)]).toEqual([ + [`1`, `3`], + [`1`, `3`], + ]) + for (const h of [moving, peer]) h.unmount() + await expect + .poll(() => source.collection.subscriberCount, { + interval: 5, + timeout: 500, + }) + .toBe(0) + }, + ) + + // `pool` takes `pooledLiveQueries`. Any other value, such as the removed + // boolean form, contradicts the hook contract, so the hook rejects it the + // same way whatever its other options: beside `gcTime`, which keeps the + // pooled path, beside `queryKey`, which compiles its own Collection, and + // on a parameterized query. Authority: AGENTS.md, which requires a + // contract contradiction to fail at the boundary, not to fall back. + scenario( + `invalid-pool`, + `a hook rejects a pool option that is not pooledLiveQueries`, + async () => { + const source = driver.makeSource(SEED) + const query = (q: any, team: string) => + q + .from({ items: source.collection }) + .where(({ items }: any) => ops.eq(items.team, team)) + // `false` is a value an adapter passes to say a hook cannot pool; a + // config that passes it still breaks the contract. A disabled query + // has no query to pool, but its config holds the same option. + for (const invalid of [ + true, + false, + ] as Array as Array) + for (const mount of [ + () => driver.mountConfig((q) => query(q, `a`), { pool: invalid }), + () => driver.mountConfig(() => null as any, { pool: invalid }), + () => driver.mountControllable(() => null, `a`, { pool: invalid }), + () => + driver.mountConfig((q) => query(q, `a`), { + pool: invalid, + gcTime: 5, + }), + () => + driver.mountConfig((q) => query(q, `a`), { + pool: invalid, + queryKey: [`teams`, `a`], + }), + () => driver.mountControllable(query, `a`, { pool: invalid }), + ]) + await expectRejection(`InvalidPoolOptionError`, async () => { + const h = mount() + await h.flush() + return h.current() + }) + }, + ) + + // Operator code is part of query identity. A re-render whose groupBy + // drops `groupKeys()` has no grouping compiler, so it rejects; a binding + // that compares identity must not keep the grouped query it compiled. + scenario( + `grouping-operator-switch`, + `a re-render that drops groupKeys() rejects instead of keeping the grouped query`, + async () => { + const source = driver.makeSource(SEED) + const team = ({ items }: any) => items.team + const h = driver.mountControllable( + (q, keys) => + q + .from({ items: source.collection }) + .groupBy(keys ? ops.groupKeys(team) : team) + .select(({ items }: any) => ({ id: items.team })), + true, + ) + await h.flush() + expectUnorderedRows(h.current().data, [{ id: `a` }, { id: `b` }]) + await expectRejection( + `QueryCompilationError`, + async () => { + await h.setParam(false) + return h.current() + }, + /^groupBy\(\) without an aggregate needs groupKeys\(\)/, + ) + expectNoRowsAfterRejection(h) + }, + ) + + // Any compile error after mount follows the rejection law, not only a + // missing operator: a select that mixes a source field with an + // aggregate has no value for one implicit group. + scenario( + `compile-error-after-rerender`, + `a re-render whose query fails to compile rejects and shows no rows`, + async () => { + const source = driver.makeSource(SEED) + const h = driver.mountControllable( + (q, valid) => + q + .from({ items: source.collection }) + .select(({ items }: any) => + valid + ? { id: items.id } + : { id: items.id, n: ops.count(items.id) }, + ), + true, + ) + await h.flush() + expectUnorderedRows( + h.current().data, + SEED.map(({ id }) => ({ id })), + ) + await expectRejection( + `GroupByError`, + async () => { + await h.setParam(false) + return h.current() + }, + /^Non-aggregate expression 'id' must also appear in GROUP BY clause/, + ) + expectNoRowsAfterRejection(h) + }, + ) + scenario(`live-insert`, `a sync insert appears in the result`, async () => { const source = driver.makeSource(SEED) const h = driver.mount((q) => @@ -810,10 +1036,12 @@ export function runSuite(rawDriver: LiveQueryDriver) { q.from({ persons: people.collection }).select(({ persons }: any) => ({ id: persons.id, name: persons.name, - issues: q - .from({ issues: issues.collection }) - .where(({ issues: i }: any) => ops.eq(i.userId, persons.id)) - .select(({ issues: i }: any) => ({ id: i.id, title: i.title })), + issues: include( + q + .from({ issues: issues.collection }) + .where(({ issues: i }: any) => ops.eq(i.userId, persons.id)) + .select(({ issues: i }: any) => ({ id: i.id, title: i.title })), + ), })), ) await h.flush() @@ -1775,7 +2003,7 @@ export function runSuite(rawDriver: LiveQueryDriver) { ) it(`registers every distinct scenario without whole-test waivers`, () => { - expect(registry.size).toBe(41) + expect(registry.size).toBe(46) }) }) } diff --git a/packages/db/tests/db-client-preload-identity-oracle.test.ts b/packages/db/tests/db-client-preload-identity-oracle.test.ts index baa25a4eae..778e371557 100644 --- a/packages/db/tests/db-client-preload-identity-oracle.test.ts +++ b/packages/db/tests/db-client-preload-identity-oracle.test.ts @@ -56,6 +56,7 @@ * claims or arbitrary hydration orders. */ import { describe, expect, it, vi } from 'vitest' +import { pooledLiveQueries } from '../src/query/pooled-live-query.js' import { DbClient, Query, @@ -155,7 +156,9 @@ describe(`DbClient preload source identity`, () => { /different source Collections/, ) if (streamState === `pending`) { - conflictingView = resolveLiveQueryValue(otherQuery) + conflictingView = resolveLiveQueryValue(otherQuery, { + pool: pooledLiveQueries, + }) expect(conflictingView).not.toBeNull() conflictingObserver = createLiveQueryObserver(conflictingView, { client: browser, @@ -279,7 +282,9 @@ describe(`DbClient preload source identity`, () => { if (streamState === `settled`) expect(rows(client)).toEqual([{ id: `one`, value: `server` }]) - view = resolveLiveQueryValue(replacementQuery) + view = resolveLiveQueryValue(replacementQuery, { + pool: pooledLiveQueries, + }) expect(view).not.toBeNull() observer = createLiveQueryObserver(view, { client, queryHash }) expect(() => { @@ -450,7 +455,9 @@ describe(`DbClient preload source identity`, () => { await client.preloadLiveQuery({ query: firstQuery }) const queryHash = getLiveQueryHash({ query: firstQuery }) expect(getLiveQueryHash({ query: candidateQuery })).toBe(queryHash) - view = resolveLiveQueryValue(candidateQuery) + view = resolveLiveQueryValue(candidateQuery, { + pool: pooledLiveQueries, + }) expect(view).not.toBeNull() observer = createLiveQueryObserver(view, { client, queryHash }) @@ -519,7 +526,9 @@ describe(`DbClient preload source identity`, () => { expect(getLiveQueryHash({ query: candidateQuery })).toBe(queryHash) expect(rows(client)).toEqual([{ id: `one`, value: `first` }]) - firstView = resolveLiveQueryValue(firstQuery) + firstView = resolveLiveQueryValue(firstQuery, { + pool: pooledLiveQueries, + }) expect(firstView).not.toBeNull() firstObserver = createLiveQueryObserver(firstView, { client, @@ -536,7 +545,9 @@ describe(`DbClient preload source identity`, () => { ), ).toEqual([`first`]) - nextView = resolveLiveQueryValue(candidateQuery) + nextView = resolveLiveQueryValue(candidateQuery, { + pool: pooledLiveQueries, + }) expect(nextView).not.toBeNull() nextObserver = createLiveQueryObserver(nextView, { client, queryHash }) expect(() => { @@ -585,7 +596,9 @@ describe(`DbClient preload source identity`, () => { await client.preloadLiveQuery({ query: firstQuery }) expect(rows(client)?.[0]?.value).toBe(`first`) - const view = resolveLiveQueryValue(candidateQuery) + const view = resolveLiveQueryValue(candidateQuery, { + pool: pooledLiveQueries, + }) expect(view).not.toBeNull() expect(view?.config, `the receiving view is pooled`).toBeUndefined() observer = createLiveQueryObserver(view, { client, queryHash }) @@ -629,7 +642,7 @@ describe(`DbClient preload source identity`, () => { await client.preloadLiveQuery({ query: firstQuery }) const queryHash = getLiveQueryHash({ query: firstQuery }) expect(getLiveQueryHash({ query: secondQuery })).toBe(queryHash) - view = resolveLiveQueryValue(secondQuery) + view = resolveLiveQueryValue(secondQuery, { pool: pooledLiveQueries }) expect(view).not.toBeNull() observer = createLiveQueryObserver(view, { client, queryHash }) let preload: Promise | undefined diff --git a/packages/db/tests/effect.test.ts b/packages/db/tests/effect.test.ts index 81f7b61e51..60762df45a 100644 --- a/packages/db/tests/effect.test.ts +++ b/packages/db/tests/effect.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' +import { Query, createEffect, createTransaction, eq } from '../src/index.js' import { createCollection } from '../src/collection/index.js' import { BTreeIndex } from '../src/indexes/btree-index.js' -import { Query, createEffect, createTransaction, eq } from '../src/index.js' import { captureMicrotaskErrors, mockSyncCollectionOptions, diff --git a/packages/db/tests/error-sample-arguments.ts b/packages/db/tests/error-sample-arguments.ts index 194f1ac043..addf627c18 100644 --- a/packages/db/tests/error-sample-arguments.ts +++ b/packages/db/tests/error-sample-arguments.ts @@ -91,6 +91,9 @@ export const errorSampleArguments: Record>> = { ['join clause', 'number'], ['unionAll clause', 'undefined'], ], + IncludeOperatorRequiredError: [['issues']], + GroupingOperatorRequiredError: [[]], + InvalidPoolOptionError: [[]], JoinConditionMustBeEqualityError: [[]], QueryMustHaveFromClauseError: [[]], InvalidWhereExpressionError: [['boolean']], diff --git a/packages/db/tests/fixtures/error-codes.json b/packages/db/tests/fixtures/error-codes.json index c7819fc597..1fb0b0dfae 100644 --- a/packages/db/tests/fixtures/error-codes.json +++ b/packages/db/tests/fixtures/error-codes.json @@ -91,5 +91,8 @@ "GetKeyRequiredError": 178, "DuplicateTransactionIdError": 233, "SetWindowFindOneLimitError": 244, - "SourceCollectionIdReuseError": 245 + "SourceCollectionIdReuseError": 245, + "GroupingOperatorRequiredError": 247, + "IncludeOperatorRequiredError": 248, + "InvalidPoolOptionError": 251 } diff --git a/packages/db/tests/fixtures/error-messages.json b/packages/db/tests/fixtures/error-messages.json index 0b9c3fbed1..93e82e6c12 100644 --- a/packages/db/tests/fixtures/error-messages.json +++ b/packages/db/tests/fixtures/error-messages.json @@ -137,7 +137,7 @@ ], "QueryMustHaveFromClauseError": ["Query must have a from clause"], "InvalidWhereExpressionError": [ - "Invalid where() expression: Expected a query expression, but received a boolean. This usually happens when using JavaScript's comparison operators (===, !==, <, >, etc.) directly. Instead, use the query builder functions:\n\n \u274c .where(({ user }) => user.id === 'abc')\n \u2705 .where(({ user }) => eq(user.id, 'abc'))\n\nAvailable comparison functions: eq, gt, gte, lt, lte, and, or, not, like, ilike, isNull, isUndefined" + "Invalid where() expression: Expected a query expression, but received a boolean. This usually happens when using JavaScript's comparison operators (===, !==, <, >, etc.) directly. Instead, use the query builder functions:\n\n ❌ .where(({ user }) => user.id === 'abc')\n ✅ .where(({ user }) => eq(user.id, 'abc'))\n\nAvailable comparison functions: eq, gt, gte, lt, lte, and, or, not, like, ilike, isNull, isUndefined" ], "QueryCompilationError": ["Compilation error"], "UnsafeAliasPathError": [ @@ -263,5 +263,14 @@ ], "SourceCollectionIdReuseError": [ "[useLiveQuery] Collection ID \"todos\" named a different source Collection than one this live query already read. While the hook is mounted, each Collection ID must keep naming one source Collection. Unmount the hook and clean up its previous source and client scope before reusing the ID." + ], + "GroupingOperatorRequiredError": [ + "groupBy() without an aggregate needs groupKeys(), such as .groupBy(groupKeys(({ todo }) => todo.status)). Import groupKeys from @tanstack/db. A query with an aggregate such as count() needs nothing extra. Query IR built by hand passes groupingCompiler to each IR.Aggregate." + ], + "IncludeOperatorRequiredError": [ + "A subquery in select() needs include(), toArray(), or materialize(). Wrap the issues field: issues: include(q.from({ i: issues }).where(...)). Import include from @tanstack/db. Query IR built by hand passes includesRuntime to each IR.IncludesSubquery." + ], + "InvalidPoolOptionError": [ + "A live query's pool option takes pooledLiveQueries from @tanstack/db, or nothing. Pooling is opt-in: pass pool: pooledLiveQueries to share one partition among equality-filtered queries." ] } diff --git a/packages/db/tests/fixtures/error-site-messages.json b/packages/db/tests/fixtures/error-site-messages.json index f70c579d1b..e285a7efc8 100644 --- a/packages/db/tests/fixtures/error-site-messages.json +++ b/packages/db/tests/fixtures/error-site-messages.json @@ -440,17 +440,17 @@ ] }, "154": { - "file": "query/live/materialized-pipeline.ts", + "file": "query/live/public-key-reduction.ts", "template": "Canonical query row has negative multiplicity", "literals": ["Canonical query row has negative multiplicity"] }, "155": { - "file": "query/live/materialized-pipeline.ts", + "file": "query/live/public-key-reduction.ts", "template": "Canonical query row has no positive contributor", "literals": ["Canonical query row has no positive contributor"] }, "156": { - "file": "query/live/materialized-pipeline.ts", + "file": "query/live/public-key-reduction.ts", "template": "Query contributors for public key ${serializeValue(left.publicKey)} are not congruent", "literals": ["Query contributors for public key ", " are not congruent"] }, @@ -935,5 +935,15 @@ "literals": [ "getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination." ] + }, + "249": { + "file": "query/builder/index.ts", + "template": "include() must wrap a subquery builder", + "literals": ["include() must wrap a subquery builder"] + }, + "250": { + "file": "query/builder/index.ts", + "template": "include() must wrap a subquery builder", + "literals": ["include() must wrap a subquery builder"] } } diff --git a/packages/db/tests/fixtures/loose-typescript-group-keys.ts b/packages/db/tests/fixtures/loose-typescript-group-keys.ts new file mode 100644 index 0000000000..d42134357b --- /dev/null +++ b/packages/db/tests/fixtures/loose-typescript-group-keys.ts @@ -0,0 +1,23 @@ +// Compiled by group-keys-typescript-modes.test.ts with noImplicitAny on and off. +import { createLiveQueryCollection, groupKeys } from '../../src/index.js' +import type { Collection, ContextFromSource } from '../../src/index.js' + +type User = { id: number; name: string } +declare const users: Collection + +export const grouped = createLiveQueryCollection((q) => + q + .from({ user: users }) + .groupBy(groupKeys(({ user }) => user.name)) + .select(({ user }) => ({ name: user.name })), +) + +// Group keys built outside `.groupBy()` pass the context explicitly. +type UserQuery = ContextFromSource<{ user: typeof users }> +export const nameKeys = groupKeys(({ user }) => user.name) +export const regrouped = createLiveQueryCollection((q) => + q + .from({ user: users }) + .groupBy(nameKeys) + .select(({ user }) => ({ name: user.name })), +) diff --git a/packages/db/tests/group-keys-typescript-modes.test.ts b/packages/db/tests/group-keys-typescript-modes.test.ts new file mode 100644 index 0000000000..698d0bab0f --- /dev/null +++ b/packages/db/tests/group-keys-typescript-modes.test.ts @@ -0,0 +1,110 @@ +// @vitest-environment node +import { fileURLToPath } from 'node:url' +import ts from 'typescript' +import { describe, expect, it } from 'vitest' + +// `groupKeys()` gets the query's context from `.groupBy()`, by inference. +// TypeScript skips that inference without noImplicitAny, so its callback must +// still compile there, untyped like other callbacks in that mode. Strict mode +// must keep it typed. Group keys that pass their context explicitly are typed +// in both modes. Type tests run in strict mode only, so this compiles the +// fixtures in both modes. +const fixture = (name: string) => + fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)) +const config = ts.getParsedCommandLineOfConfigFile( + fileURLToPath(new URL(`../tsconfig.json`, import.meta.url)), + {}, + { ...ts.sys, onUnRecoverableConfigFileDiagnostic: () => {} }, +)! + +// Sources with deliberate errors live in memory, so the project's own +// typecheck does not see them. +const header = `import { createLiveQueryCollection, groupKeys } from '../../src/index.js' +import type { Collection, ContextFromSource } from '../../src/index.js' + +type User = { id: number; name: string } +declare const users: Collection +type UserQuery = ContextFromSource<{ user: typeof users }> +` +type Form = `inline` | `reusable with context` | `reusable without context` + +// A wrong field in each form. Reusable group keys are built outside +// `.groupBy()`, so only an explicit context can type them. +const wrongField: Record = { + inline: `export const q1 = createLiveQueryCollection((q) => + q.from({ user: users }).groupBy(groupKeys(({ user }) => user.nope)), +)`, + 'reusable with context': `export const keys = groupKeys(({ user }) => user.nope)`, + 'reusable without context': `export const keys = groupKeys(({ user }) => user.nope)`, +} + +// Whether each form must report the wrong field, in strict mode and without +// noImplicitAny. Inline operators follow the mode, as other callbacks do. An +// explicit context types a reusable operator in both modes. Without one, +// TypeScript has no context to infer, and the operator is untyped in both: +// a documented limit, which the guide answers with the explicit form. +const reportsWrongField: Record = { + inline: { strict: true, loose: false }, + 'reusable with context': { strict: true, loose: true }, + 'reusable without context': { strict: false, loose: false }, +} + +const forms = Object.keys(wrongField) as Array

+const fileOf = (form: Form) => + fixture(`wrong-field-group-keys-${form.replaceAll(` `, `-`)}.ts`) +const inMemory = new Map( + forms.map((form) => [fileOf(form), `${header}${wrongField[form]}\n`]), +) + +// One program per mode compiles every file; each test reads its own file's +// diagnostics. A program per file would take seconds per test. +const programs = new Map() +function errors(file: string, noImplicitAny: boolean) { + let program = programs.get(noImplicitAny) + if (!program) { + const options = { + ...config.options, + noImplicitAny, + noUnusedLocals: false, + noEmit: true, + } + const host = ts.createCompilerHost(options) + const readFile = host.readFile + host.readFile = (path) => inMemory.get(path) ?? readFile(path) + const fileExists = host.fileExists + host.fileExists = (path) => inMemory.has(path) || fileExists(path) + program = ts.createProgram( + [fixture(`loose-typescript-group-keys.ts`), ...inMemory.keys()], + options, + host, + ) + programs.set(noImplicitAny, program) + } + return ts + .getPreEmitDiagnostics(program, program.getSourceFile(file)) + .map((d) => ts.flattenDiagnosticMessageText(d.messageText, ` `)) +} + +describe(`groupKeys callbacks across TypeScript modes`, () => { + it.each([true, false])( + `compile with noImplicitAny %s`, + (noImplicitAny) => { + expect( + errors(fixture(`loose-typescript-group-keys.ts`), noImplicitAny), + ).toEqual([]) + // Building a program takes seconds; later tests reuse it. + }, + 30_000, + ) + + it.each(forms)( + `reports a wrong field per mode: %s`, + (form) => { + const expected = reportsWrongField[form] + const wrong = [expect.stringContaining(`Property 'nope' does not exist`)] + expect(errors(fileOf(form), true)).toEqual(expected.strict ? wrong : []) + expect(errors(fileOf(form), false)).toEqual(expected.loose ? wrong : []) + }, + 30_000, + ) +}) diff --git a/packages/db/tests/initial-readiness-exposure.test.ts b/packages/db/tests/initial-readiness-exposure.test.ts index 97bd384537..8217ca96d1 100644 --- a/packages/db/tests/initial-readiness-exposure.test.ts +++ b/packages/db/tests/initial-readiness-exposure.test.ts @@ -13,6 +13,7 @@ * `toArrayWhenReady`. */ import { describe, expect, it } from 'vitest' +import { pooledLiveQueries } from '../src/query/pooled-live-query.js' import { createCollection } from '../src/collection/index.js' import { createLiveQueryCollection, eq } from '../src/query/index.js' import { resolveLiveQueryValue } from '../src/live-query-options.js' @@ -99,7 +100,7 @@ describe(`core readers release at initial-query readiness`, () => { const query = new Query() .from({ post: source.collection }) .where(({ post }) => eq(post.id, `p1`)) - const live = resolveLiveQueryValue(query)! + const live = resolveLiveQueryValue(query, { pool: pooledLiveQueries })! // On-demand loading needs the live-query Collection, not the pooled view. expect(`materialize` in live).toBe(false) const subscription = live.subscribeChanges(() => {}) @@ -130,7 +131,7 @@ describe(`core readers release at initial-query readiness`, () => { const query = new Query() .from({ post: source.collection }) .where(({ post }) => eq(post.id, `p1`)) - const live = resolveLiveQueryValue(query)! + const live = resolveLiveQueryValue(query, { pool: pooledLiveQueries })! expect(`materialize` in live).toBe(true) const subscription = live.subscribeChanges(() => {}) await flush() diff --git a/packages/db/tests/live-query-options.test.ts b/packages/db/tests/live-query-options.test.ts index b138edf23e..aae279398a 100644 --- a/packages/db/tests/live-query-options.test.ts +++ b/packages/db/tests/live-query-options.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { include, pooledLiveQueries } from '../src/query/index.js' import { createCollection } from '../src/collection/index.js' import { DbClient, collectionOptions } from '../src/client.js' import { @@ -9,12 +10,8 @@ import { resolveLiveQueryValue, } from '../src/live-query-options.js' import { BaseQueryBuilder, getQueryIR } from '../src/query/builder/index.js' -import { - caseWhen, - eq, - materialize, - toArray, -} from '../src/query/builder/functions.js' +import { caseWhen, eq } from '../src/query/builder/functions.js' +import { materialize, toArray } from '../src/query/builder/include-functions.js' import { collectSourceRefs } from '../src/query/ir.js' import type { DeferredLiveQueryCollections } from '../src/live-query-options.js' @@ -319,13 +316,13 @@ describe(`live query preparation`, () => { .where(({ child }) => eq(child.parentId, parent.id)) return { id: parent.id, - children: childQuery, + children: include(childQuery), nested: { inlineChildren: toArray(childQuery) }, materializedChildren: materialize(childQuery), conditionalChildren: caseWhen( eq(parent.id, `parent`), - childQuery, - childQuery, + include(childQuery), + include(childQuery), ), } }) @@ -436,3 +433,75 @@ describe(`live query identity`, () => { ) }) }) + +describe(`pooling is opt-in`, () => { + const source = () => + createCollection<{ id: number; group: number }>({ + id: `pool-opt-in-${Math.random()}`, + getKey: (row) => row.id, + sync: { sync: ({ markReady }) => markReady() }, + }) + const eqQuery = (rows: ReturnType) => + new BaseQueryBuilder() + .from({ row: rows }) + .where(({ row }) => eq(row.group, 1)) + + // A pooled view has no Collection config; a compiled live query has one. + it(`compiles its own Collection unless the hook passes pooledLiveQueries`, () => { + const rows = source() + expect(resolveLiveQueryValue(eqQuery(rows))?.config).toBeDefined() + expect( + resolveLiveQueryValue({ query: eqQuery(rows) })?.config, + ).toBeDefined() + expect( + resolveLiveQueryValue({ query: eqQuery(rows), pool: pooledLiveQueries }) + ?.config, + ).toBeUndefined() + }) + + // The public observation: every framework hook resolves its query here, so + // two equality-filtered queries without `pool` keep a source subscription + // each. On main before this change they shared one. + it(`keeps one source subscription per query without pool`, async () => { + const rows = source() + const queries = [1, 2].map((group) => + resolveLiveQueryValue( + new BaseQueryBuilder() + .from({ row: rows }) + .where(({ row }) => eq(row.group, group)), + )!, + ) + const subscriptions = queries.map((query) => + query.subscribeChanges(() => {}), + ) + await Promise.all(queries.map((query) => query.preload())) + expect(rows.subscriberCount).toBe(2) + for (const subscription of subscriptions) subscription.unsubscribe() + }) + + it(`does not pool when the hook passes pool: false`, () => { + const rows = source() + expect( + resolveLiveQueryValue( + { query: eqQuery(rows), pool: pooledLiveQueries }, + { pool: false }, + )?.config, + ).toBeDefined() + }) +}) + +describe(`pool does not change a DbClient query's identity`, () => { + it(`hashes a preload and a pooling hook config alike`, () => { + const client = new DbClient() + const rows = createCollection<{ id: number; group: number }>({ + id: `pool-identity-${Math.random()}`, + getKey: (row) => row.id, + sync: { sync: ({ markReady }) => markReady() }, + }) + const query = (q: any) => + q.from({ row: rows }).where(({ row }: any) => eq(row.group, 1)) + const hash = (value: unknown) => + getLiveQueryHash(prepareLiveQueryValue(value, client)) + expect(hash({ query, pool: pooledLiveQueries })).toBe(hash({ query })) + }) +}) diff --git a/packages/db/tests/live-query-order-only-move.test.ts b/packages/db/tests/live-query-order-only-move.test.ts index 5d6bf5181d..25617c1821 100644 --- a/packages/db/tests/live-query-order-only-move.test.ts +++ b/packages/db/tests/live-query-order-only-move.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { include } from '../src/query/index.js' import { createCollection } from '../src/collection/index.js' import { createDeferred } from '../src/deferred.js' import { createLiveQueryCollection } from '../src/query/live-query-collection.js' @@ -392,11 +393,13 @@ describe(`order-only move (RFC #1623 phase 4)`, () => { const lq = createLiveQueryCollection((q) => q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.parentId, parent.id)) - .orderBy(({ child }) => child.position) - .select(({ child }) => ({ id: child.id, name: child.name })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.parentId, parent.id)) + .orderBy(({ child }) => child.position) + .select(({ child }) => ({ id: child.id, name: child.name })), + ), })), ) await lq.preload() @@ -463,18 +466,25 @@ describe(`order-only move (RFC #1623 phase 4)`, () => { const lq = createLiveQueryCollection((q) => q.from({ org: orgs }).select(({ org }) => ({ id: org.id, - teams: q - .from({ team: teams }) - .where(({ team }) => eq(team.orgId, org.id)) - .orderBy(({ team }) => team.position) - .select(({ team }) => ({ - id: team.id, - members: q - .from({ member: members }) - .where(({ member }) => eq(member.teamId, team.id)) - .orderBy(({ member }) => member.position) - .select(({ member }) => ({ id: member.id, name: member.name })), - })), + teams: include( + q + .from({ team: teams }) + .where(({ team }) => eq(team.orgId, org.id)) + .orderBy(({ team }) => team.position) + .select(({ team }) => ({ + id: team.id, + members: include( + q + .from({ member: members }) + .where(({ member }) => eq(member.teamId, team.id)) + .orderBy(({ member }) => member.position) + .select(({ member }) => ({ + id: member.id, + name: member.name, + })), + ), + })), + ), })), ) await lq.preload() diff --git a/packages/db/tests/opt-in-operator-bundle.test.ts b/packages/db/tests/opt-in-operator-bundle.test.ts new file mode 100644 index 0000000000..396736c84f --- /dev/null +++ b/packages/db/tests/opt-in-operator-bundle.test.ts @@ -0,0 +1,204 @@ +// @vitest-environment node +import { readFileSync, readdirSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { build } from 'esbuild' +import { describe, expect, it } from 'vitest' + +/** + * # Does an app pay only for the operators it imports? + * + * Grouping, includes, and pooling are opt-in. Each one's code arrives with + * the value that names it: an aggregate (`count`) or `groupKeys`, an include + * function (`include`, `toArray`, `materialize`), and `pooledLiveQueries`. + * Joins stay in core, so every live-query app bundles them. The law: an app's production + * bundle contains an operator's modules exactly when the app imports one of + * those values. A static import from shared code would break it silently, so + * this bundles small apps from source as an app's build would and reads which + * modules reach the output. + * + * The check runs every operator value alone, so each one is a positive control + * for its own modules: without them, a module renamed or moved would make the + * absence checks pass vacuously. It also runs every combination of operator + * families, each family named by one of its values in turn, so a module that + * only a combination reaches is checked too. + */ +const index = fileURLToPath(new URL(`../src/index.ts`, import.meta.url)) + +const modules = { + grouping: [ + `src/query/compiler/grouping.ts`, + `src/query/compiler/group-by.ts`, + ], + includes: [ + `src/query/live/bucket-facade-adapter.ts`, + `src/query/live/includes-runtime.ts`, + `src/query/live/materialized-pipeline.ts`, + ], + pooling: [`src/query/pooled-live-query.ts`], +} as const +type Family = keyof typeof modules + +/** The values that bring each family's code in. */ +const values: Record> = { + grouping: [`count`, `sum`, `avg`, `min`, `max`, `groupKeys`], + includes: [`include`, `toArray`, `materialize`], + pooling: [`pooledLiveQueries`], +} + +// Joins are core: their modules, and the public-key reduction that joins and +// includes share, are in every live-query app. +const core = [ + `src/query/compiler/joins.ts`, + `src/query/live/public-key-reduction.ts`, +] +const families = Object.keys(modules) as Array + +/** Bundles an app that exports `names` and returns its modules' output bytes. */ +async function bundledModules(names: Array) { + const result = await build({ + stdin: { + contents: `export { ${names.join(`, `)} } from ${JSON.stringify(index)}`, + resolveDir: fileURLToPath(new URL(`.`, import.meta.url)), + loader: `ts`, + }, + bundle: true, + minify: true, + write: false, + metafile: true, + format: `esm`, + platform: `browser`, + packages: `external`, + tsconfigRaw: `{}`, + logLevel: `error`, + define: { 'process.env.NODE_ENV': `"production"` }, + }) + const output = Object.values(result.metafile.outputs)[0]! + const bytes = new Map() + for (const [input, { bytesInOutput }] of Object.entries(output.inputs)) { + if (bytesInOutput > 0) + bytes.set(input.replace(/^.*?(?=src\/)/, ``), bytesInOutput) + } + return bytes +} + +// `concat` and `caseWhen` build select values but bring no operator code. +const coreValues = [ + `createLiveQueryCollection`, + `eq`, + `and`, + `concat`, + `caseWhen`, + `createCollection`, +] + +// Every value that a framework adapter imports from `@tanstack/db`. An +// adapter app bundles these whatever operators it imports, so none of them may +// bring an operator's code. `adapterImports()` below keeps this list current. +const adapterValues = [ + `BaseQueryBuilder`, + `UnhashableQueryIRError`, + `bindLiveQueryWindow`, + `configPool`, + `createEffect`, + `createLiveQueryObserver`, + `createLiveQuerySourceIdClaims`, + `createPacedMutations`, + `deepEquals`, + `fetchNextLiveQueryWindowPage`, + `getLiveQueryHash`, + `getLiveQueryKey`, + `getPreparedLiveQueryIdentity`, + `getPreparedLiveQuerySources`, + `getPublicCollection`, + `getSourceObjectToken`, + `getStableValueHash`, + `identifyLiveQuery`, + `isCollection`, + `isSingleResultCollection`, + `prepareLiveQueryValue`, + `readLiveQueryWindowPageShape`, + `rejectLiveQueryInput`, + `releaseDeferredOnReclaim`, + `resolveLiveQueryValue`, + `resumeDeferredLiveQueryCollections`, +] + +const typicalApp = [...new Set([...coreValues, ...adapterValues])] + +/** The values that the framework adapters import from `@tanstack/db`. */ +function adapterImports() { + const packages = fileURLToPath(new URL(`../..`, import.meta.url)) + const names = new Set() + const visit = (dir: string): void => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name) + if (entry.isDirectory()) visit(path) + // Component files run their imports too. + else if (/\.(tsx?|svelte|vue)$/.test(entry.name)) + for (const [, list] of readFileSync(path, `utf8`).matchAll( + /import\s*\{([^}]*)\}\s*from\s*'@tanstack\/db'/g, + )) + for (const name of list!.split(`,`).map((n) => n.trim())) + if (name && !name.startsWith(`type `)) + names.add(name.split(/\s+as\s+/)[0]!) + } + } + for (const adapter of [ + `react-db`, + `preact-db`, + `vue-db`, + `solid-db`, + `svelte-db`, + `angular-db`, + ]) + visit(join(packages, adapter, `src`)) + return [...names].sort() +} + +/** The law: an operator's modules are in the bundle exactly when it is imported. */ +function expectOperators( + bytes: ReadonlyMap, + imported: ReadonlySet, +) { + for (const family of families) + for (const file of modules[family]) + expect(bytes.has(file), `${family}: ${file}`).toBe(imported.has(family)) + for (const file of core) expect(bytes.has(file), file).toBe(true) +} + +describe(`opt-in operator bundles`, () => { + it(`lists every value the framework adapters import`, () => { + expect(adapterImports()).toEqual([...adapterValues].sort()) + }) + + it.each( + Object.entries(values).flatMap(([family, names]) => + names.map((name) => [name, family as Family] as const), + ), + )(`brings in only what %s needs (%s)`, async (name, family) => { + expectOperators( + await bundledModules([...typicalApp, name]), + new Set([family]), + ) + }) + + // Every subset of families, the empty one included. Subset i names each + // family by its value at position i, so the combinations rotate values. + const subsets = Array.from({ length: 2 ** families.length }, (_, i) => + families.filter((_family, bit) => i & (1 << bit)), + ) + it.each( + subsets.map( + (subset, i) => [subset.join(` + `) || `none`, subset, i] as const, + ), + )(`keeps the law for %s`, async (_label, subset, i) => { + const names = subset.map( + (family) => values[family][i % values[family].length]!, + ) + expectOperators( + await bundledModules([...typicalApp, ...names]), + new Set(subset), + ) + }) +}) diff --git a/packages/db/tests/query/aggregate-select-oracle.property.test.ts b/packages/db/tests/query/aggregate-select-oracle.property.test.ts index 780c768a68..570ddc99af 100644 --- a/packages/db/tests/query/aggregate-select-oracle.property.test.ts +++ b/packages/db/tests/query/aggregate-select-oracle.property.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest' import { fc } from '@fast-check/vitest' -import { createCollection } from '../../src/collection/index.js' import { add, caseWhen, @@ -8,18 +7,37 @@ import { count, createLiveQueryCollection, eq, + groupKeys, + gt, inArray, + include as includeWrap, + materialize, max, sum, toArray, upper, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { BaseQueryBuilder } from '../../src/query/builder/index.js' +import { getQueryIR } from '../../src/query/builder/query-ir.js' +import { + Aggregate, + ConditionalSelect, + Func, + IncludesSubquery, + QueryRef, + UnionAll, + UnionFrom, +} from '../../src/query/ir.js' import { AggregateFunctionNotInSelectError, + GroupingOperatorRequiredError, + IncludeOperatorRequiredError, NonAggregateExpressionNotInGroupByError, } from '../../src/errors.js' import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js' import { oraclePropertyOptions, oracleRuns } from '../oracle-config.js' +import { createOracleReach } from '../oracle-reach.js' /** * # What does an aggregate query publish beside its aggregates? @@ -82,8 +100,75 @@ import { oraclePropertyOptions, oracleRuns } from '../oracle-config.js' * can compare with key values, so a missing key changes the branch taken. Group order is not part of the law, so rows are * compared in a fixed order. * - * Limits: one parent field (`x`), one source field (`v`) and one group key - * (`k`); numbers only. + * A query also needs the operator code its select and grouping use. Group + * keys passed to `groupBy` bare carry no grouping compiler, so a grouped + * query with no aggregate anywhere, in its select, HAVING, or orderBy, throws + * `GroupingOperatorRequiredError` unless it wraps its keys in `groupKeys()`; + * any aggregate brings the compiler, and the query compiles all its + * aggregates with it. A query builder in + * a select value is a subquery only inside `include()`, `toArray()`, or + * `materialize()`: a bare one throws `IncludeOperatorRequiredError` when the + * select is built, as a field, inside a nested object, or in a conditional + * branch. A wrapped include inside a nested object throws; a conditional + * branch may be one, which the includes-collection oracle publishes. Authority: the + * maintainer's decision in PR #2232 that these operators are opt-in and that + * a missing operator throws. When several errors apply, the query throws one + * of them; which one is not part of the law. + * + * Query IR built by hand must carry the same operator code: the grouping + * compiler on each aggregate and on `groupKeys()` keys, and the include + * runtime on each include. IR without it throws the coded error for the + * missing operator when it compiles, on every path: a grouped query, one + * implicit group, HAVING, and an include, unless another aggregate or + * `groupKeys()` brings the grouping compiler. Authority: the changeset ("Code that + * builds query IR by hand must supply the operator code") and AGENTS.md, which + * requires a contract contradiction to fail at the boundary. + * + * Each history chooses whether a grouped query passes its keys bare, where a + * nested include sits and how it is wrapped (`toArray()`, `include()`, + * `materialize()`, or not at all), and, in three generated histories of six, + * whether the driver strips one kind of operator code, the grouping compiler, the + * include runtime, or the orderBy aggregate's compiler, from the IR it built before compiling it through + * `new BaseQueryBuilder(ir)`. Stripping one kind at a time keeps a missing + * include runtime from hiding behind a missing grouping compiler. A history + * that strips the include runtime gives its include a child query that is + * valid on its own, `count()` alone, so only the missing runtime can fail it. + * Half the histories that strip the grouping compiler likewise give the + * top-level query a select of `count()` alone, with an aggregate HAVING when + * the generated shape has one. One history in four is such a forced + * history: it strips the grouping compiler everywhere or from orderBy only, + * with no grouping, one key, or two keys, in turn, so every campaign reaches + * each path whatever its seed. A model error set of exactly + * `groupingRequired` then witnesses each path, grouped, one implicit group, + * and HAVING, where no other error can hide the missing compiler. The + * property declares the states these laws need with `createOracleReach` and + * fails when a campaign does not reach each of them. + * + * Both the top-level query and the include query may order their groups + * with one or two `orderBy` calls, the first primary (docs: "Multiple Column + * Ordering"). A term orders by an aggregate, ascending or descending, or by a + * select field through `$selected` (docs: "Ordering by SELECT Fields"): a + * number field, or a string group key with the documented `en-US` locale + * collation. Published rows, and each parent's `kids`, then fall into blocks + * whose orderBy values compare equal, in the order the terms give; order + * within a block is not part of the law. An orderBy aggregate that no + * top-level field selects throws `AggregateFunctionNotInSelectError`. In one + * generated history of six, in half the forced ones, and in one ordered + * history of three, the driver strips the grouping compiler from the + * top-level orderBy aggregates only; the query then compiles with the + * compiler that `groupKeys()` or a select or HAVING aggregate brings, and + * throws `GroupingOperatorRequiredError` when none does. Generated shapes + * with an orderBy rarely compile, so one history in four takes ordered + * shapes from a grammar whose shapes compile. The source starts with a row + * whose key `A` sorts between `a` and `b`, where locale and code-unit order + * differ. + * + * Limits: one parent field (`x`) and one source field (`v`); groups by one, + * two, or three keys; numbers and string keys only. A boolean, Date, `NaN`, + * or nested-object select field never orders the rows, and no orderBy term + * is a raw expression other than an aggregate. A nested include in a + * unionAll query is rejected before compilation, so the driver checks only + * that it throws. */ const PROPERTY = `aggregate.select-shapes` @@ -109,16 +194,47 @@ type Shape = { */ having?: { part: Part; lit: number | string; selected?: string } spread?: `source` | `parent` - /** A nested include beside the aggregate. */ - include?: boolean + /** + * One or two `orderBy` calls; the first is the primary sort. A term orders + * by an aggregate, which reads the same aggregate selected as a top-level + * field, or by a select field through `$selected`. + */ + orderBy?: Array + /** + * A nested include beside the aggregate: wrapped in `toArray()` or bare, as + * a field, inside a nested object, or in both branches of a conditional. + */ + include?: { + wrap: `toArray` | `include` | `materialize` | `bare` + at: `field` | `nested` | `case` + } } +type OrderTerm = + | { by: `agg`; fn: `count` | `sum` | `max`; dir: `asc` | `desc` } + | { + by: `selected` + field: string + /** A string field sorts with the documented `en-US` locale collation. */ + kind: `number` | `string` + dir: `asc` | `desc` + } + /** * How a history groups: not at all, by the ref `c.k`, or by the computed * expression `upper(c.k)` or `inArray(c.k, ['a', 'b'])`. The `key` leaf is * that group expression, built again with a new list literal each time. + * `pair` groups by two keys, `c.k` and `gt(c.v, 1)`, and `triple` by three, + * adding `gt(c.v, 2)`; their `key` leaf is `c.k`, and an update of `v` can + * move a row to another group. */ -type Grouping = `none` | `key` | `upper` | `inList` | `date` | `nan` +type Grouping = + `none` | `key` | `upper` | `inList` | `date` | `nan` | `pair` | `triple` + +/** Groupings whose keys include the raw field `c.k`. */ +function groupsByRawKey(grouping: Grouping): boolean { + return grouping === `key` || grouping === `pair` || grouping === `triple` +} type Issue = { id: number; k: string; x: number } type Comment = { id: number; k: string; v: number } @@ -154,7 +270,7 @@ function readsSourceOutsideAggregate(part: Part, grouping: Grouping): boolean { // Under `upper(c.k)`, `a` and `A` share a group, so `c.k` is a source // field with no single value for it. case `rawKey`: - return grouping !== `key` + return !groupsByRawKey(grouping) case `add`: return [part.a, part.b].some(reads) case `case`: @@ -193,11 +309,7 @@ function modelError( grouping: Grouping, ): `nonAggregate` | `aggregateNotInSelect` | undefined { if (modelThrows(shape, grouping)) return `nonAggregate` - const selected = new Set( - Object.values(shape.fields).flatMap((part) => - part.k === `agg` ? [part.fn] : [], - ), - ) + const selected = selectedAggregates(shape) if ( shape.having && shape.having.selected === undefined && @@ -207,19 +319,131 @@ function modelError( return undefined } +/** The aggregates the select holds as top-level fields. */ +function selectedAggregates(shape: Shape): Set<`count` | `sum` | `max`> { + return new Set( + Object.values(shape.fields).flatMap((part) => + part.k === `agg` ? [part.fn] : [], + ), + ) +} + function modelThrows(shape: Shape, grouping: Grouping): boolean { return ( shape.spread !== undefined || (shape.having?.selected === undefined && shape.having !== undefined && readsSourceOutsideAggregate(shape.having.part, grouping)) || - shape.include === true || + (shape.include !== undefined && shape.include.wrap !== `bare`) || Object.values(shape.fields).some((part) => readsSourceOutsideAggregate(part, grouping), ) ) } +type ModelError = + | `nonAggregate` + | `aggregateNotInSelect` + | `includeRequired` + | `groupingRequired` + | `nestedInclude` + +/** Whether the select or HAVING holds an aggregate. */ +function selectOrHavingAggregate(shape: Shape): boolean { + return ( + Object.values(shape.fields).some(hasAggregate) || + (shape.having !== undefined && + shape.having.selected === undefined && + hasAggregate(shape.having.part)) + ) +} + +/** The aggregates the orderBy terms read. */ +function orderAggregates(shape: Shape): Array<`count` | `sum` | `max`> { + return (shape.orderBy ?? []).flatMap((term) => + term.by === `agg` ? [term.fn] : [], + ) +} + +/** An aggregate in the select, HAVING, or orderBy brings the grouping compiler. */ +function shapeHasAggregate(shape: Shape): boolean { + return selectOrHavingAggregate(shape) || orderAggregates(shape).length > 0 +} + +/** + * Which operator code a hand-built history leaves out of its IR: the grouping + * compiler everywhere, the include runtime, or the grouping compiler on the + * orderBy aggregate only. + */ +type Stripped = `none` | `grouping` | `runtime` | `orderBy` + +/** Every error the model says applies. The query must throw one of them. */ +function modelErrors( + shape: Shape, + grouping: Grouping, + bareKeys: boolean, + stripped: Stripped, +): Set { + const errors = new Set() + // Every shape aggregates or groups, so IR without the grouping compiler + // cannot compile. + if (stripped === `grouping`) errors.add(`groupingRequired`) + // A wrapped include without its runtime cannot compile either. + if ( + stripped === `runtime` && + shape.include !== undefined && + shape.include.wrap !== `bare` + ) + errors.add(`includeRequired`) + // A bare query builder is no include; the select rejects it when built. + if (shape.include?.wrap === `bare`) errors.add(`includeRequired`) + // An include inside a nested object throws (db-core skill, "Includes + // rules"). A conditional branch may be an include: the includes-collection + // oracle publishes one through guard transitions. + if ( + shape.include !== undefined && + shape.include.wrap !== `bare` && + shape.include.at === `nested` + ) + errors.add(`nestedInclude`) + // Bare group keys carry no grouping compiler, and no aggregate brings one. + if (grouping !== `none` && bareKeys && !shapeHasAggregate(shape)) + errors.add(`groupingRequired`) + // An orderBy aggregate without its compiler compiles with the one that + // `groupKeys()` or a select or HAVING aggregate brings, if any. + if ( + stripped === `orderBy` && + orderAggregates(shape).length > 0 && + !(grouping !== `none` && !bareKeys) && + !selectOrHavingAggregate(shape) + ) + errors.add(`groupingRequired`) + // An orderBy aggregate reads the same aggregate selected as a top-level + // field (docs: "Ordering by SELECT Fields"). + if (orderAggregates(shape).some((fn) => !selectedAggregates(shape).has(fn))) + errors.add(`aggregateNotInSelect`) + const error = modelError(shape, grouping) + if (error) errors.add(error) + return errors +} + +function errorName(error: unknown): ModelError | `other` { + if (error instanceof IncludeOperatorRequiredError) return `includeRequired` + if (error instanceof GroupingOperatorRequiredError) return `groupingRequired` + if (error instanceof NonAggregateExpressionNotInGroupByError) + return `nonAggregate` + if (error instanceof AggregateFunctionNotInSelectError) + return `aggregateNotInSelect` + // This error site throws a plain coded Error; tests run the development + // build, whose message names the rule. + if ( + error instanceof Error && + /must be at the top level of select\(\)/.test(error.message) + ) + return `nestedInclude` + return `other` +} + function groupKeyOf( row: Comment, grouping: Grouping, @@ -233,6 +457,9 @@ function groupKeyOf( /** Group identity by value: equal Dates and NaN each form one group. */ function groupIdOf(row: Comment, grouping: Grouping): string { + if (grouping === `pair`) return `${row.k}|${String(row.v > 1)}` + if (grouping === `triple`) + return `${row.k}|${String(row.v > 1)}|${String(row.v > 2)}` const key = groupKeyOf(row, grouping) return key instanceof Date ? `date:${key.getTime()}` @@ -279,13 +506,13 @@ function evaluate( } } -/** One published row per group; a grouped query groups by `k`. */ -function modelGroup( +/** The groups that HAVING keeps; a grouped query groups by `k`. */ +function modelGroups( shape: Shape, rows: ReadonlyArray, parent: Issue | undefined, grouping: Grouping, -): Array> { +): Array> { const groups = grouping !== `none` ? [...new Set(rows.map((row) => groupIdOf(row, grouping)))].map((k) => @@ -298,25 +525,90 @@ function modelGroup( // on `$selected` reads a select field's group value. const havingPart = (having: NonNullable) => having.selected === undefined ? having.part : shape.fields[having.selected]! - const kept = shape.having + return shape.having ? groups.filter( (group) => evaluate(havingPart(shape.having!), group, parent, grouping) === shape.having!.lit, ) : groups +} + +function modelRow( + shape: Shape, + group: ReadonlyArray, + parent: Issue | undefined, + grouping: Grouping, +): Record { + return Object.fromEntries( + Object.entries(shape.fields).map(([key, part]) => [ + key, + evaluate(part, group, parent, grouping), + ]), + ) +} + +/** One published row per kept group. */ +function modelGroup( + shape: Shape, + rows: ReadonlyArray, + parent: Issue | undefined, + grouping: Grouping, +): Array> { return sortRows( - kept.map((group) => - Object.fromEntries( - Object.entries(shape.fields).map(([key, part]) => [ - key, - evaluate(part, group, parent, grouping), - ]), - ), + modelGroups(shape, rows, parent, grouping).map((group) => + modelRow(shape, group, parent, grouping), ), ) } +/** The documented `en-US` locale collation for string orderBy terms. */ +const collator = new Intl.Collator(`en-US`) + +/** + * The published order under `orderBy`: blocks of rows whose orderBy values + * compare equal, in the order the terms give, the first term primary. Order + * within a block is not part of the law. + */ +function modelOrderBlocks( + shape: Shape, + rows: ReadonlyArray, + parent: Issue | undefined, + grouping: Grouping, +): Array>> { + const terms = shape.orderBy! + const keyed = modelGroups(shape, rows, parent, grouping).map((group) => { + const row = modelRow(shape, group, parent, grouping) + return { + row, + values: terms.map((term) => + term.by === `agg` + ? evaluate({ k: `agg`, fn: term.fn }, group, parent, grouping) + : row[term.field], + ), + } + }) + const compare = (a: Array, b: Array): number => { + for (const [index, term] of terms.entries()) { + const [x, y] = [a[index], b[index]] + const result = + typeof x === `string` && typeof y === `string` + ? collator.compare(x, y) + : (x as number) - (y as number) + if (result !== 0) return term.dir === `asc` ? result : -result + } + return 0 + } + const sorted = [...keyed].sort((a, b) => compare(a.values, b.values)) + const blocks: Array>> = [] + sorted.forEach((entry, index) => { + if (index === 0 || compare(sorted[index - 1]!.values, entry.values) !== 0) + blocks.push([]) + blocks[blocks.length - 1]!.push(entry.row) + }) + return blocks.map(sortRows) +} + /** Group order is not part of the law. */ function sortRows( rows: Array>, @@ -414,6 +706,12 @@ const part = ( ) // A grouped query needs no aggregate; an ungrouped one is an aggregate query // only when it holds one. +const orderSeed: fc.Arbitrary = fc.record({ + fn: fc.constantFrom(`count` as const, `sum` as const, `max` as const), + dir: fc.constantFrom(`asc` as const, `desc` as const), + selected: fc.option(fc.nat(2), { nil: undefined, freq: 2 }), +}) + const shape = (include: boolean, grouping: Grouping): fc.Arbitrary => fc .record({ @@ -430,7 +728,27 @@ const shape = (include: boolean, grouping: Grouping): fc.Arbitrary => ), { nil: undefined, freq: 5 }, ), - include: fc.boolean().map((b) => (b ? true : undefined)), + // Terms are chosen after the fields, so they can name one. + orderSeeds: fc.option( + fc.array(orderSeed, { minLength: 1, maxLength: 2 }), + { nil: undefined, freq: 2 }, + ), + include: fc.option( + fc.record({ + wrap: fc.constantFrom( + `toArray` as const, + `include` as const, + `materialize` as const, + `bare` as const, + ), + at: fc.constantFrom( + `field` as const, + `nested` as const, + `case` as const, + ), + }), + { nil: undefined, freq: 2 }, + ), having: fc.option( fc.record({ // A condition may read aggregates, selected as fields or not. One @@ -459,6 +777,127 @@ const shape = (include: boolean, grouping: Grouping): fc.Arbitrary => .filter( (s) => grouping !== `none` || Object.values(s.fields).some(hasAggregate), ) + .map(({ orderSeeds, ...s }): Shape => + orderSeeds ? { ...s, orderBy: orderTerms(s, orderSeeds, grouping) } : s, + ) + +type OrderSeed = { + fn: `count` | `sum` | `max` + dir: `asc` | `desc` + selected: number | undefined +} + +/** + * The orderBy terms for some seeds. A seed with a field index orders by that + * select field when the field has an order: a number, or a string group key. + */ +function orderTerms( + s: Shape, + seeds: ReadonlyArray, + grouping: Grouping, +): Array { + const orderable = Object.entries(s.fields).flatMap(([field, part]) => { + const kind = orderKind(part, grouping) + return kind ? [{ field, kind }] : [] + }) + return seeds.map(({ fn, dir, selected }): OrderTerm => { + const target = + selected === undefined || orderable.length === 0 + ? undefined + : orderable[selected % orderable.length]! + return target ? { by: `selected`, ...target, dir } : { by: `agg`, fn, dir } + }) +} + +/** + * Ordered shapes that compile: fields of aggregates, literals, the group + * key (always, when it is a string), a parent field, or an aggregate plus a + * literal, with no HAVING, + * spread, or include, ordered by one or two terms. An aggregate term orders + * by an aggregate that a field selects. Generated shapes compile too rarely + * to reach the ordering laws on their own. + */ +const orderedShape = ( + include: boolean, + grouping: Grouping, +): fc.Arbitrary => { + const agg = fc + .constantFrom(`count` as const, `sum` as const, `max` as const) + .map((fn): Part => ({ k: `agg`, fn })) + const field = fc.oneof( + { weight: 3, arbitrary: agg }, + literal.map((n): Part => ({ k: `lit`, n })), + ...(grouping === `none` ? [] : [fc.constant({ k: `key` })]), + ...(include ? [fc.constant({ k: `parent` })] : []), + fc + .tuple(agg, literal) + .map(([a, n]): Part => ({ k: `add`, a, b: { k: `lit`, n } })), + ) + return fc + .tuple( + fc.dictionary(fc.constantFrom(`f0`, `f1`, `f2`), field, { + minKeys: 1, + maxKeys: 3, + }), + fc.array(orderSeed, { minLength: 1, maxLength: 2 }), + ) + .filter( + ([fields]) => + grouping !== `none` || Object.values(fields).some(hasAggregate), + ) + .map(([generated, seeds]): Shape => { + // A grouping with a string key always selects it, so string terms occur. + const fields = + orderKind({ k: `key` }, grouping) === `string` + ? { ...generated, fk: { k: `key` } as Part } + : generated + const s: Shape = { fields } + const selected = [...selectedAggregates(s)] + // An aggregate term names an aggregate the select holds, if any. + const usable = seeds.map((seed) => + selected.length === 0 || selected.includes(seed.fn) + ? seed + : { ...seed, fn: selected[0]! }, + ) + return { ...s, orderBy: orderTerms(s, usable, grouping) } + }) +} + +/** + * Whether a select field has an order the model can state: numbers, or the + * string group key under the documented locale collation. Booleans, Dates, + * `NaN`, nested objects, and values that mix types do not. + */ +function orderKind( + part: Part, + grouping: Grouping, +): `number` | `string` | undefined { + switch (part.k) { + case `lit`: + case `parent`: + case `agg`: + return `number` + case `add`: + return orderKind(part.a, grouping) === `number` && + orderKind(part.b, grouping) === `number` + ? `number` + : undefined + case `case`: { + const a = orderKind(part.a, grouping) + return a !== undefined && a === orderKind(part.b, grouping) + ? a + : undefined + } + case `key`: + return groupsByRawKey(grouping) || grouping === `upper` + ? `string` + : undefined + case `rawKey`: + return groupsByRawKey(grouping) ? `string` : undefined + default: + return undefined + } +} type Step = | { t: `parent`; id: number; x: number } @@ -493,6 +932,8 @@ const step: fc.Arbitrary = fc.oneof( /** Build the select value of a part from the query's refs. */ /** The group expression of the current history. */ let keyExpression: (c: any) => any = (c) => c.k +/** What the history passes to `groupBy`: its key, or for `pair` two keys. */ +let groupExpressions: (c: any) => any = (c) => c.k function build(p: Part, c: any, issue: any): any { switch (p.k) { @@ -540,18 +981,144 @@ function buildSelect( const fields: Record = Object.fromEntries( Object.entries(s.fields).map(([key, p]) => [key, build(p, c, issue)]), ) - if (s.include) - fields.nestedKids = toArray( - q + if (s.include) { + const { wrap, at } = s.include + // Each use builds its own child query, as separate select values do. + const kids = () => { + const child = q .from({ i: issueSource }) .where(({ i }: any) => eq(i.k, c.k)) - .select(({ i }: any) => ({ id: i.id })), - ) + .select(({ i }: any) => ({ id: i.id })) + return wrap === `toArray` + ? toArray(child) + : wrap === `include` + ? includeWrap(child) + : wrap === `materialize` + ? materialize(child) + : child + } + // A constant condition, so the conditional reads no source field. + fields.nestedKids = + at === `field` + ? kids() + : at === `nested` + ? { inner: kids() } + : caseWhen(eq(1, 1), { inner: kids() }, { inner: kids() }) + } if (s.spread === `source`) return { ...c, ...fields } if (s.spread === `parent`) return { ...issue, ...fields } return fields } +/** + * A copy of query IR without its operator code, as hand-built IR that forgets + * it would be: no grouping on the query or its aggregates, no runtime on its + * includes. Source refs are kept as they are, because their identity is the + * compiled input identity. + */ +function withoutOperatorCode(value: T, stripped: Stripped): T { + const grouping = stripped === `grouping` + const runtime = stripped === `runtime` + const copied = (node: any, fields: Record) => + Object.assign(Object.create(Object.getPrototypeOf(node)), node, fields) + const strip = (node: any): any => { + if (Array.isArray(node)) return node.map(strip) + if (node instanceof Aggregate) + return copied(node, { + args: strip(node.args), + ...(grouping ? { grouping: undefined } : {}), + }) + if (node instanceof IncludesSubquery) + return copied(node, { + query: strip(node.query), + ...(runtime ? { runtime: undefined } : {}), + }) + if (node instanceof QueryRef) + return copied(node, { query: strip(node.query) }) + if (node instanceof UnionFrom || node instanceof UnionAll) + return copied( + node, + Object.fromEntries( + Object.entries(node).map(([key, field]) => [key, strip(field)]), + ), + ) + if (node instanceof Func) return copied(node, { args: strip(node.args) }) + if (node instanceof ConditionalSelect) + return copied(node, { + branches: node.branches.map((branch: any) => ({ + ...branch, + condition: strip(branch.condition), + value: strip(branch.value), + })), + defaultValue: strip(node.defaultValue), + }) + if (node && Object.getPrototypeOf(node) === Object.prototype) { + const fields = Object.fromEntries( + Object.entries(node).map(([key, field]) => [key, strip(field)]), + ) + if (grouping && `from` in node) fields.grouping = undefined + return fields + } + return node + } + return strip(value) +} + +/** + * The published sequence splits into the model's blocks, in order. A + * sequence with several blocks witnesses each kind of term it orders by. + */ +function expectOrder( + label: string, + shape: Shape, + published: ReadonlyArray, + blocks: Array>>, +): void { + const rows = published.map((row: any) => stripVirtualProps(row)) + let at = 0 + for (const block of blocks) { + expect( + sortRows(rows.slice(at, at + block.length)), + `${label} rows in orderBy position ${at}`, + ).toEqual(block) + at += block.length + } + expect(rows.length, `${label} row count`).toBe(at) + if (blocks.length < 2) return + const terms = shape.orderBy! + if (terms.some((term) => term.by === `agg`)) + reach?.reach(`an aggregate orders the published rows`) + if (terms.some((term) => term.by === `selected` && term.kind === `number`)) + reach?.reach(`a number select field orders the published rows`) + const strings = terms.flatMap((term) => + term.by === `selected` && term.kind === `string` ? [term.field] : [], + ) + if (strings.length > 0) + reach?.reach(`a string select field orders the published rows`) + // Locale and code-unit order differ only between `A` and a lowercase key. + const keys = new Set( + rows.flatMap((row: any) => strings.map((field) => row[field])), + ) + if (keys.has(`A`) && (keys.has(`a`) || keys.has(`b`))) + reach?.reach(`a string field orders A against a lowercase key`) + if (terms.length > 1) reach?.reach(`two orderBy terms order the rows`) +} + +/** A copy of query IR whose orderBy aggregates lack the grouping compiler. */ +function withoutOrderByGrouping(ir: any): any { + return { + ...ir, + orderBy: ir.orderBy?.map((clause: any) => ({ + ...clause, + expression: Object.assign( + Object.create(Object.getPrototypeOf(clause.expression)), + clause.expression, + { grouping: undefined }, + ), + })), + } +} + async function runHistory( top: Shape, nested: Shape, @@ -559,8 +1126,47 @@ async function runHistory( shadow: boolean, steps: ReadonlyArray, union = false, + bareKeys = false, + stripped: Stripped = `none`, + alone = false, ): Promise { const grouped = grouping !== `none` + // Most generated shapes throw on their own; a runtime-stripped history + // needs an include that only the missing runtime can fail. + if (stripped === `runtime`) + nested = { fields: { f0: { k: `agg`, fn: `count` } } } + // Half the grouping-stripped histories give the top-level query a select + // that is valid on its own, with an aggregate HAVING when the generated + // shape has one, so only the missing grouping compiler can fail it. + if (stripped === `grouping` && alone) + top = { + fields: { f0: { k: `agg`, fn: `count` } }, + having: top.having && { part: { k: `agg`, fn: `count` }, lit: 2 }, + } + // Likewise half the orderBy-stripped histories select the aggregate they + // order by, so the select aggregate's compiler must order the rows. + if (stripped === `orderBy` && alone) { + const first = top.orderBy?.[0] + const term: OrderTerm = + first?.by === `agg` ? first : { by: `agg`, fn: `sum`, dir: `asc` } + top = { fields: { f0: { k: `agg`, fn: term.fn } }, orderBy: [term] } + } + reach?.beginHistory() + if (stripped === `runtime`) reach?.reach(`include runtime stripped`) + for (const s of [top, nested]) { + if (s.include?.wrap === `bare`) + reach?.reach(`bare include: ${s.include.at}`) + if (s.include?.wrap === `toArray` && s.include.at === `case`) + reach?.reach(`toArray include in a conditional branch`) + if (s.include?.wrap === `include` || s.include?.wrap === `materialize`) + reach?.reach(`${s.include.wrap}() wrap`) + if (grouped && bareKeys) + reach?.reach( + shapeHasAggregate(s) + ? `bare group keys beside an aggregate` + : `bare group keys without an aggregate`, + ) + } // Each use builds a new list literal, as separate query callbacks do. keyExpression = grouping === `upper` @@ -572,15 +1178,24 @@ async function runHistory( : grouping === `nan` ? (c) => add(c.v, NaN) : (c) => c.k + groupExpressions = + grouping === `pair` + ? (c) => [c.k, gt(c.v, 1)] + : grouping === `triple` + ? (c) => [c.k, gt(c.v, 1), gt(c.v, 2)] + : keyExpression const suffix = Math.random().toString(36).slice(2) let issues: Array = [ { id: 1, k: `a`, x: 1 }, { id: 2, k: `b`, x: 2 }, ] + // `A` matches no parent; under `upper(c.k)` it shares a group with `a`, + // and a string term orders it between `a` and `b` in `en-US` collation. let comments: Array = [ { id: 10, k: `a`, v: 1 }, { id: 11, k: `a`, v: 2 }, { id: 12, k: `b`, v: 3 }, + { id: 13, k: `A`, v: 2 }, ] let nextId = 20 const issueSource = createCollection( @@ -612,10 +1227,11 @@ async function runHistory( // alias. const rowOf = (refs: any, alias: string | undefined) => alias === undefined ? refs : refs[alias] - const group = (query: any, alias: string | undefined) => - grouped - ? query.groupBy((refs: any) => keyExpression(rowOf(refs, alias))) - : query + const group = (query: any, alias: string | undefined) => { + if (!grouped) return query + const keys = (refs: any) => groupExpressions(rowOf(refs, alias)) + return query.groupBy(bareKeys ? keys : groupKeys(keys)) + } const having = ( s: Shape, query: any, @@ -650,37 +1266,81 @@ async function runHistory( ) : q.from({ c: commentSource }) const topAlias = union ? undefined : `c` + // The hand-built path compiles a copy of the built IR without its operator + // code. + const finish = (built: any) => + stripped === `none` + ? built + : new BaseQueryBuilder( + stripped === `orderBy` + ? withoutOrderByGrouping(getQueryIR(built)) + : withoutOperatorCode(getQueryIR(built), stripped), + ) + // Each term is one `orderBy` call, in order; a string term passes the + // documented locale collation. + const order = (s: Shape, query: any, alias: string | undefined) => + (s.orderBy ?? []).reduce( + (ordered: any, term: OrderTerm) => + ordered.orderBy( + (refs: any) => + term.by === `agg` + ? build({ k: `agg`, fn: term.fn }, rowOf(refs, alias), undefined) + : refs.$selected[term.field], + term.by === `selected` && term.kind === `string` + ? { direction: term.dir, stringSort: `locale`, locale: `en-US` } + : term.dir, + ), + query, + ) const topQuery = compile(() => createLiveQueryCollection((q) => - having( - top, - group(topSource(q), topAlias).select((refs: any) => - buildSelect(top, rowOf(refs, topAlias), undefined, q, issueSource), + finish( + order( + top, + having( + top, + group(topSource(q), topAlias).select((refs: any) => + buildSelect( + top, + rowOf(refs, topAlias), + undefined, + q, + issueSource, + ), + ), + topAlias, + ), + topAlias, ), - topAlias, ), ), ) const includeQuery = compile(() => createLiveQueryCollection((q) => - q.from({ issue: issueSource }).select(({ issue }) => ({ - id: issue.id, - kids: toArray( - having( - nested, - group( - q - .from({ [childAlias]: commentSource }) - .where((refs: any) => eq(refs[childAlias].k, issue.k)), + finish( + q.from({ issue: issueSource }).select(({ issue }) => ({ + id: issue.id, + kids: toArray( + order( + nested, + having( + nested, + group( + q + .from({ [childAlias]: commentSource }) + .where((refs: any) => eq(refs[childAlias].k, issue.k)), + childAlias, + ).select((refs: any) => + buildSelect(nested, refs[childAlias], issue, q, issueSource), + ), + childAlias, + issue, + ), childAlias, - ).select((refs: any) => - buildSelect(nested, refs[childAlias], issue, q, issueSource), ), - childAlias, - issue, ), - ), - })), + })), + ), ), ) // Compilation: the query throws exactly when the model says no value exists. @@ -688,18 +1348,48 @@ async function runHistory( [`top`, topQuery, top], [`include`, includeQuery, nested], ] as const) { - const error = modelError(s, grouping) + // The orderBy strip reaches the top-level query only. + const errors = modelErrors( + s, + grouping, + bareKeys, + label === `include` && stripped === `orderBy` ? `none` : stripped, + ) + // The include query's `kids` include loses its runtime too. + if (stripped === `runtime` && label === `include`) + errors.add(`includeRequired`) + // A stripped grouping compiler is witnessed on a path only where no + // other error could hide it. + if (stripped === `grouping` && errors.size === 1 && shapeHasAggregate(s)) { + reach?.reach( + grouped + ? `grouping stripped alone: grouped` + : `grouping stripped alone: one group`, + ) + if ( + s.having !== undefined && + s.having.selected === undefined && + hasAggregate(s.having.part) + ) + reach?.reach(`grouping stripped alone: HAVING aggregate`) + } + if ( + stripped === `orderBy` && + label === `top` && + orderAggregates(s).length > 0 && + errors.size === 0 + ) + reach?.reach(`orderBy aggregate stripped beside a select aggregate`) // A nested include cannot correlate with a unionAll row, which has no // source alias, so the builder rejects it first. - const unionInclude = label === `top` && union && s.include === true - if (error) { + const unionInclude = label === `top` && union && s.include !== undefined + if (errors.size > 0) { expect(result.ok, `${label} compile throws`).toBe(false) if (!result.ok && !unionInclude) - expect(result.error, `${label} error class`).toBeInstanceOf( - error === `nonAggregate` - ? NonAggregateExpressionNotInGroupByError - : AggregateFunctionNotInSelectError, - ) + expect( + [...errors], + `${label} error class (${String(result.error)})`, + ).toContain(errorName(result.error)) } else { expect(result.ok, `${label} compiles`).toBe(true) } @@ -721,10 +1411,21 @@ async function runHistory( ), `${label}: top-level rows`, ).toEqual(modelGroup(top, comments, undefined, grouping)) + if (grouping === `pair` && topQuery.value.toArray.length > 1) + reach?.reach(`two group keys publish several groups`) + if (grouping === `triple` && topQuery.value.toArray.length > 1) + reach?.reach(`three group keys publish several groups`) + if (top.orderBy) + expectOrder( + `${label}: top-level`, + top, + topQuery.value.toArray, + modelOrderBlocks(top, comments, undefined, grouping), + ) } if (includeQuery.ok) { for (const issue of issues) { - const row = includeQuery.value.get(issue.id) as any + const row = includeQuery.value.get(issue.id) expect( sortRows( (row?.kids ?? []).map((kid: any) => stripVirtualProps(kid)), @@ -738,6 +1439,20 @@ async function runHistory( grouping, ), ) + if (nested.orderBy && row) { + expectOrder( + `${label}: kids of issue ${issue.id}`, + nested, + row.kids, + modelOrderBlocks( + nested, + comments.filter((comment) => comment.k === issue.k), + issue, + grouping, + ), + ) + reach?.reach(`an include query orders its rows`) + } } } } @@ -781,41 +1496,188 @@ async function runHistory( } } -const history = fc - .constantFrom(`none`, `key`, `upper`, `inList`, `date`, `nan`) - .chain((grouping) => - fc.tuple( - shape(false, grouping), - shape(true, grouping), - fc.constant(grouping), - fc.boolean(), - fc.array(step, { maxLength: 6 }), - fc.boolean(), - ), +const groupings = [ + `none`, + `key`, + `upper`, + `inList`, + `date`, + `nan`, + `pair`, + `triple`, +] as const satisfies ReadonlyArray + +const historyFor = ( + grouping: Grouping, + stripped: fc.Arbitrary, + alone: fc.Arbitrary, + shapes: typeof shape = shape, +) => + fc.tuple( + shapes(false, grouping), + shapes(true, grouping), + fc.constant(grouping), + fc.boolean(), + fc.array(step, { maxLength: 6 }), + fc.boolean(), + fc.boolean(), + stripped, + alone, ) +// Half the histories are generated; three in six of those build their IR by +// hand without one kind of operator code. One history in four instead strips +// the grouping compiler, everywhere or from orderBy only, and takes the +// forced shape, for one or several groups chosen in turn. One in four takes +// ordered shapes that compile, at the top level and in the include query, +// and in one of three strips the orderBy aggregates' compiler. About 40 +// histories of each kind per campaign of 160 make each reach state certain +// in practice: a campaign misses the rarest one about once in 1,500 seeds. +const history = fc.oneof( + { + weight: 2, + arbitrary: fc + .constantFrom(...groupings) + .chain((grouping) => + historyFor( + grouping, + fc.oneof( + { weight: 3, arbitrary: fc.constant(`none`) }, + fc.constant(`grouping`), + fc.constant(`runtime`), + fc.constant(`orderBy`), + ), + fc.boolean(), + ), + ), + }, + { + weight: 1, + arbitrary: fc + .constantFrom( + ...([`none`, `key`, `pair`] as const).flatMap((grouping) => + ([`grouping`, `orderBy`] as const).map( + (stripped) => [grouping, stripped] as const, + ), + ), + ) + .chain(([grouping, stripped]) => + historyFor(grouping, fc.constant(stripped), fc.constant(true)), + ), + }, + { + weight: 1, + arbitrary: fc + .constantFrom(...groupings) + .chain((grouping) => + historyFor( + grouping, + fc.oneof( + { weight: 2, arbitrary: fc.constant(`none`) }, + fc.constant(`orderBy`), + ), + fc.constant(false), + orderedShape, + ), + ), + }, +) + +/** The states the operator laws need; each campaign must reach all of them. */ +const reachStates = [ + `bare include: field`, + `bare include: nested`, + `bare include: case`, + `toArray include in a conditional branch`, + `bare group keys without an aggregate`, + `bare group keys beside an aggregate`, + `include() wrap`, + `materialize() wrap`, + `grouping stripped alone: grouped`, + `grouping stripped alone: one group`, + `grouping stripped alone: HAVING aggregate`, + `include runtime stripped`, + `orderBy aggregate stripped beside a select aggregate`, + `an aggregate orders the published rows`, + `a number select field orders the published rows`, + `a string select field orders the published rows`, + `a string field orders A against a lowercase key`, + `two orderBy terms order the rows`, + `an include query orders its rows`, + `two group keys publish several groups`, + `three group keys publish several groups`, +] as const +let reach: + ReturnType> | undefined + describe(`aggregate select shapes`, () => { it(`match the model across generated shapes (fixed campaign)`, async () => { // A repeatable baseline. Seed 2094 is arbitrary. + reach = createOracleReach(`${PROPERTY} (fixed)`, reachStates) await fc.assert( fc.asyncProperty( history, - ([top, nested, grouping, shadow, steps, union]) => - runHistory(top, nested, grouping, shadow, steps, union), + ([ + top, + nested, + grouping, + shadow, + steps, + union, + bareKeys, + stripped, + alone, + ]) => + runHistory( + top, + nested, + grouping, + shadow, + steps, + union, + bareKeys, + stripped, + alone, + ), ), - { numRuns: oracleRuns(120), seed: 2094 }, + { numRuns: oracleRuns(160), seed: 2094 }, ) + reach.assertReached() + reach = undefined }) it(`match the model across generated shapes (random or replayed)`, async () => { + reach = createOracleReach(`${PROPERTY} (random)`, reachStates) await fc.assert( fc.asyncProperty( history, - ([top, nested, grouping, shadow, steps, union]) => - runHistory(top, nested, grouping, shadow, steps, union), + ([ + top, + nested, + grouping, + shadow, + steps, + union, + bareKeys, + stripped, + alone, + ]) => + runHistory( + top, + nested, + grouping, + shadow, + steps, + union, + bareKeys, + stripped, + alone, + ), ), - oraclePropertyOptions(120, PROPERTY), + oraclePropertyOptions(160, PROPERTY), ) + reach.assertReached() + reach = undefined }) // Pinned replay of a review counterexample: a grouped select value that @@ -905,7 +1767,7 @@ describe(`aggregate select shapes`, () => { const query = createLiveQueryCollection((q) => q .from({ c: source }) - .groupBy(({ c }) => key(c)) + .groupBy(groupKeys(({ c }) => key(c))) .select(({ c }) => ({ g: key(c), n: count(c.id) })), ) await query.preload() diff --git a/packages/db/tests/query/aggregate-value-contracts.test-d.ts b/packages/db/tests/query/aggregate-value-contracts.test-d.ts index 88030a3977..687409e8b4 100644 --- a/packages/db/tests/query/aggregate-value-contracts.test-d.ts +++ b/packages/db/tests/query/aggregate-value-contracts.test-d.ts @@ -1,6 +1,6 @@ import { describe, expectTypeOf, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { add, avg, diff --git a/packages/db/tests/query/async-hold-outcomes.test.ts b/packages/db/tests/query/async-hold-outcomes.test.ts index e302691aa2..3590483138 100644 --- a/packages/db/tests/query/async-hold-outcomes.test.ts +++ b/packages/db/tests/query/async-hold-outcomes.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, expect, it } from 'vitest' +import { createLiveQueryCollection, eq } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { BTreeIndex } from '../../src/indexes/btree-index.js' import { createEffect } from '../../src/query/effect.js' -import { createLiveQueryCollection, eq } from '../../src/query/index.js' import { mockSyncCollectionOptions } from '../utils.js' import type { LoadSubsetOptions } from '../../src/types.js' diff --git a/packages/db/tests/query/bucket-facade-adapter.test.ts b/packages/db/tests/query/bucket-facade-adapter.test.ts index 5c1564869e..eda6a2f0f6 100644 --- a/packages/db/tests/query/bucket-facade-adapter.test.ts +++ b/packages/db/tests/query/bucket-facade-adapter.test.ts @@ -1,5 +1,6 @@ import { D2, MultiSet } from '@tanstack/db-ivm' import { describe, expect, it, vi } from 'vitest' +import { include } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection } from '../../src/query/live-query-collection.js' import { eq } from '../../src/query/builder/functions.js' @@ -630,9 +631,11 @@ describe(`BucketFacadeAdapter`, () => { const live = createLiveQueryCollection((q) => q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)), + ), })), ) diff --git a/packages/db/tests/query/builder/functions.test.ts b/packages/db/tests/query/builder/functions.test.ts index e4d284960b..1876596dc5 100644 --- a/packages/db/tests/query/builder/functions.test.ts +++ b/packages/db/tests/query/builder/functions.test.ts @@ -29,9 +29,9 @@ import { or, subtract, sum, - toArray, upper, } from '../../../src/query/builder/functions.js' +import { toArray } from '../../../src/query/builder/include-functions.js' import { compileSingleRowExpression } from '../../../src/query/compiler/evaluators.js' import type { BasicExpression } from '../../../src/query/ir.js' diff --git a/packages/db/tests/query/builder/ref-proxy.test.ts b/packages/db/tests/query/builder/ref-proxy.test.ts index b3e1af381f..abc464524c 100644 --- a/packages/db/tests/query/builder/ref-proxy.test.ts +++ b/packages/db/tests/query/builder/ref-proxy.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { grouping } from '../../../src/query/compiler/grouping.js' import { createRefProxy, createRefProxyWithSelected, @@ -257,7 +258,7 @@ describe(`ref-proxy`, () => { it(`preserves constructed functions and aggregates`, () => { const funcExpr = new Func(`upper`, []) - const aggExpr = new Aggregate(`count`, []) + const aggExpr = new Aggregate(`count`, [], grouping) expect(toExpression(funcExpr)).toBe(funcExpr) expect(toExpression(aggExpr)).toBe(aggExpr) @@ -280,9 +281,9 @@ describe(`ref-proxy`, () => { expect(expression).toBeInstanceOf(Value) expect((expression as Value).value).toBe(literal) } - expect(() => toExpression(new MaterializeWrapper({} as any))).toThrow( - /materialize\(\) cannot be used inside expressions/, - ) + expect(() => + toExpression(new MaterializeWrapper({} as any, {} as any)), + ).toThrow(/materialize\(\) cannot be used inside expressions/) }) }) diff --git a/packages/db/tests/query/case-when.test-d.ts b/packages/db/tests/query/case-when.test-d.ts index 019e20e941..a064e6c4cc 100644 --- a/packages/db/tests/query/case-when.test-d.ts +++ b/packages/db/tests/query/case-when.test-d.ts @@ -1,12 +1,13 @@ import { describe, expectTypeOf, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { caseWhen, createLiveQueryCollection, eq, gt, + include, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions } from '../utils.js' import type { OutputWithVirtual } from '../utils.js' @@ -189,12 +190,14 @@ describe(`caseWhen types`, () => { id: user.id, adultProfile: caseWhen(gt(user.age, 18), { id: user.id, - posts: q - .from({ post: posts }) - .where(({ post }) => eq(post.userId, user.id)) - .select(({ post }) => ({ - title: post.title, - })), + posts: include( + q + .from({ post: posts }) + .where(({ post }) => eq(post.userId, user.id)) + .select(({ post }) => ({ + title: post.title, + })), + ), }), })), ) @@ -319,3 +322,31 @@ describe(`caseWhen types`, () => { >() }) }) + +describe(`caseWhen include branches`, () => { + test(`rejects a bare subquery branch and accepts include()`, () => { + const users = createUsers() + const posts = createPosts() + createLiveQueryCollection((q) => + q.from({ user: users }).select(({ user }) => ({ + posts: caseWhen( + gt(user.age, 18), + // @ts-expect-error A subquery branch needs include(). + q.from({ post: posts }).where(({ post }) => eq(post.userId, user.id)), + ), + })), + ) + createLiveQueryCollection((q) => + q.from({ user: users }).select(({ user }) => ({ + posts: caseWhen( + gt(user.age, 18), + include( + q + .from({ post: posts }) + .where(({ post }) => eq(post.userId, user.id)), + ), + ), + })), + ) + }) +}) diff --git a/packages/db/tests/query/case-when.test.ts b/packages/db/tests/query/case-when.test.ts index a5d8604b55..a8a5511a31 100644 --- a/packages/db/tests/query/case-when.test.ts +++ b/packages/db/tests/query/case-when.test.ts @@ -1,13 +1,14 @@ import { describe, expect, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { caseWhen, count, createLiveQueryCollection, eq, gt, + include, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js' type User = { @@ -657,13 +658,15 @@ describe(`caseWhen`, () => { id: user.id, name: user.name, label: `adult`, - posts: q - .from({ post: posts }) - .where(({ post }) => eq(post.userId, user.id)) - .orderBy(({ post }) => post.id) - .select(({ post }) => ({ - title: post.title, - })), + posts: include( + q + .from({ post: posts }) + .where(({ post }) => eq(post.userId, user.id)) + .orderBy(({ post }) => post.id) + .select(({ post }) => ({ + title: post.title, + })), + ), } return { diff --git a/packages/db/tests/query/clone-query.test.ts b/packages/db/tests/query/clone-query.test.ts index b4caf8a88d..65d5a4d32a 100644 --- a/packages/db/tests/query/clone-query.test.ts +++ b/packages/db/tests/query/clone-query.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { grouping } from '../../src/query/compiler/grouping.js' import { cloneQueryForPlacement } from '../../src/query/builder/clone-query.js' import { Aggregate, @@ -14,7 +15,7 @@ describe(`cloneQueryForPlacement`, () => { it.each([ new PropRef([`u`, `name`]), new Func(`upper`, [new PropRef([`u`, `name`])]), - new Aggregate(`count`, [new PropRef([`u`, `id`])]), + new Aggregate(`count`, [new PropRef([`u`, `id`])], grouping), new ConditionalSelect( [ { diff --git a/packages/db/tests/query/cold-join-reconciliation-oracle.test.ts b/packages/db/tests/query/cold-join-reconciliation-oracle.test.ts index 8a82670b95..ae233249bd 100644 --- a/packages/db/tests/query/cold-join-reconciliation-oracle.test.ts +++ b/packages/db/tests/query/cold-join-reconciliation-oracle.test.ts @@ -1,10 +1,6 @@ import fc from 'fast-check' import { Temporal } from 'temporal-polyfill' import { describe, expect, it } from 'vitest' -import { Func } from '../../src/query/ir.js' -import { JoinConditionMustBeEqualityError } from '../../src/errors.js' -import { createCollection } from '../../src/collection/index.js' -import { BTreeIndex } from '../../src/indexes/btree-index.js' import { Query, and, @@ -13,6 +9,10 @@ import { gt, or, } from '../../src/query/index.js' +import { Func } from '../../src/query/ir.js' +import { JoinConditionMustBeEqualityError } from '../../src/errors.js' +import { createCollection } from '../../src/collection/index.js' +import { BTreeIndex } from '../../src/indexes/btree-index.js' import { oraclePropertyOptions, oracleRuns, diff --git a/packages/db/tests/query/collection-ref-compatibility.test.ts b/packages/db/tests/query/collection-ref-compatibility.test.ts index 772ebc3af0..53bc20f72c 100644 --- a/packages/db/tests/query/collection-ref-compatibility.test.ts +++ b/packages/db/tests/query/collection-ref-compatibility.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { includesRuntime } from '../../src/query/live/includes-runtime.js' import { IR, Query, @@ -119,6 +120,7 @@ describe(`CollectionRef public shape`, () => { new IR.PropRef([`root`, `id`]), new IR.PropRef([`child`, `id`]), `children`, + includesRuntime, ), }, }, diff --git a/packages/db/tests/query/compiler/group-by-pipeline.test.ts b/packages/db/tests/query/compiler/group-by-pipeline.test.ts index ee4794774e..03abcd6fc8 100644 --- a/packages/db/tests/query/compiler/group-by-pipeline.test.ts +++ b/packages/db/tests/query/compiler/group-by-pipeline.test.ts @@ -1,5 +1,6 @@ import { D2, MultiSet, output } from '@tanstack/db-ivm' import { describe, expect, test } from 'vitest' +import { grouping } from '../../../src/query/compiler/grouping.js' import { NonAggregateExpressionNotInGroupByError } from '../../../src/errors.js' import { coalesce } from '../../../src/query/builder/functions.js' import { processGroupBy } from '../../../src/query/compiler/group-by.js' @@ -133,7 +134,11 @@ describe(`group-by production pipeline`, () => { const graph = new D2() const input = graph.newInput() const groupRef = new PropRef([`row`, `group`]) - const total = new Aggregate(`sum`, [new PropRef([`row`, `amount`])]) + const total = new Aggregate( + `sum`, + [new PropRef([`row`, `amount`])], + grouping, + ) // Exercise generated-field collision avoidance as well as wrapped refs. const totalAlias = `__tanstack_group_synced` const select: Select | undefined = diff --git a/packages/db/tests/query/compiler/group-by.test.ts b/packages/db/tests/query/compiler/group-by.test.ts index 0ec27fd54f..0fd9521814 100644 --- a/packages/db/tests/query/compiler/group-by.test.ts +++ b/packages/db/tests/query/compiler/group-by.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { grouping } from '../../../src/query/compiler/grouping.js' import { Aggregate, Func, PropRef, Value } from '../../../src/query/ir.js' // Import the validation function that we want to test directly @@ -73,8 +74,16 @@ describe(`group-by compiler`, () => { const groupByClause = [new PropRef([`users`, `department`])] const selectClause = { department: new PropRef([`users`, `department`]), - count: new Aggregate(`count`, [new PropRef([`users`, `id`])]), - avg_salary: new Aggregate(`avg`, [new PropRef([`users`, `salary`])]), + count: new Aggregate( + `count`, + [new PropRef([`users`, `id`])], + grouping, + ), + avg_salary: new Aggregate( + `avg`, + [new PropRef([`users`, `salary`])], + grouping, + ), } // Should not throw diff --git a/packages/db/tests/query/compiler/select.test.ts b/packages/db/tests/query/compiler/select.test.ts index c5459944ae..38c4c5b67e 100644 --- a/packages/db/tests/query/compiler/select.test.ts +++ b/packages/db/tests/query/compiler/select.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { grouping } from '../../../src/query/compiler/grouping.js' import { compileExpression } from '../../../src/query/compiler/evaluators.js' import { Aggregate, Func, PropRef, Value } from '../../../src/query/ir.js' @@ -33,7 +34,11 @@ describe(`select compiler`, () => { }) it(`throws error for aggregate expressions`, () => { - const arg = new Aggregate(`count`, [new PropRef([`users`, `id`])]) + const arg = new Aggregate( + `count`, + [new PropRef([`users`, `id`])], + grouping, + ) const namespacedRow = { users: { id: 1 } } expect(() => { @@ -161,11 +166,11 @@ describe(`select compiler`, () => { // Test the helper function that can be imported and tested directly it(`rejects aggregate IR at the single-row compiler boundary`, () => { const aggregateExpressions = [ - new Aggregate(`count`, [new PropRef([`users`, `id`])]), - new Aggregate(`sum`, [new PropRef([`orders`, `amount`])]), - new Aggregate(`avg`, [new PropRef([`products`, `price`])]), - new Aggregate(`min`, [new PropRef([`dates`, `created`])]), - new Aggregate(`max`, [new PropRef([`dates`, `updated`])]), + new Aggregate(`count`, [new PropRef([`users`, `id`])], grouping), + new Aggregate(`sum`, [new PropRef([`orders`, `amount`])], grouping), + new Aggregate(`avg`, [new PropRef([`products`, `price`])], grouping), + new Aggregate(`min`, [new PropRef([`dates`, `created`])], grouping), + new Aggregate(`max`, [new PropRef([`dates`, `updated`])], grouping), ] const namespacedRow = { diff --git a/packages/db/tests/query/functional-variants.test-d.ts b/packages/db/tests/query/functional-variants.test-d.ts index a388579f48..b26a78dc96 100644 --- a/packages/db/tests/query/functional-variants.test-d.ts +++ b/packages/db/tests/query/functional-variants.test-d.ts @@ -6,6 +6,7 @@ import { createLiveQueryCollection, eq, gt, + include, materialize, toArray, } from '../../src/query/index.js' @@ -171,7 +172,7 @@ describe(`Functional Variants Types`, () => { createLiveQueryCollection((q) => q.from({ user: usersCollection }).fn.select((row) => ({ id: row.user.id, - departments: q.from({ department: departmentsCollection }), + departments: include(q.from({ department: departmentsCollection })), })), ) diff --git a/packages/db/tests/query/functional-variants.test.ts b/packages/db/tests/query/functional-variants.test.ts index f803e1f04e..8833be095e 100644 --- a/packages/db/tests/query/functional-variants.test.ts +++ b/packages/db/tests/query/functional-variants.test.ts @@ -5,6 +5,7 @@ import { createLiveQueryCollection, eq, gt, + include, materialize, toArray, } from '../../src/query/index.js' @@ -304,6 +305,22 @@ describe(`Functional Variants Query`, () => { }), ).toThrow(`fn.select() cannot return a child query builder`) + expect(() => + createLiveQueryCollection({ + startSync: true, + query: (q) => + q.from({ user: usersCollection }).fn.select( + (row) => + ({ + id: row.user.id, + departments: include( + q.from({ department: departmentsCollection }), + ), + }) as any, + ), + }), + ).toThrow(`fn.select() cannot return include()`) + const departments = Symbol(`departments`) expect(() => createLiveQueryCollection({ diff --git a/packages/db/tests/query/group-by-work.test.ts b/packages/db/tests/query/group-by-work.test.ts index dbdc0a6d87..bb5fcc1f10 100644 --- a/packages/db/tests/query/group-by-work.test.ts +++ b/packages/db/tests/query/group-by-work.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it, vi } from 'vitest' +import { + createLiveQueryCollection, + eq, + include, +} from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' -import { createLiveQueryCollection, eq } from '../../src/query/index.js' import { count, max, sum } from '../../src/query/builder/functions.js' import { mockSyncCollectionOptions } from '../utils.js' @@ -194,10 +198,12 @@ function createCommentCount(size: number) { q.from({ issue: issues }).select(({ issue }) => ({ id: issue.id, title: issue.title, - comments: q - .from({ comment: comments }) - .where(({ comment }) => eq(comment.issueId, issue.id)) - .select(({ comment }) => ({ n: count(comment.id) })), + comments: include( + q + .from({ comment: comments }) + .where(({ comment }) => eq(comment.issueId, issue.id)) + .select(({ comment }) => ({ n: count(comment.id) })), + ), })), ) const write = ( diff --git a/packages/db/tests/query/group-by.test.ts b/packages/db/tests/query/group-by.test.ts index ebb82bc23c..0021d933a0 100644 --- a/packages/db/tests/query/group-by.test.ts +++ b/packages/db/tests/query/group-by.test.ts @@ -8,8 +8,15 @@ */ import { beforeEach, describe, expect, test } from 'vitest' import { Temporal } from 'temporal-polyfill' -import { createLiveQueryCollection, toArray } from '../../src/query/index.js' -import { NonAggregateExpressionNotInGroupByError } from '../../src/errors.js' +import { + createLiveQueryCollection, + groupKeys, + toArray, +} from '../../src/query/index.js' +import { + GroupingOperatorRequiredError, + NonAggregateExpressionNotInGroupByError, +} from '../../src/errors.js' import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js' import { @@ -2717,7 +2724,7 @@ describe(`group value among equal instances`, () => { const grouped = createLiveQueryCollection((q) => q .from({ row: source }) - .groupBy(({ row }) => row.v) + .groupBy(groupKeys(({ row }) => row.v)) .select(({ row }) => ({ v: row.v })), ) const write = (type: `insert` | `delete`, id: number) => { @@ -2893,3 +2900,62 @@ describe(`non-aggregate fields of an aggregate without groupBy`, () => { ).toThrow(NonAggregateExpressionNotInGroupByError) }) }) + +describe(`grouping without an aggregate`, () => { + type Row = { id: number; v: string } + const rows = () => + createCollection( + mockSyncCollectionOptions({ + id: `grouping-operator`, + getKey: (row) => row.id, + initialData: [ + { id: 1, v: `a` }, + { id: 2, v: `a` }, + { id: 3, v: `b` }, + ], + }), + ) + + test(`needs groupKeys and names it`, () => { + const source = rows() + expect(() => + createLiveQueryCollection({ + startSync: true, + query: (q) => + q + .from({ row: source }) + .groupBy(({ row }) => row.v) + .select(({ row }) => ({ v: row.v })), + }), + ).toThrow(GroupingOperatorRequiredError) + }) + + test(`groups by keys with groupKeys, and an aggregate needs nothing extra`, async () => { + const source = rows() + const keysOnly = createLiveQueryCollection({ + startSync: true, + query: (q) => + q + .from({ row: source }) + .groupBy(groupKeys(({ row }) => row.v)) + .select(({ row }) => ({ v: row.v })), + }) + const counted = createLiveQueryCollection({ + startSync: true, + query: (q) => + q + .from({ row: source }) + .groupBy(({ row }) => row.v) + .select(({ row }) => ({ v: row.v, n: count(row.id) })), + }) + await Promise.all([keysOnly.preload(), counted.preload()]) + expect(keysOnly.toArray.map(stripVirtualProps)).toEqual([ + { v: `a` }, + { v: `b` }, + ]) + expect(counted.toArray.map(stripVirtualProps)).toEqual([ + { v: `a`, n: 2 }, + { v: `b`, n: 1 }, + ]) + }) +}) diff --git a/packages/db/tests/query/hand-built-ir-operators.test.ts b/packages/db/tests/query/hand-built-ir-operators.test.ts new file mode 100644 index 0000000000..cd944a6863 --- /dev/null +++ b/packages/db/tests/query/hand-built-ir-operators.test.ts @@ -0,0 +1,157 @@ +import { describe, expect, it } from 'vitest' +import { + Query, + count, + createCollection, + groupingCompiler, + include, + includesRuntime, +} from '../../src/index.js' +import { mockSyncCollectionOptions } from '../utils.js' +import { + GroupingOperatorRequiredError, + IncludeOperatorRequiredError, +} from '../../src/errors.js' +import { BaseQueryBuilder } from '../../src/query/builder/index.js' +import { getQueryIR } from '../../src/query/builder/query-ir.js' +import { IncludesSubquery } from '../../src/query/ir.js' +import { + createLiveQueryCollection, + eq, + toArray, +} from '../../src/query/index.js' + +// Code that builds query IR by hand passes operator code to `IR.Aggregate` +// and `IR.IncludesSubquery`. The public entry exports that code, and it must +// be the same value the operator functions carry, so hand-built and +// builder-built IR compile the same way. +describe(`operator code for hand-built IR`, () => { + it(`exports the grouping compiler that aggregates carry`, () => { + expect(groupingCompiler).toBe(count(1).grouping) + }) + + it(`exports the include runtime that include() carries`, () => { + const issues = createCollection( + mockSyncCollectionOptions<{ id: number }>({ + id: `hand-built-ir-issues`, + getKey: (issue) => issue.id, + initialData: [], + }), + ) + expect(includesRuntime).toBe( + include(new Query().from({ i: issues })).runtime, + ) + }) + + // Paths the aggregate-select oracle's grammar does not generate: a caller + // that still passes the old IncludesSubquery arguments, and an aggregate in + // orderBy. A query without the operator code must fail when it compiles, + // with the coded error for the missing operator, not later with a + // TypeError or a wrong result. + const rows = () => + createCollection( + mockSyncCollectionOptions<{ id: number; k: string }>({ + id: `hand-built-ir-${Math.random().toString(36).slice(2)}`, + getKey: (row) => row.id, + initialData: [{ id: 1, k: `a` }], + }), + ) + + // A copy of an aggregate without its grouping compiler. + const strip = (aggregate: T): T => + Object.assign(Object.create(Object.getPrototypeOf(aggregate)), aggregate, { + grouping: undefined, + }) + + it(`rejects an include whose runtime slot holds the old parentFilters argument`, () => { + const parents = rows() + const children = rows() + expect(() => + createLiveQueryCollection((q) => { + const ir = getQueryIR( + q.from({ p: parents }).select(({ p }) => ({ + id: p.id, + kids: toArray( + q.from({ c: children }).where(({ c }) => eq(c.k, p.k)), + ), + })), + ) + const kids = ir.select!.kids as IncludesSubquery + // The argument order before this change: parentFilters fifth. + const shifted = new (IncludesSubquery as any)( + kids.query, + kids.correlationField, + kids.childCorrelationField, + `kids`, + kids.parentFilters ?? [], + ) + return new BaseQueryBuilder({ + ...ir, + select: { ...ir.select, kids: shifted }, + }) as any + }), + ).toThrow(IncludeOperatorRequiredError) + }) + + it(`rejects an orderBy aggregate without the grouping compiler`, () => { + const source = rows() + expect(() => + createLiveQueryCollection((q) => { + const ir = getQueryIR( + // No groupBy and no select aggregate: only orderBy reads one. + q + .from({ c: source }) + .select(({ c }) => ({ k: c.k })) + .orderBy(({ c }) => count(c.id)), + ) + return new BaseQueryBuilder({ + ...ir, + orderBy: ir.orderBy!.map((clause) => ({ + ...clause, + expression: strip(clause.expression), + })), + }) as any + }), + ).toThrow(GroupingOperatorRequiredError) + }) + + // The query compiles with the grouping compiler any of its aggregates + // brings, so an orderBy aggregate without one orders as the same aggregate + // built by `count()` does. + it(`orders by an aggregate without the grouping compiler when the select brings one`, async () => { + const source = createCollection( + mockSyncCollectionOptions<{ id: number; k: string }>({ + id: `hand-built-ir-${Math.random().toString(36).slice(2)}`, + getKey: (row) => row.id, + initialData: [ + { id: 1, k: `a` }, + { id: 2, k: `a` }, + { id: 3, k: `b` }, + ], + }), + ) + const query = createLiveQueryCollection((q) => { + // Bare group keys: only the select aggregate brings the compiler. + const ir = getQueryIR( + q + .from({ c: source }) + .groupBy(({ c }) => c.k) + .select(({ c }) => ({ k: c.k, n: count(c.id) })) + .orderBy(({ c }) => count(c.id), `asc`), + ) + return new BaseQueryBuilder({ + ...ir, + orderBy: ir.orderBy!.map((clause) => ({ + ...clause, + expression: strip(clause.expression), + })), + }) as any + }) + await query.preload() + expect(query.toArray.map((row: any) => [row.k, row.n])).toEqual([ + [`b`, 1], + [`a`, 2], + ]) + await query.cleanup() + }) +}) diff --git a/packages/db/tests/query/identity-output-shape-oracle.test.ts b/packages/db/tests/query/identity-output-shape-oracle.test.ts index 0158b72e5e..e332be63bc 100644 --- a/packages/db/tests/query/identity-output-shape-oracle.test.ts +++ b/packages/db/tests/query/identity-output-shape-oracle.test.ts @@ -3,18 +3,18 @@ import { D2, MultiSet, output, serializeValue } from '@tanstack/db-ivm' import { Temporal } from 'temporal-polyfill' import fc from 'fast-check' import { describe, expect, it } from 'vitest' -import { getQueryIR } from '../../src/query/builder/query-ir.js' -import { createCollection } from '../../src/collection/index.js' -import { count as countRows } from '../../src/query/builder/functions.js' -import { compileExpression } from '../../src/query/compiler/evaluators.js' -import { compileQuery } from '../../src/query/compiler/index.js' -import { getEqualityValueIdentity } from '../../src/query/equality-value-identity.js' import { Query, and, createLiveQueryCollection, eq, } from '../../src/query/index.js' +import { getQueryIR } from '../../src/query/builder/query-ir.js' +import { createCollection } from '../../src/collection/index.js' +import { count as countRows } from '../../src/query/builder/functions.js' +import { compileExpression } from '../../src/query/compiler/evaluators.js' +import { compileQuery } from '../../src/query/compiler/index.js' +import { getEqualityValueIdentity } from '../../src/query/equality-value-identity.js' import { queriesMatchForCaching } from '../../src/query/compiler/query-equivalence.js' import { getLoadSubsetDemandKey, diff --git a/packages/db/tests/query/includes-collection-oracle.property.test.ts b/packages/db/tests/query/includes-collection-oracle.property.test.ts index b162ba7e5e..a0719cca1d 100644 --- a/packages/db/tests/query/includes-collection-oracle.property.test.ts +++ b/packages/db/tests/query/includes-collection-oracle.property.test.ts @@ -1,7 +1,5 @@ import { fc, test as fcTest } from '@fast-check/vitest' import { describe, expect } from 'vitest' -import { createCollection } from '../../src/collection/index.js' -import { BTreeIndex } from '../../src/indexes/btree-index.js' import { add, caseWhen, @@ -10,12 +8,15 @@ import { createLiveQueryCollection, eq, gt, + include, lt, materialize, multiply, sum, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { BTreeIndex } from '../../src/indexes/btree-index.js' import { runTrace } from '../trace-runner.js' import { withHistoryCleanup } from '../optimistic-history-oracle.js' import { @@ -219,7 +220,7 @@ function createCollectionQuery( return { id: parent.id, group: parent.group, - children: childRows, + children: include(childRows), arrayChildren: toArray( reuseChildRelation ? childRows : createChildRows(), ), @@ -1240,10 +1241,12 @@ describe(`Collection-valued includes oracle`, () => { .select(({ parent }) => ({ id: parent.id, value: parent.value, - children: q - .from({ child: nodes.collection }) - .where(({ child }) => eq(child.kind, `child`)) - .where(({ child }) => eq(child.group, parent.group)), + children: include( + q + .from({ child: nodes.collection }) + .where(({ child }) => eq(child.kind, `child`)) + .where(({ child }) => eq(child.group, parent.group)), + ), })), ) @@ -1332,10 +1335,12 @@ describe(`Collection-valued includes oracle`, () => { .select(({ parent }) => ({ id: parent.id, value: parent.value, - children: q - .from({ child: nodes.collection }) - .where(({ child }) => eq(child.kind, `child`)) - .where(({ child }) => eq(child.group, parent.group)), + children: include( + q + .from({ child: nodes.collection }) + .where(({ child }) => eq(child.kind, `child`)) + .where(({ child }) => eq(child.group, parent.group)), + ), })), ) @@ -1386,9 +1391,11 @@ describe(`Collection-valued includes oracle`, () => { const live = createLiveQueryCollection((q) => q.from({ parent: parents.collection }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children.collection }) - .where(({ child }) => eq(child.parentGroup, parent.group)), + children: include( + q + .from({ child: children.collection }) + .where(({ child }) => eq(child.parentGroup, parent.group)), + ), })), ) @@ -1534,11 +1541,13 @@ describe(`Collection-valued includes oracle`, () => { .select(({ message }) => ({ kind: `message` as const, id: message.id, - children: q - .from({ messageChild: children.collection }) - .where(({ messageChild }) => - eq(messageChild.parentGroup, message.group), - ), + children: include( + q + .from({ messageChild: children.collection }) + .where(({ messageChild }) => + eq(messageChild.parentGroup, message.group), + ), + ), })) const toolRows = q .from({ tool: tools.collection }) @@ -1789,10 +1798,12 @@ describe(`Collection-valued includes oracle`, () => { .select(({ parent }) => ({ id: parent.id, value: parent.value, - children: q - .from({ child: nodes.collection }) - .where(({ child }) => eq(child.kind, `child`)) - .where(({ child }) => eq(child.group, parent.group)), + children: include( + q + .from({ child: nodes.collection }) + .where(({ child }) => eq(child.kind, `child`)) + .where(({ child }) => eq(child.group, parent.group)), + ), })), ) @@ -1866,10 +1877,12 @@ describe(`Collection-valued includes oracle`, () => { .select(({ parent }) => ({ id: parent.id, value: parent.value, - children: q - .from({ child: nodes.collection }) - .where(({ child }) => eq(child.kind, `child`)) - .where(({ child }) => eq(child.group, parent.group)), + children: include( + q + .from({ child: nodes.collection }) + .where(({ child }) => eq(child.kind, `child`)) + .where(({ child }) => eq(child.group, parent.group)), + ), })), ) @@ -2035,7 +2048,7 @@ describe(`Collection-valued includes oracle`, () => { return { id: parent.id, - facade: childRows(), + facade: include(childRows()), array: toArray(childRows()), materialized: materialize(childRows()), first: materialize(childRows().findOne()), @@ -2138,7 +2151,7 @@ describe(`Collection-valued includes oracle`, () => { return { id: parent.id, - facade: childRows(), + facade: include(childRows()), array: toArray(childRows()), materialized: materialize(childRows()), first: materialize(childRows().findOne()), @@ -2319,11 +2332,13 @@ describe(`Collection-valued includes oracle`, () => { const live = createLiveQueryCollection((q) => q.from({ parent: parents.collection }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: orderedChildren }) - .where(({ child }) => eq(child.parentGroup, parent.group)) - .orderBy(({ child }) => child.rank) - .select(({ child }) => ({ id: child.id, value: child.value })), + children: include( + q + .from({ child: orderedChildren }) + .where(({ child }) => eq(child.parentGroup, parent.group)) + .orderBy(({ child }) => child.rank) + .select(({ child }) => ({ id: child.id, value: child.value })), + ), })), ) const callbacks: Array<{ @@ -2511,12 +2526,14 @@ describe(`Collection-valued includes oracle`, () => { const live = createLiveQueryCollection((q) => q.from({ parent: parents.collection }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.parentGroup, parent.group)) - .orderBy(({ child }) => child.rank) - .limit(2) - .select(({ child }) => ({ id: child.id, value: child.value })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.parentGroup, parent.group)) + .orderBy(({ child }) => child.rank) + .limit(2) + .select(({ child }) => ({ id: child.id, value: child.value })), + ), })), ) const callbacks: Array<{ @@ -2676,7 +2693,7 @@ describe(`Collection-valued includes oracle`, () => { eq(parent.active, true), { kind: `active` as const, - facade: childRows(), + facade: include(childRows()), array: toArray(childRows()), materialized: materialize(childRows()), }, @@ -2782,7 +2799,7 @@ describe(`Collection-valued includes oracle`, () => { return { id: parent.id, - facade: summaries(), + facade: include(summaries()), array: toArray(summaries()), materialized: materialize(summaries()), implicit: materialize(total()), @@ -2888,7 +2905,7 @@ describe(`Collection-valued includes oracle`, () => { return { id: parent.id, - facade: summaries(), + facade: include(summaries()), array: toArray(summaries()), materialized: materialize(summaries()), } @@ -2961,7 +2978,7 @@ describe(`Collection-valued includes oracle`, () => { return { id: parent.id, - facade: childRows(), + facade: include(childRows()), array: toArray(childRows()), materialized: materialize(childRows()), } @@ -3028,7 +3045,7 @@ describe(`Collection-valued includes oracle`, () => { return { id: parent.id, - facade: childRows(), + facade: include(childRows()), array: toArray(childRows()), materialized: materialize(childRows()), } @@ -3120,7 +3137,7 @@ describe(`Collection-valued includes oracle`, () => { return { id: parent.id, - facade: childRows(), + facade: include(childRows()), array: toArray(childRows()), materialized: materialize(childRows()), } diff --git a/packages/db/tests/query/includes-context-transport-oracle.test.ts b/packages/db/tests/query/includes-context-transport-oracle.test.ts index 4768204add..29b8578283 100644 --- a/packages/db/tests/query/includes-context-transport-oracle.test.ts +++ b/packages/db/tests/query/includes-context-transport-oracle.test.ts @@ -7,7 +7,9 @@ import { count, createLiveQueryCollection, eq, + groupKeys, gt, + include, lt, lte, materialize, @@ -558,7 +560,7 @@ function includeInEveryForm( query: QueryBuilder, ) { return { - collection: query, + collection: include(query), array: toArray(query), materialized: materialize(query), } @@ -918,7 +920,7 @@ async function runAggregationCell({ const rows = grouping === `explicit` ? correlated - .groupBy(({ child }) => child.parentGroup) + .groupBy(groupKeys(({ child }) => child.parentGroup)) .select(({ child }) => ({ score: placement === `inside-aggregate` @@ -1047,10 +1049,12 @@ async function runRecursiveSourceCell({ })) case `aggregate`: return correlated - .groupBy(({ candidate }) => [ - candidate.id, - candidate.parentGroup, - ]) + .groupBy( + groupKeys(({ candidate }) => [ + candidate.id, + candidate.parentGroup, + ]), + ) .select(({ candidate }) => ({ id: candidate.id, parentGroup: candidate.parentGroup, @@ -1058,10 +1062,12 @@ async function runRecursiveSourceCell({ })) case `having`: return correlated - .groupBy(({ candidate }) => [ - candidate.id, - candidate.parentGroup, - ]) + .groupBy( + groupKeys(({ candidate }) => [ + candidate.id, + candidate.parentGroup, + ]), + ) .having(({ candidate }) => gt(count(candidate.id), parent.parameter), ) @@ -1113,10 +1119,12 @@ async function runRecursiveSourceCell({ })) case `aggregate`: return correlated - .groupBy(({ leftCandidate }) => [ - leftCandidate.id, - leftCandidate.parentGroup, - ]) + .groupBy( + groupKeys(({ leftCandidate }) => [ + leftCandidate.id, + leftCandidate.parentGroup, + ]), + ) .select(({ leftCandidate }) => ({ id: leftCandidate.id, parentGroup: leftCandidate.parentGroup, @@ -1124,10 +1132,12 @@ async function runRecursiveSourceCell({ })) case `having`: return correlated - .groupBy(({ leftCandidate }) => [ - leftCandidate.id, - leftCandidate.parentGroup, - ]) + .groupBy( + groupKeys(({ leftCandidate }) => [ + leftCandidate.id, + leftCandidate.parentGroup, + ]), + ) .having(({ leftCandidate }) => gt(count(leftCandidate.id), parent.parameter), ) @@ -1176,10 +1186,12 @@ async function runRecursiveSourceCell({ })) case `aggregate`: return correlated - .groupBy(({ rightCandidate }) => [ - rightCandidate.id, - rightCandidate.parentGroup, - ]) + .groupBy( + groupKeys(({ rightCandidate }) => [ + rightCandidate.id, + rightCandidate.parentGroup, + ]), + ) .select(({ rightCandidate }) => ({ id: rightCandidate.id, parentGroup: rightCandidate.parentGroup, @@ -1187,10 +1199,12 @@ async function runRecursiveSourceCell({ })) case `having`: return correlated - .groupBy(({ rightCandidate }) => [ - rightCandidate.id, - rightCandidate.parentGroup, - ]) + .groupBy( + groupKeys(({ rightCandidate }) => [ + rightCandidate.id, + rightCandidate.parentGroup, + ]), + ) .having(({ rightCandidate }) => gt(count(rightCandidate.id), parent.parameter), ) @@ -1934,7 +1948,9 @@ async function runNamespaceCollisionCell({ case `group`: return includeInEveryForm( correlated - .groupBy(({ child }) => [child.id, child.label]) + .groupBy( + groupKeys(({ child }) => [child.id, child.label]), + ) .select(({ child }) => ({ id: child.id, value: child.label, @@ -1996,7 +2012,9 @@ async function runNamespaceCollisionCell({ case `group`: return includeInEveryForm( correlated - .groupBy(({ child }) => [child.id, child.label]) + .groupBy( + groupKeys(({ child }) => [child.id, child.label]), + ) .select(({ child }) => ({ id: child.id, [name]: child.label, @@ -2192,7 +2210,7 @@ async function runPublicSurfaceCell({ if (shape === `functional-having-input`) { const rows = correlated - .groupBy(({ child }) => child.parentGroup) + .groupBy(groupKeys(({ child }) => child.parentGroup)) .select(({ child }) => ({ parentGroup: child.parentGroup, total: count(child.id), diff --git a/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts b/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts index 46523e423b..4f536147c7 100644 --- a/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts +++ b/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts @@ -1,9 +1,6 @@ import { fc, test as fcTest } from '@fast-check/vitest' import { describe, expect, test } from 'vitest' import { Temporal } from 'temporal-polyfill' -import { Func, PropRef, Value } from '../../src/query/ir.js' -import { createCollection } from '../../src/collection/index.js' -import { createFilterFunctionFromExpression } from '../../src/collection/change-events.js' import { and, count, @@ -15,6 +12,9 @@ import { queryOnce, toArray, } from '../../src/query/index.js' +import { Func, PropRef, Value } from '../../src/query/ir.js' +import { createCollection } from '../../src/collection/index.js' +import { createFilterFunctionFromExpression } from '../../src/collection/change-events.js' import { oraclePropertyOptions, oracleRuns, diff --git a/packages/db/tests/query/includes-functional-input-boundary.test.ts b/packages/db/tests/query/includes-functional-input-boundary.test.ts index 0e47a21a86..222ed5d824 100644 --- a/packages/db/tests/query/includes-functional-input-boundary.test.ts +++ b/packages/db/tests/query/includes-functional-input-boundary.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest' import { createLiveQueryCollection, eq, + include, materialize, toArray, } from '../../src/query/index.js' @@ -180,9 +181,11 @@ describe(`functional include input boundary`, () => { .from({ parent: parents.collection }) .select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children.collection }) - .where(({ child }) => eq(child.parentId, parent.id)), + children: include( + q + .from({ child: children.collection }) + .where(({ child }) => eq(child.parentId, parent.id)), + ), })), }) .fn.select(({ row }) => { @@ -287,9 +290,11 @@ describe(`functional include input boundary`, () => { .from({ row: q.from({ parent: parents.collection }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children.collection }) - .where(({ child }) => eq(child.parentId, parent.id)), + children: include( + q + .from({ child: children.collection }) + .where(({ child }) => eq(child.parentId, parent.id)), + ), })), }) .select(({ row }) => ({ id: row.id, children: row.children })), @@ -323,9 +328,11 @@ describe(`functional include input boundary`, () => { return q.from({ row: projected }).select(({ row }) => ({ id: row.id, label: row.label, - children: q - .from({ child: children.collection }) - .where(({ child }) => eq(child.parentId, row.id)), + children: include( + q + .from({ child: children.collection }) + .where(({ child }) => eq(child.parentId, row.id)), + ), })) }) let publications = 0 @@ -377,7 +384,9 @@ describe(`functional include input boundary`, () => { .where(({ leaf }) => eq(leaf.childId, child.id)) return { id: child.id, - leaves: inline ? toArray(leafQuery) : leafQuery, + leaves: inline + ? toArray(leafQuery) + : include(leafQuery), } }), ), diff --git a/packages/db/tests/query/includes-functional-projection-oracle.test.ts b/packages/db/tests/query/includes-functional-projection-oracle.test.ts index 1ecabac7dd..fb3d280055 100644 --- a/packages/db/tests/query/includes-functional-projection-oracle.test.ts +++ b/packages/db/tests/query/includes-functional-projection-oracle.test.ts @@ -1,14 +1,15 @@ import { describe, expect, it, vi } from 'vitest' -import { createCollection } from '../../src/collection/index.js' -import { createDeferred } from '../../src/deferred.js' -import { BasicIndex } from '../../src/indexes/basic-index.js' -import { BucketFacadeAdapter } from '../../src/query/live/bucket-facade-adapter.js' import { createLiveQueryCollection, eq, + include, materialize, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { createDeferred } from '../../src/deferred.js' +import { BasicIndex } from '../../src/indexes/basic-index.js' +import { BucketFacadeAdapter } from '../../src/query/live/bucket-facade-adapter.js' import { flushPromises } from '../utils.js' import { createControlledCollection } from './includes-oracle-helpers.js' @@ -262,9 +263,11 @@ describe(`functional projection output compatibility`, () => { .from({ parent: parents.collection }) .select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)), + ), })), }) return projection === `expression` @@ -449,9 +452,11 @@ describe(`functional projection output compatibility`, () => { return q.from({ row: projected }).select(({ row }) => ({ id: row.id, groupId: row.groupId, - children: q - .from({ child: children.collection }) - .where(({ child }) => eq(child.groupId, row.groupId)), + children: include( + q + .from({ child: children.collection }) + .where(({ child }) => eq(child.groupId, row.groupId)), + ), })) }) if (subscribeAt === `publication`) { @@ -592,9 +597,11 @@ describe(`functional projection output compatibility`, () => { return { id: parent.id, groupId: parent.groupId, - children: ordered - ? childQuery.orderBy(({ child }) => child.id, `desc`) - : childQuery, + children: include( + ordered + ? childQuery.orderBy(({ child }) => child.id, `desc`) + : childQuery, + ), } }), }) @@ -762,9 +769,11 @@ describe(`functional projection output compatibility`, () => { row: q.from({ parent: parents.collection }).select(({ parent }) => ({ id: parent.id, label: parent.label, - children: q - .from({ child: childSource.collection }) - .where(({ child }) => eq(child.groupId, parent.groupId)), + children: include( + q + .from({ child: childSource.collection }) + .where(({ child }) => eq(child.groupId, parent.groupId)), + ), })), }) return source.select(({ row }) => ({ @@ -861,9 +870,11 @@ describe(`functional projection output compatibility`, () => { }) return q.from({ row: projected }).select(({ row }) => ({ id: row.id, - children: q - .from({ child: children.collection }) - .where(({ child }) => eq(child.groupId, row.groupId)), + children: include( + q + .from({ child: children.collection }) + .where(({ child }) => eq(child.groupId, row.groupId)), + ), })) }) let primaryFailure: unknown = noPrimaryFailure @@ -921,9 +932,11 @@ describe(`functional projection output compatibility`, () => { const source = createLiveQueryCollection((q) => q.from({ parent: parents.collection }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children.collection }) - .where(({ child }) => eq(child.parentId, parent.id)), + children: include( + q + .from({ child: children.collection }) + .where(({ child }) => eq(child.parentId, parent.id)), + ), })), ) const projected = createLiveQueryCollection((q) => @@ -1016,7 +1029,7 @@ describe(`functional projection output compatibility`, () => { base: parent.base, children: form === `collection` - ? childRows + ? include(childRows) : form === `array` ? toArray(childRows) : materialize(childRows), @@ -1176,7 +1189,7 @@ describe(`functional projection output compatibility`, () => { ? { children: form === `collection` - ? childRows + ? include(childRows) : form === `array` ? toArray(childRows) : materialize(childRows), @@ -1305,7 +1318,7 @@ describe(`functional projection output compatibility`, () => { id: parent.id, children: form === `collection` - ? primary + ? include(primary) : form === `array` ? toArray(primary) : materialize(primary), @@ -1313,7 +1326,7 @@ describe(`functional projection output compatibility`, () => { ? { sibling: form === `collection` - ? sibling + ? include(sibling) : form === `array` ? toArray(sibling) : materialize(sibling), @@ -1632,7 +1645,7 @@ describe(`functional include projection boundary grammar`, () => { total: 0, children: form === `collection` - ? childRows + ? include(childRows) : form === `array` ? toArray(childRows) : materialize(childRows), diff --git a/packages/db/tests/query/includes-lazy-loading.test.ts b/packages/db/tests/query/includes-lazy-loading.test.ts index f2ed8f5bc3..00371d4cb1 100644 --- a/packages/db/tests/query/includes-lazy-loading.test.ts +++ b/packages/db/tests/query/includes-lazy-loading.test.ts @@ -4,6 +4,7 @@ import { createLiveQueryCollection, eq, gte, + include, toArray, } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' @@ -482,13 +483,15 @@ describe(`includes lazy loading`, () => { q.from({ r: roots }).select(({ r }) => ({ id: r.id, name: r.name, - children: q - .from({ item: items }) - .where(({ item }) => eq(item.rootId, r.id)) - .select(({ item }) => ({ - id: item.id, - title: item.title, - })), + children: include( + q + .from({ item: items }) + .where(({ item }) => eq(item.rootId, r.id)) + .select(({ item }) => ({ + id: item.id, + title: item.title, + })), + ), })), ) diff --git a/packages/db/tests/query/includes-publication-oracle.test.ts b/packages/db/tests/query/includes-publication-oracle.test.ts index a05fe7406e..9550aa4c9d 100644 --- a/packages/db/tests/query/includes-publication-oracle.test.ts +++ b/packages/db/tests/query/includes-publication-oracle.test.ts @@ -1,15 +1,15 @@ import { setImmediate as yieldToRunner } from 'node:timers/promises' import { fc, test as fcTest } from '@fast-check/vitest' import { afterEach, describe, expect, it } from 'vitest' -import { createCollection } from '../../src/collection/index.js' -import { BasicIndex } from '../../src/indexes/basic-index.js' -import { BTreeIndex } from '../../src/indexes/btree-index.js' -import { createEffect } from '../../src/query/effect.js' import { createLiveQueryCollection, eq, materialize, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { BasicIndex } from '../../src/indexes/basic-index.js' +import { BTreeIndex } from '../../src/indexes/btree-index.js' +import { createEffect } from '../../src/query/effect.js' import { runTrace } from '../trace-runner.js' import { oraclePropertyOptions, readOracleRunConfig } from '../oracle-config.js' import { withHistoryCleanup } from '../optimistic-history-oracle.js' diff --git a/packages/db/tests/query/includes-query-shape-oracle.test.ts b/packages/db/tests/query/includes-query-shape-oracle.test.ts index 02ad01cc05..4291c10808 100644 --- a/packages/db/tests/query/includes-query-shape-oracle.test.ts +++ b/packages/db/tests/query/includes-query-shape-oracle.test.ts @@ -1,11 +1,11 @@ import { fc, test as fcTest } from '@fast-check/vitest' import { describe, expect } from 'vitest' -import { BasicIndex } from '../../src/indexes/basic-index.js' import { createLiveQueryCollection, eq, materialize, } from '../../src/query/index.js' +import { BasicIndex } from '../../src/indexes/basic-index.js' import { runTrace } from '../trace-runner.js' import { oraclePropertyOptions, oracleRuns } from '../oracle-config.js' import { createControlledCollection } from './includes-oracle-helpers.js' diff --git a/packages/db/tests/query/includes-scope-identity-oracle.ts b/packages/db/tests/query/includes-scope-identity-oracle.ts index 61b740c0c1..613244e91e 100644 --- a/packages/db/tests/query/includes-scope-identity-oracle.ts +++ b/packages/db/tests/query/includes-scope-identity-oracle.ts @@ -1,6 +1,4 @@ import { fc } from '@fast-check/vitest' -import { createCollection } from '../../src/collection/index.js' -import { createFilterFunctionFromExpression } from '../../src/collection/change-events.js' import { and, createLiveQueryCollection, @@ -9,6 +7,8 @@ import { not, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { createFilterFunctionFromExpression } from '../../src/collection/change-events.js' import { mockSyncCollectionOptions } from '../utils.js' import type { Collection } from '../../src/collection/index.js' import type { LoadSubsetOptions } from '../../src/types.js' diff --git a/packages/db/tests/query/includes-space-oracle-fixture.ts b/packages/db/tests/query/includes-space-oracle-fixture.ts index 1cdebeb8d5..059cd45c31 100644 --- a/packages/db/tests/query/includes-space-oracle-fixture.ts +++ b/packages/db/tests/query/includes-space-oracle-fixture.ts @@ -1,7 +1,11 @@ +import { + createLiveQueryCollection, + eq, + include, +} from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { BTreeIndex } from '../../src/indexes/btree-index.js' import { localOnlyCollectionOptions } from '../../src/local-only.js' -import { createLiveQueryCollection, eq } from '../../src/query/index.js' import { withHistoryCleanup } from '../optimistic-history-oracle.js' type RootRow = { id: string } @@ -98,22 +102,28 @@ export async function createNestedCollectionFixture( const live = createLiveQueryCollection((q) => q.from({ root: sources.roots }).select(({ root }) => ({ id: root.id, - branches: q - .from({ branch: sources.branches }) - .where(({ branch }) => eq(branch.rootId, root.id)) - .select(({ branch }) => ({ - id: branch.id, - twigs: q - .from({ twig: sources.twigs }) - .where(({ twig }) => eq(twig.branchId, branch.id)) - .select(({ twig }) => ({ - id: twig.id, - leaves: q - .from({ leaf: sources.leaves }) - .where(({ leaf }) => eq(leaf.twigId, twig.id)) - .select(({ leaf }) => ({ id: leaf.id })), - })), - })), + branches: include( + q + .from({ branch: sources.branches }) + .where(({ branch }) => eq(branch.rootId, root.id)) + .select(({ branch }) => ({ + id: branch.id, + twigs: include( + q + .from({ twig: sources.twigs }) + .where(({ twig }) => eq(twig.branchId, branch.id)) + .select(({ twig }) => ({ + id: twig.id, + leaves: include( + q + .from({ leaf: sources.leaves }) + .where(({ leaf }) => eq(leaf.twigId, twig.id)) + .select(({ leaf }) => ({ id: leaf.id })), + ), + })), + ), + })), + ), })), ) diff --git a/packages/db/tests/query/includes-temporal-oracle.test.ts b/packages/db/tests/query/includes-temporal-oracle.test.ts index f146360acd..460ed64d5b 100644 --- a/packages/db/tests/query/includes-temporal-oracle.test.ts +++ b/packages/db/tests/query/includes-temporal-oracle.test.ts @@ -1,5 +1,12 @@ import { fc, test as fcTest } from '@fast-check/vitest' import { describe, expect, it, vi } from 'vitest' +import { + createLiveQueryCollection, + eq, + materialize, + queryOnce, + toArray, +} from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { createDeferred } from '../../src/deferred.js' import { BasicIndex } from '../../src/indexes/basic-index.js' @@ -9,13 +16,6 @@ import { SubsetDemandController } from '../../src/query/live/subset-demand-contr import { DeduplicatedLoadSubset } from '../../src/query/subset-dedupe.js' import { createEffect } from '../../src/query/effect.js' import { LoadSubsetOperationAbortedError } from '../../src/errors.js' -import { - createLiveQueryCollection, - eq, - materialize, - queryOnce, - toArray, -} from '../../src/query/index.js' import { runTrace } from '../trace-runner.js' import { oraclePropertyOptions, diff --git a/packages/db/tests/query/includes-work-counter-oracle.test.ts b/packages/db/tests/query/includes-work-counter-oracle.test.ts index 1296cb0f86..52ac609ede 100644 --- a/packages/db/tests/query/includes-work-counter-oracle.test.ts +++ b/packages/db/tests/query/includes-work-counter-oracle.test.ts @@ -1,16 +1,17 @@ import { fc, test as fcTest } from '@fast-check/vitest' import { beforeAll, describe, expect, it } from 'vitest' -import { oraclePropertyOptions, oracleRuns } from '../oracle-config.js' -import { createCollection } from '../../src/collection/index.js' -import { BTreeIndex } from '../../src/indexes/btree-index.js' -import { localOnlyCollectionOptions } from '../../src/local-only.js' import { createLiveQueryCollection, eq, inArray, + include, materialize, toArray, } from '../../src/query/index.js' +import { oraclePropertyOptions, oracleRuns } from '../oracle-config.js' +import { createCollection } from '../../src/collection/index.js' +import { BTreeIndex } from '../../src/indexes/btree-index.js' +import { localOnlyCollectionOptions } from '../../src/local-only.js' import { evaluateReferenceExpression } from '../reference-expression-oracle.js' import { SetWindowFindOneLimitError, @@ -1117,7 +1118,9 @@ async function checkWindowedIncludeWork(testCase: WindowCase): Promise { groupChildren.select(({ child }) => ({ id: child.id })), ) case `collection`: - return groupChildren.select(({ child }) => ({ id: child.id })) + return include( + groupChildren.select(({ child }) => ({ id: child.id })), + ) case `nested`: return materialize( groupChildren.select(({ child }) => ({ diff --git a/packages/db/tests/query/includes.test-d.ts b/packages/db/tests/query/includes.test-d.ts index da1f6f591f..d24c9d3b12 100644 --- a/packages/db/tests/query/includes.test-d.ts +++ b/packages/db/tests/query/includes.test-d.ts @@ -4,6 +4,7 @@ import { concat, createLiveQueryCollection, eq, + include, materialize, queryOnce, toArray, @@ -104,13 +105,15 @@ describe(`includes subquery types`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) @@ -130,7 +133,9 @@ describe(`includes subquery types`, () => { const collection = createLiveQueryCollection((q) => q.from({ p: projects }).select(({ p }) => ({ id: p.id, - issues: q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + issues: include( + q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + ), })), ) @@ -149,20 +154,24 @@ describe(`includes subquery types`, () => { const collection = createLiveQueryCollection((q) => q.from({ p: projects }).select(({ p }) => ({ id: p.id, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), - comments: q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, p.id)) - .select(({ c }) => ({ - id: c.id, - body: c.body, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), + comments: include( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, p.id)) + .select(({ c }) => ({ + id: c.id, + body: c.body, + })), + ), })), ) @@ -179,20 +188,24 @@ describe(`includes subquery types`, () => { const collection = createLiveQueryCollection((q) => q.from({ p: projects }).select(({ p }) => ({ id: p.id, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - comments: q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ - id: c.id, - body: c.body, - })), - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + comments: include( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ + id: c.id, + body: c.body, + })), + ), + })), + ), })), ) @@ -544,3 +557,24 @@ describe(`includes subquery types`, () => { }) }) }) + +describe(`a subquery in select needs an include function`, () => { + test(`rejects a bare subquery field, also when nested`, () => { + const projects = createProjectsCollection() + const issues = createIssuesCollection() + createLiveQueryCollection((q) => + // @ts-expect-error A bare subquery field needs include(). + q.from({ p: projects }).select(({ p }) => ({ + issues: q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + })), + ) + createLiveQueryCollection((q) => + // @ts-expect-error A nested bare subquery field needs include(). + q.from({ p: projects }).select(({ p }) => ({ + detail: { + issues: q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + }, + })), + ) + }) +}) diff --git a/packages/db/tests/query/includes.test.ts b/packages/db/tests/query/includes.test.ts index b4a9def149..bd42ca758c 100644 --- a/packages/db/tests/query/includes.test.ts +++ b/packages/db/tests/query/includes.test.ts @@ -1,12 +1,15 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { and, + caseWhen, coalesce, concat, count, createLiveQueryCollection, eq, + gt, gte, + include, materialize, toArray, } from '../../src/query/index.js' @@ -19,6 +22,7 @@ import { resetCleanupQueue, stripVirtualProps, } from '../utils.js' +import { IncludeOperatorRequiredError } from '../../src/errors.js' import type { SyncConfig } from '../../src/types.js' type Project = { @@ -159,13 +163,15 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) } @@ -372,10 +378,12 @@ describe(`includes subqueries`, () => { const collection = createLiveQueryCollection((q) => q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, - items: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ id: child.id })), + items: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ id: child.id })), + ), })), ) @@ -530,12 +538,14 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - ...i, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + ...i, + })), + ), })), ) @@ -664,14 +674,16 @@ describe(`includes subqueries`, () => { const collection = createLiveQueryCollection((q) => q.from({ lineItem: lineItems }).select(({ lineItem }) => ({ id: lineItem.id, - product: q - .from({ product: products }) - .where(({ product }) => eq(product.id, lineItem.productId)) - .select(({ product }) => ({ - id: product.id, - categoryId: product.categoryId, - name: product.name, - })), + product: include( + q + .from({ product: products }) + .where(({ product }) => eq(product.id, lineItem.productId)) + .select(({ product }) => ({ + id: product.id, + categoryId: product.categoryId, + name: product.name, + })), + ), })), ) await collection.preload() @@ -791,13 +803,15 @@ describe(`includes subqueries`, () => { .select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) @@ -822,14 +836,16 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .orderBy(({ i }) => i.title, `desc`) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .orderBy(({ i }) => i.title, `desc`) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) @@ -859,14 +875,16 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .orderBy(({ i }) => i.title, `asc`) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .orderBy(({ i }) => i.title, `asc`) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) @@ -898,11 +916,13 @@ describe(`includes subqueries`, () => { const collection = createLiveQueryCollection((q) => q.from({ p: projects }).select(({ p }) => ({ id: p.id, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .orderBy(({ i }) => i.title, `asc`) - .select(({ i }) => ({ id: i.id })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .orderBy(({ i }) => i.title, `asc`) + .select(({ i }) => ({ id: i.id })), + ), })), ) @@ -932,15 +952,17 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .orderBy(({ i }) => i.title, `asc`) - .limit(1) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .orderBy(({ i }) => i.title, `asc`) + .limit(1) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) @@ -966,15 +988,17 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .orderBy(({ i }) => i.title, `asc`) - .limit(1) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .orderBy(({ i }) => i.title, `asc`) + .limit(1) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) @@ -1052,13 +1076,15 @@ describe(`includes subqueries`, () => { id: t.id, name: t.name, departmentId: t.departmentId, - members: q - .from({ m: members }) - .where(({ m }) => eq(m.departmentId, t.departmentId)) - .select(({ m }) => ({ - id: m.id, - name: m.name, - })), + members: include( + q + .from({ m: members }) + .where(({ m }) => eq(m.departmentId, t.departmentId)) + .select(({ m }) => ({ + id: m.id, + name: m.name, + })), + ), })), ) @@ -1102,13 +1128,15 @@ describe(`includes subqueries`, () => { id: t.id, name: t.name, departmentId: t.departmentId, - members: q - .from({ m: members }) - .where(({ m }) => eq(m.departmentId, t.departmentId)) - .select(({ m }) => ({ - id: m.id, - name: m.name, - })), + members: include( + q + .from({ m: members }) + .where(({ m }) => eq(m.departmentId, t.departmentId)) + .select(({ m }) => ({ + id: m.id, + name: m.name, + })), + ), })), ) @@ -1149,13 +1177,15 @@ describe(`includes subqueries`, () => { id: t.id, name: t.name, departmentId: t.departmentId, - members: q - .from({ m: members }) - .where(({ m }) => eq(m.departmentId, t.departmentId)) - .select(({ m }) => ({ - id: m.id, - name: m.name, - })), + members: include( + q + .from({ m: members }) + .where(({ m }) => eq(m.departmentId, t.departmentId)) + .select(({ m }) => ({ + id: m.id, + name: m.name, + })), + ), })), ) @@ -1215,10 +1245,12 @@ describe(`includes subqueries`, () => { q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, groupId: parent.groupId, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ id: child.id })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ id: child.id })), + ), })), ) @@ -1311,10 +1343,12 @@ describe(`includes subqueries`, () => { const collection = createLiveQueryCollection((q) => q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ id: child.id })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ id: child.id })), + ), })), ) @@ -1360,10 +1394,12 @@ describe(`includes subqueries`, () => { .limit(1) .select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ id: child.id })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ id: child.id })), + ), })), ) @@ -1415,13 +1451,15 @@ describe(`includes subqueries`, () => { .limit(1) .select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ - id: child.id, - label: child.label, - })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ + id: child.id, + label: child.label, + })), + ), })), ) const collection = buildQuery() @@ -1497,9 +1535,11 @@ describe(`includes subqueries`, () => { .where(({ parent }) => eq(parent.active, true)) .select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)), + ), })), ) @@ -1547,10 +1587,12 @@ describe(`includes subqueries`, () => { q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, groupId: parent.groupId, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ id: child.id })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ id: child.id })), + ), })), getKey: (row) => { keyReads += 1 @@ -1642,10 +1684,12 @@ describe(`includes subqueries`, () => { .limit(3) .select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ id: child.id })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ id: child.id })), + ), })), ) const observations: Array< @@ -1712,10 +1756,12 @@ describe(`includes subqueries`, () => { q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, groupId: parent.groupId, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .select(({ child }) => ({ id: child.id })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .select(({ child }) => ({ id: child.id })), + ), })), ) @@ -1785,13 +1831,15 @@ describe(`includes subqueries`, () => { q.from({ t: teams }).select(({ t }) => ({ id: t.id, name: t.name, - members: q - .from({ m: members }) - .where(({ m }) => eq(m.departmentId, t.departmentId)) - .select(({ m }) => ({ - id: m.id, - name: m.name, - })), + members: include( + q + .from({ m: members }) + .where(({ m }) => eq(m.departmentId, t.departmentId)) + .select(({ m }) => ({ + id: m.id, + name: m.name, + })), + ), })), ) @@ -1836,20 +1884,24 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - comments: q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ - id: c.id, - body: c.body, - })), - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + comments: include( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ + id: c.id, + body: c.body, + })), + ), + })), + ), })), ) } @@ -2228,22 +2280,24 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - comments: toArray( - q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ - id: c.id, - body: c.body, - })), - ), - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + comments: toArray( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ + id: c.id, + body: c.body, + })), + ), + })), + ), })), ) } @@ -2461,13 +2515,15 @@ describe(`includes subqueries`, () => { .select(({ i }) => ({ id: i.id, title: i.title, - comments: q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ - id: c.id, - body: c.body, - })), + comments: include( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ + id: c.id, + body: c.body, + })), + ), })), ), })), @@ -2941,15 +2997,17 @@ describe(`includes subqueries`, () => { id: p.id, name: p.name, createdBy: p.createdBy, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => eq(i.projectId, p.id)) - .where(({ i }) => eq(i.createdBy, p.createdBy)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - createdBy: i.createdBy, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => eq(i.projectId, p.id)) + .where(({ i }) => eq(i.createdBy, p.createdBy)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + createdBy: i.createdBy, + })), + ), })), ) @@ -2996,15 +3054,17 @@ describe(`includes subqueries`, () => { id: p.id, name: p.name, createdBy: p.createdBy, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => eq(i.projectId, p.id)) - .where(({ i }) => eq(i.createdBy, p.createdBy)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - createdBy: i.createdBy, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => eq(i.projectId, p.id)) + .where(({ i }) => eq(i.createdBy, p.createdBy)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + createdBy: i.createdBy, + })), + ), })), ) @@ -3036,15 +3096,17 @@ describe(`includes subqueries`, () => { id: p.id, name: p.name, createdBy: p.createdBy, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => eq(i.projectId, p.id)) - .where(({ i }) => eq(i.createdBy, p.createdBy)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - createdBy: i.createdBy, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => eq(i.projectId, p.id)) + .where(({ i }) => eq(i.createdBy, p.createdBy)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + createdBy: i.createdBy, + })), + ), })), ) @@ -3081,16 +3143,18 @@ describe(`includes subqueries`, () => { id: p.id, name: p.name, createdBy: p.createdBy, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => eq(i.projectId, p.id)) - .where(({ i }) => eq(i.createdBy, p.createdBy)) - .where(({ i }) => eq(i.title, `Bug in Alpha`)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - createdBy: i.createdBy, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => eq(i.projectId, p.id)) + .where(({ i }) => eq(i.createdBy, p.createdBy)) + .where(({ i }) => eq(i.title, `Bug in Alpha`)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + createdBy: i.createdBy, + })), + ), })), ) @@ -3114,15 +3178,17 @@ describe(`includes subqueries`, () => { q.from({ p: projectsWC }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => - and(eq(i.projectId, p.id), eq(i.createdBy, `alice`)), - ) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => + and(eq(i.projectId, p.id), eq(i.createdBy, `alice`)), + ) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) @@ -3153,16 +3219,18 @@ describe(`includes subqueries`, () => { id: p.id, name: p.name, createdBy: p.createdBy, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => - and(eq(i.projectId, p.id), eq(i.createdBy, p.createdBy)), - ) - .select(({ i }) => ({ - id: i.id, - title: i.title, - createdBy: i.createdBy, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => + and(eq(i.projectId, p.id), eq(i.createdBy, p.createdBy)), + ) + .select(({ i }) => ({ + id: i.id, + title: i.title, + createdBy: i.createdBy, + })), + ), })), ) @@ -3192,16 +3260,18 @@ describe(`includes subqueries`, () => { id: p.id, name: p.name, createdBy: p.createdBy, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => - and(eq(i.projectId, p.id), eq(i.createdBy, p.createdBy)), - ) - .select(({ i }) => ({ - id: i.id, - title: i.title, - createdBy: i.createdBy, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => + and(eq(i.projectId, p.id), eq(i.createdBy, p.createdBy)), + ) + .select(({ i }) => ({ + id: i.id, + title: i.title, + createdBy: i.createdBy, + })), + ), })), ) @@ -3268,14 +3338,16 @@ describe(`includes subqueries`, () => { q.from({ p: parents }).select(({ p }) => ({ id: p.id, createdBy: p.createdBy, - items: q - .from({ c: children }) - .where(({ c }) => eq(c.groupId, p.groupId)) - .where(({ c }) => eq(c.createdBy, p.createdBy)) - .select(({ c }) => ({ - id: c.id, - createdBy: c.createdBy, - })), + items: include( + q + .from({ c: children }) + .where(({ c }) => eq(c.groupId, p.groupId)) + .where(({ c }) => eq(c.createdBy, p.createdBy)) + .select(({ c }) => ({ + id: c.id, + createdBy: c.createdBy, + })), + ), })), ) @@ -3513,19 +3585,21 @@ describe(`includes subqueries`, () => { const collection = createLiveQueryCollection((q) => q.from({ parent: parents }).select(({ parent }) => ({ id: parent.id, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.groupId, parent.groupId)) - .where(({ child }) => gte(child.score, parent.minimumScore)) - .select(({ child }) => ({ - id: child.id, - notes: materialize( - q - .from({ note: notes }) - .where(({ note }) => eq(note.childId, child.id)) - .select(({ note }) => ({ id: note.id })), - ), - })), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.groupId, parent.groupId)) + .where(({ child }) => gte(child.score, parent.minimumScore)) + .select(({ child }) => ({ + id: child.id, + notes: materialize( + q + .from({ note: notes }) + .where(({ note }) => eq(note.childId, child.id)) + .select(({ note }) => ({ id: note.id })), + ), + })), + ), })), ) @@ -3603,16 +3677,18 @@ describe(`includes subqueries`, () => { q.from({ p: parents }).select(({ p }) => ({ id: p.id, createdBy: p.createdBy, - items: q - .from({ c: children }) - .where(({ c }) => eq(c.groupId, p.groupId)) - .where(({ c }) => eq(c.createdBy, p.createdBy)) - .orderBy(({ c }) => c.id, `asc`) - .limit(1) - .select(({ c }) => ({ - id: c.id, - createdBy: c.createdBy, - })), + items: include( + q + .from({ c: children }) + .where(({ c }) => eq(c.groupId, p.groupId)) + .where(({ c }) => eq(c.createdBy, p.createdBy)) + .orderBy(({ c }) => c.id, `asc`) + .limit(1) + .select(({ c }) => ({ + id: c.id, + createdBy: c.createdBy, + })), + ), })), ) @@ -3638,20 +3714,22 @@ describe(`includes subqueries`, () => { id: p.id, name: p.name, createdBy: p.createdBy, - issues: q - .from({ i: issuesWC }) - .where(({ i }) => - and( - eq(i.projectId, p.id), - eq(i.createdBy, p.createdBy), - eq(i.title, `Bug in Alpha`), - ), - ) - .select(({ i }) => ({ - id: i.id, - title: i.title, - createdBy: i.createdBy, - })), + issues: include( + q + .from({ i: issuesWC }) + .where(({ i }) => + and( + eq(i.projectId, p.id), + eq(i.createdBy, p.createdBy), + eq(i.title, `Bug in Alpha`), + ), + ) + .select(({ i }) => ({ + id: i.id, + title: i.title, + createdBy: i.createdBy, + })), + ), })), ) @@ -3714,24 +3792,28 @@ describe(`includes subqueries`, () => { const collection = createLiveQueryCollection((q) => q.from({ p: nProjects }).select(({ p }) => ({ id: p.id, - issues: q - .from({ i: nIssues }) - .where(({ i }) => eq(i.groupId, p.groupId)) - .where(({ i }) => eq(i.createdBy, p.createdBy)) - .select(({ i }) => ({ - id: i.id, - createdBy: i.createdBy, - categoryId: i.categoryId, - comments: q - .from({ c: nComments }) - .where(({ c }) => eq(c.categoryId, i.categoryId)) - .where(({ c }) => eq(c.createdBy, i.createdBy)) - .select(({ c }) => ({ - id: c.id, - createdBy: c.createdBy, - body: c.body, - })), - })), + issues: include( + q + .from({ i: nIssues }) + .where(({ i }) => eq(i.groupId, p.groupId)) + .where(({ i }) => eq(i.createdBy, p.createdBy)) + .select(({ i }) => ({ + id: i.id, + createdBy: i.createdBy, + categoryId: i.categoryId, + comments: include( + q + .from({ c: nComments }) + .where(({ c }) => eq(c.categoryId, i.categoryId)) + .where(({ c }) => eq(c.createdBy, i.createdBy)) + .select(({ c }) => ({ + id: c.id, + createdBy: c.createdBy, + body: c.body, + })), + ), + })), + ), })), ) @@ -3802,32 +3884,38 @@ describe(`includes subqueries`, () => { const collection = createLiveQueryCollection((q) => q.from({ a: l0 }).select(({ a }) => ({ id: a.id, - children: q - .from({ b: l1 }) - .where(({ b }) => eq(b.groupId, a.groupId)) - .where(({ b }) => eq(b.owner, a.owner)) - .select(({ b }) => ({ - id: b.id, - tagId: b.tagId, - owner: b.owner, - grandchildren: q - .from({ c: l2 }) - .where(({ c }) => eq(c.tagId, b.tagId)) - .where(({ c }) => eq(c.owner, b.owner)) - .select(({ c }) => ({ - id: c.id, - flagId: c.flagId, - owner: c.owner, - leaves: q - .from({ d: l3 }) - .where(({ d }) => eq(d.flagId, c.flagId)) - .where(({ d }) => eq(d.owner, c.owner)) - .select(({ d }) => ({ - id: d.id, - text: d.text, + children: include( + q + .from({ b: l1 }) + .where(({ b }) => eq(b.groupId, a.groupId)) + .where(({ b }) => eq(b.owner, a.owner)) + .select(({ b }) => ({ + id: b.id, + tagId: b.tagId, + owner: b.owner, + grandchildren: include( + q + .from({ c: l2 }) + .where(({ c }) => eq(c.tagId, b.tagId)) + .where(({ c }) => eq(c.owner, b.owner)) + .select(({ c }) => ({ + id: c.id, + flagId: c.flagId, + owner: c.owner, + leaves: include( + q + .from({ d: l3 }) + .where(({ d }) => eq(d.flagId, c.flagId)) + .where(({ d }) => eq(d.owner, c.owner)) + .select(({ d }) => ({ + id: d.id, + text: d.text, + })), + ), })), - })), - })), + ), + })), + ), })), ) @@ -3862,9 +3950,11 @@ describe(`includes subqueries`, () => { createLiveQueryCollection((q) => q.from({ p: projects }).select(({ p }) => ({ id: p.id, - issues: q - .from({ i: issues }) - .select(({ i }) => ({ id: i.id, title: i.title })), + issues: include( + q + .from({ i: issues }) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), })), ), ).toThrow(/must have a WHERE clause with an eq\(\) condition/) @@ -3875,10 +3965,12 @@ describe(`includes subqueries`, () => { createLiveQueryCollection((q) => q.from({ p: projects }).select(({ p }) => ({ id: p.id, - issues: q - .from({ i: issues }) - .where(({ i }) => i.projectId) - .select(({ i }) => ({ id: i.id, title: i.title })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => i.projectId) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), })), ), ).toThrow(/must have a WHERE clause with an eq\(\) condition/) @@ -3889,10 +3981,12 @@ describe(`includes subqueries`, () => { createLiveQueryCollection((q) => q.from({ p: projects }).select(({ p }) => ({ id: p.id, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, i.id)) - .select(({ i }) => ({ id: i.id, title: i.title })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, i.id)) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), })), ), ).toThrow(/must have a WHERE clause with an eq\(\) condition/) @@ -3929,14 +4023,18 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ id: i.id, title: i.title })), - milestones: q - .from({ m: milestones }) - .where(({ m }) => eq(m.projectId, p.id)) - .select(({ m }) => ({ id: m.id, name: m.name })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), + milestones: include( + q + .from({ m: milestones }) + .where(({ m }) => eq(m.projectId, p.id)) + .select(({ m }) => ({ id: m.id, name: m.name })), + ), })), ) @@ -4027,14 +4125,18 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ id: i.id, title: i.title })), - milestones: q - .from({ m: milestones }) - .where(({ m }) => eq(m.projectId, p.id)) - .select(({ m }) => ({ id: m.id, name: m.name })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), + milestones: include( + q + .from({ m: milestones }) + .where(({ m }) => eq(m.projectId, p.id)) + .select(({ m }) => ({ id: m.id, name: m.name })), + ), })), ) @@ -4226,22 +4328,24 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - comments: toArray( - q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ - id: c.id, - body: c.body, - })), - ), - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + comments: toArray( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ + id: c.id, + body: c.body, + })), + ), + })), + ), })), ) } @@ -4459,13 +4563,15 @@ describe(`includes subqueries`, () => { .select(({ i }) => ({ id: i.id, title: i.title, - comments: q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ - id: c.id, - body: c.body, - })), + comments: include( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ + id: c.id, + body: c.body, + })), + ), })), ), })), @@ -4888,10 +4994,12 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issueCount: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ total: count(i.id) })), + issueCount: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ total: count(i.id) })), + ), })), ) } @@ -5008,17 +5116,21 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - commentCount: q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ total: count(c.id) })), - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + commentCount: include( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ total: count(c.id) })), + ), + })), + ), })), ) } @@ -5053,19 +5165,21 @@ describe(`includes subqueries`, () => { q.from({ p: projects }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issues }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - commentCount: toArray( - q - .from({ c: comments }) - .where(({ c }) => eq(c.issueId, i.id)) - .select(({ c }) => ({ total: count(c.id) })), - ), - })), + issues: include( + q + .from({ i: issues }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + commentCount: toArray( + q + .from({ c: comments }) + .where(({ c }) => eq(c.issueId, i.id)) + .select(({ c }) => ({ total: count(c.id) })), + ), + })), + ), })), ) } @@ -7201,17 +7315,21 @@ describe(`includes subqueries`, () => { q.from({ p: products }).select(({ p }) => ({ id: p.id, title: p.title, - priceRanges: q - .from({ pr: priceRanges }) - .where(({ pr }) => eq(pr.productId, p.id)) - .select(({ pr }) => ({ - id: pr.id, - regionId: pr.regionId, - region: q - .from({ r: regions }) - .where(({ r }) => eq(r.id, pr.regionId)) - .select(({ r }) => ({ id: r.id, name: r.name })), - })), + priceRanges: include( + q + .from({ pr: priceRanges }) + .where(({ pr }) => eq(pr.productId, p.id)) + .select(({ pr }) => ({ + id: pr.id, + regionId: pr.regionId, + region: include( + q + .from({ r: regions }) + .where(({ r }) => eq(r.id, pr.regionId)) + .select(({ r }) => ({ id: r.id, name: r.name })), + ), + })), + ), })), }) await collection.preload() @@ -7612,7 +7730,7 @@ describe(`includes subqueries`, () => { return { id: parent.id, - facade: childRows(), + facade: include(childRows()), array: toArray(childRows()), joined: concat( toArray( @@ -7881,3 +7999,58 @@ describe(`includes subqueries`, () => { }) }) }) + +describe(`a subquery in select needs an include operator`, () => { + const query = (shape: `field` | `nested` | `caseWhen`) => () => + createLiveQueryCollection((q) => + q.from({ p: createProjectsCollection() }).select(({ p }) => { + const issues = q + .from({ i: createIssuesCollection() }) + .where(({ i }) => eq(i.projectId, p.id)) as any + return shape === `field` + ? { id: p.id, issues } + : shape === `nested` + ? { id: p.id, detail: { issues } } + : { id: p.id, detail: caseWhen(gt(p.id, 0), { issues }, null) } + }), + ) + + it.each([`field`, `nested`, `caseWhen`] as const)( + `throws a coded error for a bare subquery: %s`, + (shape) => { + expect(query(shape)).toThrow(IncludeOperatorRequiredError) + expect(query(shape)).toThrow(/issues: include\(/) + }, + ) + + it(`includes a Collection with include()`, async () => { + const projects = createProjectsCollection() + const issues = createIssuesCollection() + const live = createLiveQueryCollection((q) => + q.from({ p: projects }).select(({ p }) => ({ + id: p.id, + issues: include( + q.from({ i: issues }).where(({ i }) => eq(i.projectId, p.id)), + ), + })), + ) + await live.preload() + const first = live.toArray[0]! + expect(first.issues.toArray.length).toBeGreaterThan(0) + await live.cleanup() + }) +}) + +describe(`include() argument`, () => { + it(`throws a coded error when include() does not wrap a subquery builder`, () => { + const projects = createProjectsCollection() + expect(() => + createLiveQueryCollection((q) => + q.from({ p: projects }).select(({ p }) => ({ + id: p.id, + issues: include(createIssuesCollection() as any), + })), + ), + ).toThrow(`include() must wrap a subquery builder`) + }) +}) diff --git a/packages/db/tests/query/ir-stable-identity-oracle.test.ts b/packages/db/tests/query/ir-stable-identity-oracle.test.ts index f475760351..0eb66daca0 100644 --- a/packages/db/tests/query/ir-stable-identity-oracle.test.ts +++ b/packages/db/tests/query/ir-stable-identity-oracle.test.ts @@ -1,6 +1,18 @@ import { describe, expect, it, vi } from 'vitest' import { fc, test as fcTest } from '@fast-check/vitest' import { Temporal } from 'temporal-polyfill' +import { + BaseQueryBuilder, + createLiveQueryCollection, + groupKeys, + include, + pooledLiveQueries, +} from '../../src/query/index.js' +import { + GroupingOperatorRequiredError, + IncludeOperatorRequiredError, +} from '../../src/errors.js' +import { includesRuntime } from '../../src/query/live/includes-runtime.js' import { CollectionImpl } from '../../src/collection/index.js' import { Query, getQueryIR } from '../../src/query/builder/index.js' import { @@ -53,6 +65,7 @@ import { getRuntimeReferenceIdentity, } from '../../src/query/runtime-reference-identity.js' import { createValueIdentity } from '../../src/query/equality-value-identity.js' +import { getPreparedLiveQueryIdentity } from '../../src/live-query-options.js' import type { BasicExpression, OrderBy, QueryIR } from '../../src/query/ir.js' import type { LoadSubsetOptions } from '../../src/types.js' @@ -1015,6 +1028,7 @@ function createUnionDerivedIncludesQuery(parentAlias: string): QueryIR { new PropRef([parentAlias, `id`]), new PropRef([`userId`]), `posts`, + includesRuntime, undefined, undefined, `array`, @@ -1057,6 +1071,7 @@ function createCorrelatedUnionIncludesQuery(parentAlias: string): QueryIR { new PropRef([parentAlias, `id`]), new PropRef([`userId`]), `posts`, + includesRuntime, undefined, [new PropRef([parentAlias, `age`])], `array`, @@ -1097,6 +1112,7 @@ function createAlphaRenamedIncludesQuery( new PropRef([parentAlias, `id`]), new PropRef([childAlias, `userId`]), `posts`, + includesRuntime, [ new Func(`eq`, [ new PropRef([parentAlias, `status`]), @@ -1335,13 +1351,15 @@ const structuredQueries: Array<[string, () => QueryIR]> = [ getQueryIR( new Query().from({ user: usersCollection }).select(({ user }) => ({ id: user.id, - posts: new Query() - .from({ post: postsCollection }) - .where(({ post }) => eq(post.userId, user.id)) - .select(({ post }) => ({ - id: post.id, - title: post.title, - })), + posts: include( + new Query() + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)) + .select(({ post }) => ({ + id: post.id, + title: post.title, + })), + ), })), ), ], @@ -1874,3 +1892,301 @@ describe(`stable QueryIR identity smoke test`, () => { ).not.toThrow() }) }) + +/** + * Operator code is part of what a query means. Two queries with one identity + * may share one compiled live query, so a query that compiles must never share + * an identity with one that throws for missing operator code: a hook that + * reuses the compiled query would hide the error. Authority: the opt-in + * operator contract (a missing operator throws its coded error) and the + * identity contract above, that equal identities imply equal results. + * + * The model predicts the compile outcome from the operator code alone. A + * groupBy, or an aggregate, needs the grouping compiler, which `groupKeys()` + * or any aggregate the query builds with `count()` brings. An include needs + * its runtime. Hand-built IR may lack either. The check runs every + * combination, compares each outcome with production, and then requires + * distinct identities for each pair of combinations with different outcomes. + * Combinations with equal outcomes may share an identity or not. + */ +describe(`operator-code identity`, () => { + type GroupBy = `none` | `bare` | `groupKeys` + type Agg = `none` | `built` | `stripped` + type Inc = `none` | `built` | `stripped` + type Outcome = `compiles` | `groupingRequired` | `includeRequired` + + function modelOutcome(groupBy: GroupBy, agg: Agg, inc: Inc): Outcome { + const grouping = groupBy === `groupKeys` || agg === `built` + if ((groupBy !== `none` || agg !== `none`) && !grouping) + return `groupingRequired` + if (inc === `stripped`) return `includeRequired` + return `compiles` + } + + // A copy of an IR node without one field of operator code. + const without = (node: T, field: string): T => + Object.assign(Object.create(Object.getPrototypeOf(node)), node, { + [field]: undefined, + }) + + function buildIR(groupBy: GroupBy, agg: Agg, inc: Inc): QueryIR { + const keys = ({ user }: any) => user.teamId + let query: any = new Query().from({ user: usersCollection }) + if (groupBy === `bare`) query = query.groupBy(keys) + if (groupBy === `groupKeys`) query = query.groupBy(groupKeys(keys)) + query = query.select(({ user }: any) => ({ + // A grouped select reads its key; one implicit group reads none. + ...(groupBy === `none` ? {} : { teamId: user.teamId }), + ...(agg === `none` ? {} : { n: count(user.id) }), + ...(groupBy === `none` && agg === `none` ? { id: user.id } : {}), + ...(inc === `none` + ? {} + : { + posts: include( + new Query() + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)), + ), + }), + })) + const ir = getQueryIR(query) + const select = { ...ir.select! } as Record + if (agg === `stripped`) select.n = without(select.n, `grouping`) + if (inc === `stripped`) select.posts = without(select.posts, `runtime`) + return { ...ir, select } + } + + function productionOutcome(ir: QueryIR): Outcome | `other` { + try { + createLiveQueryCollection({ query: new BaseQueryBuilder(ir) as any }) + return `compiles` + } catch (error) { + if (error instanceof GroupingOperatorRequiredError) + return `groupingRequired` + if (error instanceof IncludeOperatorRequiredError) + return `includeRequired` + return `other` + } + } + + // Includes beside a grouped select are outside this law; an include joins + // only the ungrouped, aggregate-free shape. + const cases = ([`none`, `bare`, `groupKeys`] as const).flatMap((groupBy) => + ([`none`, `built`, `stripped`] as const).flatMap((agg) => + (groupBy === `none` && agg === `none` + ? ([`none`, `built`, `stripped`] as const) + : ([`none`] as const) + ).map((inc) => ({ groupBy, agg, inc })), + ), + ) + + it(`keeps queries whose operator code changes the compile outcome distinct`, () => { + const results = cases.map(({ groupBy, agg, inc }) => { + const ir = buildIR(groupBy, agg, inc) + const label = `${groupBy} groupBy, ${agg} aggregate, ${inc} include` + const outcome = modelOutcome(groupBy, agg, inc) + expect(productionOutcome(ir), `${label}: compile outcome`).toBe(outcome) + return { label, outcome, identity: getQueryIdentity(ir) } + }) + for (const a of results) + for (const b of results) + if (a.outcome !== b.outcome) + expect(a.identity, `${a.label} vs ${b.label}`).not.toBe(b.identity) + }) + + /** + * The wide product: every way the query can hold operator code, each + * present or missing. The compile outcome here is production's own: the + * law relates two separate functions, identity and compilation, and the + * aggregate-select oracle owns which error each shape throws. Several + * errors may apply to one shape, so a hand model would have to pick one. + * Each identity function a hook uses must satisfy the law: the IR + * identity, and the prepared identity with a pool, which takes a shortcut + * for a query that a pool can serve. + */ + type Slot = `none` | `built` | `stripped` + type Include = + | `none` + | `field` + | `field-stripped` + | `case` + | `case-stripped` + | `child-aggregate` + | `child-aggregate-stripped` + type Wide = { + groupBy: GroupBy + select: Slot + having: Slot + orderBy: Slot + include: Include + // An `eq` filter and no select make a query that a pool can serve. + poolable: boolean + } + + // A deep copy that leaves out operator code: aggregates lose their + // grouping compiler, includes their runtime. Neither enters an include's + // child query. + function strip(node: any, field: `grouping` | `runtime`): any { + if (Array.isArray(node)) return node.map((item) => strip(item, field)) + if (node === null || typeof node !== `object`) return node + if (node instanceof IncludesSubquery) + return field === `runtime` ? without(node, `runtime`) : node + // A ref's binding is not an own enumerable field, so refs and values + // stay as they are. + if (node instanceof PropRef || node instanceof Value) return node + const copy = Object.create(Object.getPrototypeOf(node)) + for (const [key, value] of Object.entries(node)) + copy[key] = + key === `collection` || key === `query` ? value : strip(value, field) + if (field === `grouping` && `grouping` in node && `args` in node) + copy.grouping = undefined + return copy + } + + function buildWide(w: Wide): QueryIR { + const keys = ({ user }: any) => user.teamId + const child = (user: any, aggregate: boolean) => + include( + new Query() + .from({ post: postsCollection }) + .where(({ post }) => eq(post.userId, user.id)) + .select(({ post }) => + aggregate ? { n: count(post.id) } : { id: post.id }, + ), + ) + let query: any = new Query().from({ user: usersCollection }) + if (w.poolable) query = query.where(({ user }: any) => eq(user.teamId, `a`)) + if (w.groupBy === `bare`) query = query.groupBy(keys) + if (w.groupBy === `groupKeys`) query = query.groupBy(groupKeys(keys)) + if (!w.poolable) + query = query.select(({ user }: any) => ({ + // One implicit group selects only its aggregate. + ...(w.groupBy !== `none` + ? { teamId: user.teamId } + : w.select === `none` + ? { id: user.id } + : {}), + ...(w.select === `none` ? {} : { n: count(user.id) }), + ...(w.include === `none` + ? {} + : { + posts: w.include.startsWith(`case`) + ? caseWhen( + eq(user.id, 1), + child(user, false), + child(user, false), + ) + : child(user, w.include.startsWith(`child-aggregate`)), + }), + })) + if (w.having !== `none`) + query = query.having(({ user }: any) => eq(count(user.id), 2)) + if (w.orderBy !== `none`) + query = query.orderBy(({ user }: any) => count(user.id)) + const ir = getQueryIR(query) + const select = ir.select && ({ ...ir.select } as Record) + if (select && w.select === `stripped`) + select.n = strip(select.n, `grouping`) + if ( + select && + (w.include === `field-stripped` || w.include === `case-stripped`) + ) + select.posts = strip(select.posts, `runtime`) + if (select && w.include === `child-aggregate-stripped`) + select.posts = Object.assign(without(select.posts, `query`), { + query: { + ...select.posts.query, + select: strip(select.posts.query.select, `grouping`), + }, + }) + return { + ...ir, + ...(select ? { select } : {}), + ...(w.having === `stripped` + ? { having: strip(ir.having, `grouping`) } + : {}), + ...(w.orderBy === `stripped` + ? { orderBy: strip(ir.orderBy, `grouping`) } + : {}), + } + } + + function wideOutcome(ir: QueryIR): string { + try { + createLiveQueryCollection({ query: new BaseQueryBuilder(ir) as any }) + return `compiles` + } catch (error) { + return error instanceof Error ? error.constructor.name : `unknown` + } + } + + const slots = [`none`, `built`, `stripped`] as const + const wideCases: Array = [] + for (const groupBy of [`none`, `bare`, `groupKeys`] as const) + for (const select of slots) + for (const having of slots) + for (const orderBy of slots) + for (const inc of [ + `none`, + `field`, + `field-stripped`, + `case`, + `case-stripped`, + `child-aggregate`, + `child-aggregate-stripped`, + ] as const) + for (const poolable of [false, true]) + // A query without a select holds no select aggregate or include; + // an include sits beside no aggregate or grouping, where it + // could not compile anyway. + if ( + (!poolable || (select === `none` && inc === `none`)) && + (inc === `none` || (groupBy === `none` && select === `none`)) + ) + wideCases.push({ + groupBy, + select, + having, + orderBy, + include: inc, + poolable, + }) + + it(`keeps every operator-code variant with a different compile outcome distinct`, () => { + const identities = { + ir: (ir: QueryIR) => getQueryIdentity(ir), + pooled: (ir: QueryIR) => + JSON.stringify( + getPreparedLiveQueryIdentity( + new BaseQueryBuilder(ir), + pooledLiveQueries, + ), + ), + } + const built = wideCases.map((w) => { + const ir = buildWide(w) + return { label: JSON.stringify(w), ir, outcome: wideOutcome(ir) } + }) + // The product must reach each outcome the law separates. + const outcomes = new Set(built.map(({ outcome }) => outcome)) + for (const expected of [ + `compiles`, + `GroupingOperatorRequiredError`, + `IncludeOperatorRequiredError`, + ]) + expect(outcomes, `reaches ${expected}`).toContain(expected) + for (const [name, identityOf] of Object.entries(identities)) { + const byIdentity = new Map() + for (const { label, ir, outcome } of built) { + const identity = identityOf(ir) + const seen = byIdentity.get(identity) + if (seen) + expect( + outcome, + `${name} identity shared by ${seen.label} and ${label}`, + ).toBe(seen.outcome) + else byIdentity.set(identity, { label, outcome }) + } + } + }) +}) diff --git a/packages/db/tests/query/join-result-key-oracle.property.test.ts b/packages/db/tests/query/join-result-key-oracle.property.test.ts index 7fcfafc4e3..cd844ef9d8 100644 --- a/packages/db/tests/query/join-result-key-oracle.property.test.ts +++ b/packages/db/tests/query/join-result-key-oracle.property.test.ts @@ -42,8 +42,8 @@ */ import { fc, test as fcTest } from '@fast-check/vitest' import { describe, expect, it } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection, eq } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { oraclePropertyOptions, oracleRuns, diff --git a/packages/db/tests/query/live-query-collection.test.ts b/packages/db/tests/query/live-query-collection.test.ts index 99f211ceb6..f8c22204f9 100644 --- a/packages/db/tests/query/live-query-collection.test.ts +++ b/packages/db/tests/query/live-query-collection.test.ts @@ -1,6 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Temporal } from 'temporal-polyfill' -import { createCollection } from '../../src/collection/index.js' import { and, coalesce, @@ -9,6 +8,7 @@ import { ilike, liveQueryCollectionOptions, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { Query } from '../../src/query/builder/index.js' import { flushPromises, diff --git a/packages/db/tests/query/load-subset-join-dedupe.test.ts b/packages/db/tests/query/load-subset-join-dedupe.test.ts index 54427f5920..46a3b75747 100644 --- a/packages/db/tests/query/load-subset-join-dedupe.test.ts +++ b/packages/db/tests/query/load-subset-join-dedupe.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest' +import { createLiveQueryCollection, eq } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { BasicIndex } from '../../src/indexes/basic-index.js' import { extractSimpleComparisons } from '../../src/query/expression-helpers.js' -import { createLiveQueryCollection, eq } from '../../src/query/index.js' import { expectAssertionFailure } from '../expected-failure.js' import { TraceAssertionError } from '../trace-runner.js' import { flushPromises } from '../utils.js' diff --git a/packages/db/tests/query/load-subset-replay-refinement-oracle.test.ts b/packages/db/tests/query/load-subset-replay-refinement-oracle.test.ts index f537b670d3..84a1621a37 100644 --- a/packages/db/tests/query/load-subset-replay-refinement-oracle.test.ts +++ b/packages/db/tests/query/load-subset-replay-refinement-oracle.test.ts @@ -1,7 +1,5 @@ import fc from 'fast-check' import { describe, expect, it, vi } from 'vitest' -import { createCollection } from '../../src/collection/index.js' -import { createDeferred } from '../../src/deferred.js' import { createLiveQueryCollection, eq, @@ -9,6 +7,8 @@ import { not, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { createDeferred } from '../../src/deferred.js' import { BasicIndex } from '../../src/indexes/basic-index.js' import { BTreeIndex } from '../../src/indexes/btree-index.js' import { evaluateReferenceExpression } from '../reference-expression-oracle.js' diff --git a/packages/db/tests/query/load-subset-source-readiness-refinement-oracle.test.ts b/packages/db/tests/query/load-subset-source-readiness-refinement-oracle.test.ts index 9fa17aec30..ceedcd364a 100644 --- a/packages/db/tests/query/load-subset-source-readiness-refinement-oracle.test.ts +++ b/packages/db/tests/query/load-subset-source-readiness-refinement-oracle.test.ts @@ -1,7 +1,7 @@ import { expect, it } from 'vitest' +import { BTreeIndex } from '../../src/index.js' import { createCollection } from '../../src/collection/index.js' import { createDeferred } from '../../src/deferred.js' -import { BTreeIndex } from '../../src/index.js' import { extractSimpleComparisons } from '../../src/query/expression-helpers.js' import { createLiveQueryCollection, diff --git a/packages/db/tests/query/load-subset-subquery.test.ts b/packages/db/tests/query/load-subset-subquery.test.ts index ead6b921d3..a9e8c1f94a 100644 --- a/packages/db/tests/query/load-subset-subquery.test.ts +++ b/packages/db/tests/query/load-subset-subquery.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { and, coalesce, @@ -14,6 +13,7 @@ import { not, or, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { PropRef, Value } from '../../src/query/ir.js' import type { Collection } from '../../src/collection/index.js' import type { diff --git a/packages/db/tests/query/optimizer-semantics-oracle.test.ts b/packages/db/tests/query/optimizer-semantics-oracle.test.ts index 7d53c68b7b..1b66ea04fd 100644 --- a/packages/db/tests/query/optimizer-semantics-oracle.test.ts +++ b/packages/db/tests/query/optimizer-semantics-oracle.test.ts @@ -22,8 +22,8 @@ * The nested matching cases also reject an all-empty QueryRef implementation. */ import { describe, expect, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { optimizeQuery } from '../../src/query/optimizer.js' import { add, diff --git a/packages/db/tests/query/optimizer.test.ts b/packages/db/tests/query/optimizer.test.ts index 7545fd5e62..80ce1e91f6 100644 --- a/packages/db/tests/query/optimizer.test.ts +++ b/packages/db/tests/query/optimizer.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from 'vitest' +import { grouping } from '../../src/query/compiler/grouping.js' import { optimizeQuery } from '../../src/query/optimizer.js' import { Aggregate, @@ -45,7 +46,7 @@ function createOr(...args: Array) { } function createAgg(name: string, ...args: Array) { - return new Aggregate(name, args) + return new Aggregate(name, args, grouping) } describe(`Query Optimizer`, () => { diff --git a/packages/db/tests/query/optional-fields-runtime.test.ts b/packages/db/tests/query/optional-fields-runtime.test.ts index 3778eb7d8c..7bdcb7b838 100644 --- a/packages/db/tests/query/optional-fields-runtime.test.ts +++ b/packages/db/tests/query/optional-fields-runtime.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from 'vitest' -import { createLiveQueryCollection, eq } from '../../src/query/index.js' +import { + createLiveQueryCollection, + eq, + groupKeys, +} from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions } from '../utils.js' @@ -196,7 +200,7 @@ describe(`Optional Fields - Runtime Tests`, () => { query: (q) => q .from({ user: usersCollection }) - .groupBy(({ user }) => user.department_id) + .groupBy(groupKeys(({ user }) => user.department_id)) .select(({ user }) => ({ department_id: user.department_id, })), diff --git a/packages/db/tests/query/order-by.test.ts b/packages/db/tests/query/order-by.test.ts index ba55fbc209..9fba6e6e86 100644 --- a/packages/db/tests/query/order-by.test.ts +++ b/packages/db/tests/query/order-by.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it } from 'vitest' +import { groupKeys } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js' import { createLiveQueryCollection } from '../../src/query/live-query-collection.js' @@ -893,7 +894,7 @@ function createOrderByTests(autoIndex: `off` | `eager`): void { query: (qb) => qb .from({ vehicleDocuments: vehicleDocumentCollection }) - .groupBy((q) => q.vehicleDocuments.vin) + .groupBy(groupKeys((q) => q.vehicleDocuments.vin)) .orderBy((q) => q.vehicleDocuments.vin, `asc`) .select((q) => ({ vin: q.vehicleDocuments.vin, @@ -952,7 +953,7 @@ function createOrderByTests(autoIndex: `off` | `eager`): void { query: (qb) => qb .from({ vehicleDocuments: vehicleDocumentCollection }) - .groupBy((q) => q.vehicleDocuments.vin) + .groupBy(groupKeys((q) => q.vehicleDocuments.vin)) .orderBy((q) => max(q.vehicleDocuments.updatedAt), `desc`) .select((q) => ({ vin: q.vehicleDocuments.vin, diff --git a/packages/db/tests/query/ordered-lifecycle-oracle.property.test.ts b/packages/db/tests/query/ordered-lifecycle-oracle.property.test.ts index 281ffd4788..1ffa8c8234 100644 --- a/packages/db/tests/query/ordered-lifecycle-oracle.property.test.ts +++ b/packages/db/tests/query/ordered-lifecycle-oracle.property.test.ts @@ -1,9 +1,6 @@ import { isDeepStrictEqual } from 'node:util' import { describe, expect, it } from 'vitest' import { fc, test as fcTest } from '@fast-check/vitest' -import { createCollection } from '../../src/collection/index.js' -import { createDeferred } from '../../src/deferred.js' -import { BTreeIndex } from '../../src/indexes/btree-index.js' import { createLiveQueryCollection, eq, @@ -11,6 +8,9 @@ import { not, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { createDeferred } from '../../src/deferred.js' +import { BTreeIndex } from '../../src/indexes/btree-index.js' import { evaluateReferenceExpression } from '../reference-expression-oracle.js' import { captureMicrotaskErrors, flushPromises } from '../utils.js' import { diff --git a/packages/db/tests/query/ordered-work-oracle.property.test.ts b/packages/db/tests/query/ordered-work-oracle.property.test.ts index 8ef2cd6d1d..1c73f7bb73 100644 --- a/packages/db/tests/query/ordered-work-oracle.property.test.ts +++ b/packages/db/tests/query/ordered-work-oracle.property.test.ts @@ -1,5 +1,6 @@ import { fc, test as fcTest } from '@fast-check/vitest' import { describe, expect, it, vi } from 'vitest' +import { toArray } from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { localOnlyCollectionOptions } from '../../src/local-only.js' import { createDeferred } from '../../src/deferred.js' @@ -11,7 +12,6 @@ import { getLoadSubsetDemandKey } from '../../src/query/ir-stable-identity.js' import { DeduplicatedLoadSubset } from '../../src/query/subset-dedupe.js' import { Func, PropRef, Value } from '../../src/query/ir.js' import { createLiveQueryCollection } from '../../src/query/live-query-collection.js' -import { toArray } from '../../src/query/index.js' import { eq, gte, isUndefined, not } from '../../src/query/builder/functions.js' import { oracleRandomParameters, diff --git a/packages/db/tests/query/pagination-oracle.property.test.ts b/packages/db/tests/query/pagination-oracle.property.test.ts index 7db4b3a22f..c4d58cf271 100644 --- a/packages/db/tests/query/pagination-oracle.property.test.ts +++ b/packages/db/tests/query/pagination-oracle.property.test.ts @@ -1,8 +1,8 @@ import { fc, test as fcTest } from '@fast-check/vitest' import { describe, expect, it } from 'vitest' +import { BTreeIndex } from '../../src/index.js' import { createCollection } from '../../src/collection/index.js' import { createDeferred } from '../../src/deferred.js' -import { BTreeIndex } from '../../src/index.js' import { createLiveQueryCollection } from '../../src/query/live-query-collection.js' import { and, eq } from '../../src/query/builder/functions.js' import { PropRef } from '../../src/query/ir.js' diff --git a/packages/db/tests/query/pooled-live-query-gc-oracle.test.ts b/packages/db/tests/query/pooled-live-query-gc-oracle.test.ts index 583c5ef24f..2ed7d27e55 100644 --- a/packages/db/tests/query/pooled-live-query-gc-oracle.test.ts +++ b/packages/db/tests/query/pooled-live-query-gc-oracle.test.ts @@ -1,9 +1,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + createPooledLiveQuery, + pooledLiveQueries, +} from '../../src/query/pooled-live-query.js' import { CollectionImpl, createCollection } from '../../src/collection/index.js' import { createLiveQueryObserver } from '../../src/live-query-observer.js' import { Query } from '../../src/query/builder/index.js' import { createLiveQueryCollection, eq, not } from '../../src/query/index.js' -import { createPooledLiveQuery } from '../../src/query/pooled-live-query.js' import { resolveLiveQueryValue } from '../../src/live-query-options.js' import { mockSyncCollectionOptions } from '../utils.js' import type { Collection } from '../../src/collection/index.js' @@ -248,7 +251,9 @@ describe(`pooled live query gcTime`, () => { // A pooled view and a compiled one, each on its own source. const pooledOnOwn = makeSource() const pooledObserver = createLiveQueryObserver( - resolveLiveQueryValue(query(pooledOnOwn)(new Query())), + resolveLiveQueryValue(query(pooledOnOwn)(new Query()), { + pool: pooledLiveQueries, + }), { mode: `wholesale` }, ) pooledObserver.subscribe(() => {})() @@ -272,9 +277,12 @@ describe(`pooled live query gcTime`, () => { it(`pools a query config that names only its query`, () => { const source = makeSource() const observe = (value: unknown) => - createLiveQueryObserver(resolveLiveQueryValue(value, { gcTime: 1 }), { - mode: `wholesale`, - }).subscribe(() => {}) + createLiveQueryObserver( + resolveLiveQueryValue(value, { pool: pooledLiveQueries, gcTime: 1 }), + { + mode: `wholesale`, + }, + ).subscribe(() => {}) const stops = [ observe({ query: query(source)(new Query()) }), observe({ query: query(source)(new Query()), gcTime: 5 }), diff --git a/packages/db/tests/query/pooled-query-identity.test.ts b/packages/db/tests/query/pooled-query-identity.test.ts index aab60ccf61..53465ec079 100644 --- a/packages/db/tests/query/pooled-query-identity.test.ts +++ b/packages/db/tests/query/pooled-query-identity.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { pooledLiveQueries } from '../../src/query/pooled-live-query.js' import { createCollection } from '../../src/collection/index.js' import { getPreparedLiveQueryIdentity } from '../../src/live-query-options.js' import { Query } from '../../src/query/builder/index.js' @@ -23,7 +24,7 @@ const makeSource = () => }), ) const identity = (build: (q: any) => unknown) => - getPreparedLiveQueryIdentity(build(new Query())) + getPreparedLiveQueryIdentity(build(new Query()), pooledLiveQueries) describe(`pooled query identity`, () => { const source = makeSource() diff --git a/packages/db/tests/query/public-container-copy.test.ts b/packages/db/tests/query/public-container-copy.test.ts index e29c5942bf..c4d75776e9 100644 --- a/packages/db/tests/query/public-container-copy.test.ts +++ b/packages/db/tests/query/public-container-copy.test.ts @@ -1,11 +1,12 @@ import { expect, it } from 'vitest' -import { transformPublicContainers } from '../../src/query/compiler/route-metadata.js' import { createLiveQueryCollection, eq, + include, materialize, toArray, } from '../../src/query/index.js' +import { transformPublicContainers } from '../../src/query/compiler/route-metadata.js' import { createControlledCollection } from './includes-oracle-helpers.js' it.each( @@ -43,7 +44,7 @@ it.each( .select(({ inner }) => ({ id: inner.id })) return { id: parent.id, - collection: matches, + collection: include(matches), array: toArray(matches), materialized: materialize(matches), } diff --git a/packages/db/tests/query/query-api-type-algebra.test.ts b/packages/db/tests/query/query-api-type-algebra.test.ts index eb3025c8da..0d6b2b9c50 100644 --- a/packages/db/tests/query/query-api-type-algebra.test.ts +++ b/packages/db/tests/query/query-api-type-algebra.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection, eq } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions } from '../utils.js' test(`an unmatched whole-object projection publishes undefined`, () => { diff --git a/packages/db/tests/query/query-once.test.ts b/packages/db/tests/query/query-once.test.ts index 74efc105e0..4d668db501 100644 --- a/packages/db/tests/query/query-once.test.ts +++ b/packages/db/tests/query/query-once.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { CollectionImpl, createCollection } from '../../src/collection/index.js' import { eq, queryOnce } from '../../src/query/index.js' +import { CollectionImpl, createCollection } from '../../src/collection/index.js' import { Query } from '../../src/query/builder/index.js' import { mockSyncCollectionOptions } from '../utils.js' diff --git a/packages/db/tests/query/replay-failure-boundary.test.ts b/packages/db/tests/query/replay-failure-boundary.test.ts index 54926265ca..11022d1e6c 100644 --- a/packages/db/tests/query/replay-failure-boundary.test.ts +++ b/packages/db/tests/query/replay-failure-boundary.test.ts @@ -1,8 +1,12 @@ import { describe, expect, it } from 'vitest' +import { + createLiveQueryCollection, + eq, + include, +} from '../../src/query/index.js' import { createCollection } from '../../src/collection' import { createDeferred } from '../../src/deferred' import { BasicIndex } from '../../src/indexes/basic-index' -import { createLiveQueryCollection, eq } from '../../src/query' import { PropRef } from '../../src/query/ir' import { evaluateReferenceExpression } from '../reference-expression-oracle' import { flushPromises } from '../utils' @@ -92,9 +96,11 @@ describe.each([`direct`, `query`] as const)( .select(({ row }) => ({ id: row.id, version: row.version, - children: q - .from({ child: children }) - .where(({ child }) => eq(child.id, row.id)), + children: include( + q + .from({ child: children }) + .where(({ child }) => eq(child.id, row.id)), + ), })), ) const live = consumer === `query` ? makeLive() : undefined diff --git a/packages/db/tests/query/scheduler.test.ts b/packages/db/tests/query/scheduler.test.ts index 3d51a72dd5..cea6cfba98 100644 --- a/packages/db/tests/query/scheduler.test.ts +++ b/packages/db/tests/query/scheduler.test.ts @@ -1,7 +1,12 @@ import { afterEach, describe, expect, it, vi } from 'vitest' +import { + createLiveQueryCollection, + eq, + include, + isNull, +} from '../../src/query/index.js' import { createCollection } from '../../src/collection/index.js' import { createDeferred } from '../../src/deferred.js' -import { createLiveQueryCollection, eq, isNull } from '../../src/query/index.js' import { createTransaction } from '../../src/transactions.js' import { createOptimisticAction } from '../../src/optimistic-action.js' import { @@ -1629,12 +1634,16 @@ describe(`live query scheduler`, () => { query: (q) => q.from({ root: firstSource }).select(({ root }) => ({ id: root.id, - second: q - .from({ item: secondSource }) - .where(({ item }) => eq(item.id, root.id)), - third: q - .from({ item: thirdSource }) - .where(({ item }) => eq(item.id, root.id)), + second: include( + q + .from({ item: secondSource }) + .where(({ item }) => eq(item.id, root.id)), + ), + third: include( + q + .from({ item: thirdSource }) + .where(({ item }) => eq(item.id, root.id)), + ), })), }) type BuilderSyncConfig = Parameters< diff --git a/packages/db/tests/query/select-spread.test.ts b/packages/db/tests/query/select-spread.test.ts index a902d52ff9..6efdfa92fe 100644 --- a/packages/db/tests/query/select-spread.test.ts +++ b/packages/db/tests/query/select-spread.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions, stripVirtualProps } from '../utils.js' import { add, eq, upper } from '../../src/query/builder/functions.js' diff --git a/packages/db/tests/query/subquery-user-value-oracle.test.ts b/packages/db/tests/query/subquery-user-value-oracle.test.ts index 5dc44ea6c4..9b27a203fe 100644 --- a/packages/db/tests/query/subquery-user-value-oracle.test.ts +++ b/packages/db/tests/query/subquery-user-value-oracle.test.ts @@ -1,14 +1,14 @@ import { D2, MultiSet, output } from '@tanstack/db-ivm' import { describe, expect, it } from 'vitest' -import { buildQuery } from '../../src/query/builder/index.js' -import { compileQuery } from '../../src/query/compiler/index.js' -import { collectCollectionSources } from '../../src/query/ir.js' import { count, createLiveQueryCollection, eq, materialize, } from '../../src/query/index.js' +import { buildQuery } from '../../src/query/builder/index.js' +import { compileQuery } from '../../src/query/compiler/index.js' +import { collectCollectionSources } from '../../src/query/ir.js' import { withHistoryCleanup } from '../optimistic-history-oracle.js' import { stripVirtualProps } from '../utils.js' import { createControlledCollection } from './includes-oracle-helpers.js' diff --git a/packages/db/tests/query/subset-error-matrix.test.ts b/packages/db/tests/query/subset-error-matrix.test.ts index 8872aad5bc..0f803f1660 100644 --- a/packages/db/tests/query/subset-error-matrix.test.ts +++ b/packages/db/tests/query/subset-error-matrix.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' +import { createEffect, createLiveQueryCollection, eq } from '../../src/index.js' import { createCollection } from '../../src/collection/index.js' import { BTreeIndex } from '../../src/indexes/btree-index.js' -import { createEffect, createLiveQueryCollection, eq } from '../../src/index.js' import { getLoadSubsetDemandKey } from '../../src/query/ir-stable-identity.js' import { captureMicrotaskErrors, mockSyncCollectionOptions } from '../utils.js' diff --git a/packages/db/tests/query/union-all.test-d.ts b/packages/db/tests/query/union-all.test-d.ts index 16001ef210..903b0d7480 100644 --- a/packages/db/tests/query/union-all.test-d.ts +++ b/packages/db/tests/query/union-all.test-d.ts @@ -1,5 +1,4 @@ import { describe, expectTypeOf, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { coalesce, count, @@ -7,6 +6,7 @@ import { eq, gt, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions } from '../utils.js' import type { OutputWithVirtual } from '../utils.js' import type { diff --git a/packages/db/tests/query/union-all.test.ts b/packages/db/tests/query/union-all.test.ts index ce5f0faf51..fd104e6004 100644 --- a/packages/db/tests/query/union-all.test.ts +++ b/packages/db/tests/query/union-all.test.ts @@ -1,6 +1,4 @@ import { describe, expect, it } from 'vitest' -import { createCollection } from '../../src/collection/index.js' -import { BTreeIndex } from '../../src/indexes/btree-index.js' import { caseWhen, coalesce, @@ -9,8 +7,11 @@ import { createLiveQueryCollection, eq, gt, + include, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' +import { BTreeIndex } from '../../src/indexes/btree-index.js' import { flushPromises, mockSyncCollectionOptions, @@ -1588,14 +1589,16 @@ describe(`unionAll`, () => { id: user.id, profile: caseWhen(user.id, { id: user.id, - items: q - .from({ item: events }) - .where(({ item }) => eq(item.userId, user.id)) - .orderBy(({ item }) => item.timestamp) - .select(({ item }) => ({ - label: item.label, - timestamp: item.timestamp, - })), + items: include( + q + .from({ item: events }) + .where(({ item }) => eq(item.userId, user.id)) + .orderBy(({ item }) => item.timestamp) + .select(({ item }) => ({ + label: item.label, + timestamp: item.timestamp, + })), + ), }), })) .orderBy(({ user }) => user.id) @@ -1697,23 +1700,25 @@ describe(`unionAll`, () => { key: run.key, order: run.order, status: run.status, - items: q - .from({ item: runItemsSource }) - .where(({ item }) => eq(item.runId, run.key)) - .orderBy(({ item }) => item.order) - .select(({ item }) => ({ - text: caseWhen(item.text.key, { - key: item.text.key, - runId: item.text.runId, - order: item.text.order, - status: item.text.status, - content: item.textContent, - }), - inactiveText: caseWhen(item.toolCall.key, { - content: item.textContent, - }), - toolCall: item.toolCall, - })), + items: include( + q + .from({ item: runItemsSource }) + .where(({ item }) => eq(item.runId, run.key)) + .orderBy(({ item }) => item.order) + .select(({ item }) => ({ + text: caseWhen(item.text.key, { + key: item.text.key, + runId: item.text.runId, + order: item.text.order, + status: item.text.status, + content: item.textContent, + }), + inactiveText: caseWhen(item.toolCall.key, { + content: item.textContent, + }), + toolCall: item.toolCall, + })), + ), }), })) }) diff --git a/packages/db/tests/query/virtual-row-fields-oracle.test-d.ts b/packages/db/tests/query/virtual-row-fields-oracle.test-d.ts index 135bf61c33..c204c99901 100644 --- a/packages/db/tests/query/virtual-row-fields-oracle.test-d.ts +++ b/packages/db/tests/query/virtual-row-fields-oracle.test-d.ts @@ -34,13 +34,13 @@ * projected-value cells. */ import { describe, expectTypeOf, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection, eq, materialize, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { mockSyncCollectionOptions } from '../utils.js' import type { RefLeaf } from '../../src/query/builder/types.js' import type { SingleRowRefProxy } from '../../src/query/builder/ref-proxy.js' diff --git a/packages/db/tests/query/virtual-row-fields-oracle.test.ts b/packages/db/tests/query/virtual-row-fields-oracle.test.ts index 93b54f08ed..796f93f36c 100644 --- a/packages/db/tests/query/virtual-row-fields-oracle.test.ts +++ b/packages/db/tests/query/virtual-row-fields-oracle.test.ts @@ -33,13 +33,13 @@ * when Collection cleanup also fails and attempts every cleanup. */ import { describe, expect, test } from 'vitest' -import { createCollection } from '../../src/collection/index.js' import { createLiveQueryCollection, eq, materialize, toArray, } from '../../src/query/index.js' +import { createCollection } from '../../src/collection/index.js' import { hasVirtualProps } from '../../src/virtual-props.js' import { mockSyncCollectionOptions } from '../utils.js' diff --git a/packages/preact-db/src/useLiveQuery.ts b/packages/preact-db/src/useLiveQuery.ts index a2936ba844..171ffea3d0 100644 --- a/packages/preact-db/src/useLiveQuery.ts +++ b/packages/preact-db/src/useLiveQuery.ts @@ -22,6 +22,7 @@ import type { LiveQueryPersistedStatus, LiveQuerySourceIdClaims, NonSingleResult, + PooledLiveQueries, QueryBuilder, SingleResult, } from '@tanstack/db' @@ -47,6 +48,11 @@ type UseLiveQueryConfigOptions = Omit< * with functional variants. Structured queries should omit it. */ queryKey?: LiveQueryKey + /** + * Pass `pooledLiveQueries` to share one partition among equality-filtered + * queries of one shape. + */ + pool?: PooledLiveQueries } export type UseLiveQueryConfig = diff --git a/packages/preact-db/tests/conformance.test.ts b/packages/preact-db/tests/conformance.test.ts index a1c54161d4..4bd7ca13ce 100644 --- a/packages/preact-db/tests/conformance.test.ts +++ b/packages/preact-db/tests/conformance.test.ts @@ -24,6 +24,7 @@ import { createLiveQueryCollection, createOptimisticAction, eq, + groupKeys, gt, sum, } from '@tanstack/db' @@ -38,6 +39,7 @@ import { makeLaggingSource } from '../../db/tests/conformance/lagging-source' import { expectResultSurface } from '../../db/tests/conformance/result-laws' import { renderHook, settle } from './render-probe' import type { MountedHook } from './render-probe' +import type { PooledLiveQueries } from '@tanstack/db' import type { ConformanceResult, ControllableHandle, @@ -183,7 +185,11 @@ function mountCollection(collection: any) { function mountConfig( build: QueryBuild, - opts?: { gcTime?: number; queryKey?: ReadonlyArray }, + opts?: { + gcTime?: number + pool?: PooledLiveQueries + queryKey?: ReadonlyArray + }, ) { return makeHandle( renderHook(() => useLiveQuery({ query: build as any, ...opts })), @@ -198,19 +204,25 @@ function mountDisabled() { function mountControllable

( build: (q: any, param: P) => any, initial: P, + opts?: { pool?: PooledLiveQueries }, ): ControllableHandle

{ const published = createPublicationLog() const hook = renderHook( - ({ param }: { param: P }) => useLiveQuery((q: any) => build(q, param)), + ({ param }: { param: P }) => + opts?.pool !== undefined + ? // The config form derives identity from the query on each render. + useLiveQuery({ query: (q: any) => build(q, param), pool: opts.pool }) + : useLiveQuery((q: any) => build(q, param)), { initialProps: { param: initial }, - onCommit: (result) => published.record(result.data), + onCommit: (result) => published.record(result.data, result.state), }, ) const handle = makeHandle(hook) return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(param: P) { hook.rerender({ param }) await settle() @@ -234,13 +246,14 @@ function mountKeyedControllable

( } as any), { initialProps: { param: initial }, - onCommit: (result) => published.record(result.data), + onCommit: (result) => published.record(result.data, result.state), }, ) const handle = makeHandle(hook) return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(param: P) { hook.rerender({ param }) await settle() @@ -253,7 +266,7 @@ const preactDriver: LiveQueryDriver = { // Each render re-runs the query input and compares its identity. comparesQueryIdentity: true, disabledRepresentation: `absent`, - ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + ops: { eq, gt, count, sum, coalesce, groupKeys, createOptimisticAction }, makeSource, makeDeferredSource, makeLaggingSource: () => diff --git a/packages/query-db-collection/tests/includes-work-counter-oracle.test.ts b/packages/query-db-collection/tests/includes-work-counter-oracle.test.ts index a36d8b9925..0055b1b4fa 100644 --- a/packages/query-db-collection/tests/includes-work-counter-oracle.test.ts +++ b/packages/query-db-collection/tests/includes-work-counter-oracle.test.ts @@ -1,10 +1,11 @@ -import { QueryClient } from '@tanstack/query-core' import { BasicIndex, createCollection, createLiveQueryCollection, eq, + include, } from '@tanstack/db' +import { QueryClient } from '@tanstack/query-core' import { describe, expect, it } from 'vitest' import { evaluateReferenceExpression } from '../../db/tests/reference-expression-oracle' import { queryCollectionOptions } from '../src/query' @@ -363,27 +364,33 @@ async function checkNestedTreeShape( q.from({ root: sources.roots }).select(({ root }) => ({ id: root.id, value: root.value, - children: q - .from({ branch: sources.branches }) - .where(({ branch }) => eq(branch.parentId, root.id)) - .select(({ branch }) => ({ - id: branch.id, - value: branch.value, - children: q - .from({ twig: sources.twigs }) - .where(({ twig }) => eq(twig.parentId, branch.id)) - .select(({ twig }) => ({ - id: twig.id, - value: twig.value, - children: q - .from({ leaf: sources.leaves }) - .where(({ leaf }) => eq(leaf.parentId, twig.id)) - .select(({ leaf }) => ({ - id: leaf.id, - value: leaf.value, + children: include( + q + .from({ branch: sources.branches }) + .where(({ branch }) => eq(branch.parentId, root.id)) + .select(({ branch }) => ({ + id: branch.id, + value: branch.value, + children: include( + q + .from({ twig: sources.twigs }) + .where(({ twig }) => eq(twig.parentId, branch.id)) + .select(({ twig }) => ({ + id: twig.id, + value: twig.value, + children: include( + q + .from({ leaf: sources.leaves }) + .where(({ leaf }) => eq(leaf.parentId, twig.id)) + .select(({ leaf }) => ({ + id: leaf.id, + value: leaf.value, + })), + ), })), - })), - })), + ), + })), + ), })), }) rootCollection = roots diff --git a/packages/query-db-collection/tests/load-subset-lifecycle-oracle.test.ts b/packages/query-db-collection/tests/load-subset-lifecycle-oracle.test.ts index 1eef409062..f5cd7e90e5 100644 --- a/packages/query-db-collection/tests/load-subset-lifecycle-oracle.test.ts +++ b/packages/query-db-collection/tests/load-subset-lifecycle-oracle.test.ts @@ -1,4 +1,3 @@ -import { QueryClient } from '@tanstack/query-core' import { BasicIndex, IR, @@ -7,6 +6,7 @@ import { eq, getLoadSubsetDemandKey, } from '@tanstack/db' +import { QueryClient } from '@tanstack/query-core' import { describe, expect, it, vi } from 'vitest' import { TraceAssertionError } from '../../db/tests/trace-runner.js' import { createDeferred } from '../../db/src/deferred.js' diff --git a/packages/query-db-collection/tests/query.test.ts b/packages/query-db-collection/tests/query.test.ts index f4672f9d86..a9575b0347 100644 --- a/packages/query-db-collection/tests/query.test.ts +++ b/packages/query-db-collection/tests/query.test.ts @@ -1,12 +1,3 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - QueryClient, - QueryObserver, - dehydrate, - focusManager, - hashKey, - onlineManager, -} from '@tanstack/query-core' import { BTreeIndex, DbClient, @@ -18,9 +9,19 @@ import { eq, ilike, inArray, + include, or, resetWarnings, } from '@tanstack/db' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + QueryClient, + QueryObserver, + dehydrate, + focusManager, + hashKey, + onlineManager, +} from '@tanstack/query-core' import { mockSyncCollectionOptions, stripVirtualProps, @@ -1385,14 +1386,16 @@ describe(`QueryCollection`, () => { const collection = createLiveQueryCollection((q) => q.from({ lineItem: lineItems }).select(({ lineItem }) => ({ id: lineItem.id, - product: q - .from({ product: products }) - .where(({ product }) => eq(product.id, lineItem.productId)) - .select(({ product }) => ({ - id: product.id, - categoryId: product.categoryId, - name: product.name, - })), + product: include( + q + .from({ product: products }) + .where(({ product }) => eq(product.id, lineItem.productId)) + .select(({ product }) => ({ + id: product.id, + categoryId: product.categoryId, + name: product.name, + })), + ), })), ) @@ -1465,13 +1468,15 @@ describe(`QueryCollection`, () => { const live = createLiveQueryCollection((q) => q.from({ lineItem: lineItems }).select(({ lineItem }) => ({ id: lineItem.id, - product: q - .from({ product: products }) - .where(({ product }) => eq(product.id, lineItem.productId)) - .select(({ product }) => ({ - id: product.id, - name: product.name, - })), + product: include( + q + .from({ product: products }) + .where(({ product }) => eq(product.id, lineItem.productId)) + .select(({ product }) => ({ + id: product.id, + name: product.name, + })), + ), })), ) diff --git a/packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts b/packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts index 2adf895b7f..62205e37d2 100644 --- a/packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts +++ b/packages/query-db-collection/tests/refetch-ordered-prefix-oracle.test.ts @@ -1,4 +1,3 @@ -import { QueryClient } from '@tanstack/query-core' import { BTreeIndex, IR, @@ -9,6 +8,7 @@ import { isUndefined, not, } from '@tanstack/db' +import { QueryClient } from '@tanstack/query-core' import fc from 'fast-check' import { describe, expect, it, vi } from 'vitest' import { diff --git a/packages/react-db/skills/react-db/SKILL.md b/packages/react-db/skills/react-db/SKILL.md index 1d05c237e0..f5ec011138 100644 --- a/packages/react-db/skills/react-db/SKILL.md +++ b/packages/react-db/skills/react-db/SKILL.md @@ -100,6 +100,8 @@ const { data, status } = useLiveQuery({ // When disabled: status='disabled', data=undefined ``` +Many queries of one shape, such as one per list row, can share one partition. Pass `pool: pooledLiveQueries` (from `@tanstack/db`) in the config form: `useLiveQuery({ query, pool: pooledLiveQueries })`. Pooling applies to queries that filter one eager source with `eq(field, literal)`. It is off by default, and Suspense and `DbClient` queries never pool. + ### useLiveSuspenseQuery ```tsx @@ -171,7 +173,7 @@ const mutate = usePacedMutations({ ## Includes (Hierarchical Data) -When a query uses includes (subqueries in `select`), each child field is a live `Collection` by default. Subscribe to it with `useLiveQuery` in a subcomponent: +When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `useLiveQuery` in a subcomponent: ```tsx function ProjectList() { @@ -180,10 +182,12 @@ function ProjectList() { q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issuesCollection }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ id: i.id, title: i.title })), + issues: include( + q + .from({ i: issuesCollection }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), })), }) diff --git a/packages/react-db/src/useLiveQuery.ts b/packages/react-db/src/useLiveQuery.ts index d1dc7697f3..6297f72ac2 100644 --- a/packages/react-db/src/useLiveQuery.ts +++ b/packages/react-db/src/useLiveQuery.ts @@ -41,6 +41,7 @@ import type { LiveQueryPersistedStatus, LiveQuerySourceIdClaims, NonSingleResult, + PooledLiveQueries, QueryBuilder, SingleResult, } from '@tanstack/db' @@ -125,6 +126,11 @@ type UseLiveQueryConfigOptions = Omit< queryKey?: LiveQueryKey /** Override the nearest DbProvider for this query. */ client?: DbClient + /** + * Pass `pooledLiveQueries` to share one partition among equality-filtered + * queries of one shape. Suspense and DbClient queries never pool. + */ + pool?: PooledLiveQueries } type ConfiguredQueryBuilder = Extract< @@ -941,8 +947,9 @@ function useLiveQueryImpl( gcTime: forSuspense ? DEFAULT_SUSPENSE_GC_TIME_MS : DEFAULT_GC_TIME_MS, - // Hydration and Suspense key the live-query Collection by identity. - pool: !forSuspense && !dbClient, + // Hydration and Suspense key the live-query Collection by identity, + // so they never pool, even when the config passes a pool. + pool: forSuspense || dbClient ? false : undefined, // Without a DbClient, Suspense starts it through a preload below. startSync: !forSuspense || dbClient !== undefined, }) as SuspenseCollection | null diff --git a/packages/react-db/tests/conformance.test.tsx b/packages/react-db/tests/conformance.test.tsx index 7dc0e7bff5..575e898332 100644 --- a/packages/react-db/tests/conformance.test.tsx +++ b/packages/react-db/tests/conformance.test.tsx @@ -16,6 +16,7 @@ import { createLiveQueryCollection, createOptimisticAction, eq, + groupKeys, gt, sum, } from '@tanstack/db' @@ -31,6 +32,7 @@ import { makeLaggingSource } from '../../db/tests/conformance/lagging-source' import { expectResultSurface } from '../../db/tests/conformance/result-laws' import { rejectReadsAfterRenderError } from './rejected-binding' import type { RenderHookResult } from '@testing-library/react' +import type { PooledLiveQueries } from '@tanstack/db' import type { ConformanceResult, ControllableHandle, @@ -142,7 +144,10 @@ function mountCollection(collection: any) { return makeHandle(hook) } -function mountConfig(build: QueryBuild, opts?: { gcTime?: number }) { +function mountConfig( + build: QueryBuild, + opts?: { gcTime?: number; pool?: PooledLiveQueries }, +) { const hook = renderHook(() => useLiveQuery({ query: build as any, ...opts })) return makeHandle(hook) } @@ -156,7 +161,7 @@ function mountWithDependencyList(build: QueryBuild) { function mountConfigWithDependencyList( build: QueryBuild, - opts?: { gcTime?: number }, + opts?: { gcTime?: number; pool?: PooledLiveQueries }, ) { const hook = renderHook(() => useLiveQuery({ query: build as any, ...opts } as any, []), @@ -180,18 +185,25 @@ const mountControllableWith = (path: IdentityPath) => function mountControllable

( build: (q: any, param: P) => any, initial: P, + opts?: { pool?: PooledLiveQueries }, ): ControllableHandle

{ const published = createPublicationLog() const hook = renderHook( ({ param }: { param: P }) => { + const query = (q: any) => build(q, param) + // A pooled query uses the config form, on either identity path. const result = - path === `derived` - ? // The callback captures param, so its IR identity changes with it. - useLiveQuery((q: any) => build(q, param)) - : useLiveQuery((q: any) => build(q, param), [param]) + opts?.pool !== undefined + ? path === `derived` + ? useLiveQuery({ query, pool: opts.pool }) + : useLiveQuery({ query, pool: opts.pool }, [param]) + : path === `derived` + ? // The callback captures param, so its IR identity changes with it. + useLiveQuery(query) + : useLiveQuery(query, [param]) // A layout effect records each committed value. useLayoutEffect(() => { - published.record(result.data) + published.record(result.data, result.state) }) return result }, @@ -202,6 +214,7 @@ const mountControllableWith = (path: IdentityPath) => return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(param: P) { await guard(() => act(async () => { @@ -269,7 +282,7 @@ const mountKeyedControllableWith = (path: IdentityPath) => ? useLiveQuery(config) : useLiveQuery(config, [param]) useLayoutEffect(() => { - published.record(result.data) + published.record(result.data, result.state) }) return result }, @@ -280,6 +293,7 @@ const mountKeyedControllableWith = (path: IdentityPath) => return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(param: P) { await guard(() => act(async () => { @@ -296,7 +310,7 @@ const reactDriver: LiveQueryDriver = { // Each render re-runs the query input and compares its identity. comparesQueryIdentity: true, disabledRepresentation: `absent`, - ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + ops: { eq, gt, count, sum, coalesce, groupKeys, createOptimisticAction }, makeSource, makeDeferredSource, makeLaggingSource: () => diff --git a/packages/react-db/tests/descriptor-query-binding-oracle.test.tsx b/packages/react-db/tests/descriptor-query-binding-oracle.test.tsx index 5042bef2d4..c214bd3f01 100644 --- a/packages/react-db/tests/descriptor-query-binding-oracle.test.tsx +++ b/packages/react-db/tests/descriptor-query-binding-oracle.test.tsx @@ -82,7 +82,6 @@ * start the source even though the earlier render never committed. These * histories do not cover arbitrary concurrent render schedules. */ -import { act, render, renderHook, waitFor } from '@testing-library/react' import { DbClient, Query, @@ -94,6 +93,7 @@ import { prepareLiveQueryValue, toArray, } from '@tanstack/db' +import { act, render, renderHook, waitFor } from '@testing-library/react' import { describe, expect, it } from 'vitest' import { Suspense } from 'react' import { DbProvider } from '../src/DbProvider' diff --git a/packages/react-db/tests/source-id-reuse-oracle.test.tsx b/packages/react-db/tests/source-id-reuse-oracle.test.tsx index a08c9131b2..66780035c4 100644 --- a/packages/react-db/tests/source-id-reuse-oracle.test.tsx +++ b/packages/react-db/tests/source-id-reuse-oracle.test.tsx @@ -40,7 +40,6 @@ * below runs dependency-list hook paths too. This does not assert a * process-wide Collection registry or other explicit-key semantics. */ -import { act, render, renderHook, waitFor } from '@testing-library/react' import { DbClient, SourceCollectionIdReuseError, @@ -49,6 +48,7 @@ import { eq, gt, } from '@tanstack/db' +import { act, render, renderHook, waitFor } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' import { Component, Suspense } from 'react' import { DbProvider } from '../src/DbProvider' diff --git a/packages/react-db/tests/useLiveQuery.test.tsx b/packages/react-db/tests/useLiveQuery.test.tsx index 4712782491..f1c2d55fea 100644 --- a/packages/react-db/tests/useLiveQuery.test.tsx +++ b/packages/react-db/tests/useLiveQuery.test.tsx @@ -1,5 +1,3 @@ -import { describe, expect, it, vi } from 'vitest' -import { act, renderHook, waitFor } from '@testing-library/react' import { DbClient, Query, @@ -12,11 +10,14 @@ import { createOptimisticAction, eq, gt, + include, localOnlyCollectionOptions, lte, sum, toArray, } from '@tanstack/db' +import { describe, expect, it, vi } from 'vitest' +import { act, renderHook, waitFor } from '@testing-library/react' import { useEffect } from 'react' import { useLiveQuery, useLiveQueryForSuspense } from '../src/useLiveQuery' import { getLiveQueryResultInfo } from '../src/live-query-internals' @@ -2762,13 +2763,15 @@ describe(`Query Collections`, () => { q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issuesCollection }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ - id: i.id, - title: i.title, - })), + issues: include( + q + .from({ i: issuesCollection }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ + id: i.id, + title: i.title, + })), + ), })), ) }) diff --git a/packages/solid-db/skills/solid-db/SKILL.md b/packages/solid-db/skills/solid-db/SKILL.md index 0039c6cc89..f1feccf4e8 100644 --- a/packages/solid-db/skills/solid-db/SKILL.md +++ b/packages/solid-db/skills/solid-db/SKILL.md @@ -145,7 +145,7 @@ through the resource, so do not rely on reading `isError` after failure. ## Includes (Hierarchical Data) -When a query uses includes (subqueries in `select`), each child field is a live `Collection` by default. Subscribe to it with `useLiveQuery` in a subcomponent: +When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `useLiveQuery` in a subcomponent: ```tsx function ProjectList() { @@ -153,10 +153,12 @@ function ProjectList() { q.from({ p: projectsCollection }).select(({ p }) => ({ id: p.id, name: p.name, - issues: q - .from({ i: issuesCollection }) - .where(({ i }) => eq(i.projectId, p.id)) - .select(({ i }) => ({ id: i.id, title: i.title })), + issues: include( + q + .from({ i: issuesCollection }) + .where(({ i }) => eq(i.projectId, p.id)) + .select(({ i }) => ({ id: i.id, title: i.title })), + ), })), ) diff --git a/packages/solid-db/src/useLiveQuery.ts b/packages/solid-db/src/useLiveQuery.ts index 3430789030..f8e8d7b512 100644 --- a/packages/solid-db/src/useLiveQuery.ts +++ b/packages/solid-db/src/useLiveQuery.ts @@ -25,7 +25,7 @@ import type { GetResult, InferResultType, InitialQueryBuilder, - LiveQueryCollectionConfig, + LiveQueryHookConfig, LiveQueryObserver, LiveQueryPersistedStatus, NonSingleResult, @@ -197,7 +197,7 @@ export function useLiveQuery( */ // Overload 2: Accept config object export function useLiveQuery( - config: Accessor>, + config: Accessor>, ): Accessor> & { /** * @deprecated use function result instead diff --git a/packages/solid-db/tests/conformance.test.tsx b/packages/solid-db/tests/conformance.test.tsx index 4bc6c570b9..bd789dab8e 100644 --- a/packages/solid-db/tests/conformance.test.tsx +++ b/packages/solid-db/tests/conformance.test.tsx @@ -17,6 +17,7 @@ import { createLiveQueryCollection, createOptimisticAction, eq, + groupKeys, gt, sum, } from '@tanstack/db' @@ -36,6 +37,7 @@ import { createPublicationLog } from '../../db/tests/conformance/publication-log import { makeLaggingSource } from '../../db/tests/conformance/lagging-source' import { expectResultSurface } from '../../db/tests/conformance/result-laws' import { withScopeSetup } from '../../db/tests/conformance/scope-setup' +import type { PooledLiveQueries } from '@tanstack/db' import type { ConformanceResult, ControllableHandle, @@ -197,7 +199,10 @@ function mountCollection(collection: any) { return makeHandle(getResult, dispose) } -function mountConfig(build: QueryBuild, opts?: { gcTime?: number }) { +function mountConfig( + build: QueryBuild, + opts?: { gcTime?: number; pool?: PooledLiveQueries }, +) { // Solid accepts the config-object form via an accessor. const { getResult, dispose } = inRoot(() => useLiveQuery(() => ({ query: build, ...opts })), @@ -214,25 +219,34 @@ function mountDisabled() { function mountControllable

( build: (q: any, param: P) => any, initial: P, + opts?: { pool?: PooledLiveQueries }, ): ControllableHandle

{ const [param, setParam] = createSignal

(initial) const published = createPublicationLog() const { getResult, dispose } = inRoot(() => { - // Reading param() inside the query fn makes Solid recompute on change. - const query = useLiveQuery((q: any) => build(q, param())) + const query = + opts?.pool !== undefined + ? // Reading param() in the config accessor makes Solid recompute on change. + useLiveQuery(() => { + const value = param() + return { query: (q: any) => build(q, value), pool: opts.pool } + }) + : // Reading param() inside the query fn makes Solid recompute on change. + useLiveQuery((q: any) => build(q, param())) // Solid renders the value each render effect reads. - createRenderEffect(() => published.record(query.data)) + createRenderEffect(() => published.record(query.data, query.state)) return query }) const handle = makeHandle(getResult, dispose) return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(next: P) { setParam(() => next) // The first paint after the change: the framework ran its reactive // work synchronously, but no later task has run. - published.record(getResult().data) + published.record(getResult().data, getResult().state) await settle() }, } @@ -243,7 +257,7 @@ const solidDriver: LiveQueryDriver = { // Reactive reads decide when the query compiles again. comparesQueryIdentity: false, disabledRepresentation: `empty-reactive`, - ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + ops: { eq, gt, count, sum, coalesce, groupKeys, createOptimisticAction }, makeSource, makeDeferredSource, makeLaggingSource: () => diff --git a/packages/solid-db/tests/useLiveQuery.test.tsx b/packages/solid-db/tests/useLiveQuery.test.tsx index 85f90c60dd..743b91afcf 100644 --- a/packages/solid-db/tests/useLiveQuery.test.tsx +++ b/packages/solid-db/tests/useLiveQuery.test.tsx @@ -1,5 +1,3 @@ -import { describe, expect, it, vi } from 'vitest' -import { render, renderHook, waitFor } from '@solidjs/testing-library' import { Query, count, @@ -8,9 +6,12 @@ import { createOptimisticAction, eq, gt, + include, toArray, waitForPersistedInitialRender, } from '@tanstack/db' +import { describe, expect, it, vi } from 'vitest' +import { render, renderHook, waitFor } from '@solidjs/testing-library' import { For, Suspense, @@ -3468,14 +3469,16 @@ describe(`includes subqueries`, () => { useLiveQuery((q) => q.from({ project: projects }).select(({ project }) => ({ id: project.id, - issues: q - .from({ issue: issues }) - .where(({ issue }) => eq(issue.projectId, project.id)) - .select(({ issue }) => ({ - id: issue.id, - projectId: issue.projectId, - title: issue.title, - })), + issues: include( + q + .from({ issue: issues }) + .where(({ issue }) => eq(issue.projectId, project.id)) + .select(({ issue }) => ({ + id: issue.id, + projectId: issue.projectId, + title: issue.title, + })), + ), })), ), ) diff --git a/packages/svelte-db/skills/svelte-db/SKILL.md b/packages/svelte-db/skills/svelte-db/SKILL.md index 295103a302..ac2ababcba 100644 --- a/packages/svelte-db/skills/svelte-db/SKILL.md +++ b/packages/svelte-db/skills/svelte-db/SKILL.md @@ -113,22 +113,24 @@ const { data, isLoading } = $derived(query) ## Includes (Hierarchical Data) -When a query uses includes (subqueries in `select`), each child field is a live `Collection` by default. Subscribe to it with `useLiveQuery` in a subcomponent: +When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `useLiveQuery` in a subcomponent: ```svelte diff --git a/packages/svelte-db/src/useLiveQuery.svelte.ts b/packages/svelte-db/src/useLiveQuery.svelte.ts index c79776a51d..76f95b7389 100644 --- a/packages/svelte-db/src/useLiveQuery.svelte.ts +++ b/packages/svelte-db/src/useLiveQuery.svelte.ts @@ -29,7 +29,7 @@ import type { GetResult, InferResultType, InitialQueryBuilder, - LiveQueryCollectionConfig, + LiveQueryHookConfig, LiveQueryKey, LiveQueryObserver, LiveQueryPersistedStatus, @@ -109,7 +109,7 @@ export interface UseLiveQueryReturnWithCollection< type MaybeGetter = T | (() => T) export type UseLiveQueryConfig = - LiveQueryCollectionConfig & { + LiveQueryHookConfig & { queryKey?: MaybeGetter client?: DbClient } @@ -457,10 +457,11 @@ export function useLiveQuery( } else if (isCollection(preparedValue)) { collection = preparedValue } else { - // Hydration keys the live-query Collection by identity. + // Hydration keys the live-query Collection by identity, so it never + // pools, even when the config passes a pool. collection = resolveLiveQueryValue(preparedValue, { gcTime: DEFAULT_GC_TIME_MS, - pool: !dbClient, + pool: dbClient ? false : undefined, }) } diff --git a/packages/svelte-db/tests/conformance.svelte.test.ts b/packages/svelte-db/tests/conformance.svelte.test.ts index 58ae6b9db6..126d6daaca 100644 --- a/packages/svelte-db/tests/conformance.svelte.test.ts +++ b/packages/svelte-db/tests/conformance.svelte.test.ts @@ -15,6 +15,7 @@ import { createLiveQueryCollection, createOptimisticAction, eq, + groupKeys, gt, sum, } from '@tanstack/db' @@ -29,6 +30,7 @@ import { runSuite } from '../../db/tests/conformance/suite' import { createPublicationLog } from '../../db/tests/conformance/publication-log' import { makeLaggingSource } from '../../db/tests/conformance/lagging-source' import { expectResultSurface } from '../../db/tests/conformance/result-laws' +import type { PooledLiveQueries } from '@tanstack/db' import type { ConformanceResult, ControllableHandle, @@ -180,7 +182,10 @@ function mountCollection(collection: any) { return makeHandle(() => query, dispose) } -function mountConfig(build: QueryBuild, opts?: { gcTime?: number }) { +function mountConfig( + build: QueryBuild, + opts?: { gcTime?: number; pool?: PooledLiveQueries }, +) { let query: any const dispose = $effect.root(() => { query = useLiveQuery({ query: build, ...opts } as any) @@ -200,27 +205,35 @@ function mountDisabled() { function mountControllable

( build: (q: any, param: P) => any, initial: P, + opts?: { pool?: PooledLiveQueries }, ): ControllableHandle

{ let param = $state(initial) let query: any const published = createPublicationLog() const dispose = $effect.root(() => { - query = useLiveQuery((q: any) => build(q, param), [() => param]) + query = + opts?.pool !== undefined + ? useLiveQuery( + { query: (q: any) => build(q, param), pool: opts.pool } as any, + [() => param], + ) + : useLiveQuery((q: any) => build(q, param), [() => param]) // Svelte renders the value each pre-effect run reads. $effect.pre(() => { - published.record(query.data) + published.record(query.data, query.state) }) }) const handle = makeHandle(() => query, dispose) return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(next: P) { param = next // The first paint after the change: the framework has flushed its // reactive work, but no later task has run. flushSync() - published.record(query.data) + published.record(query.data, query.state) await settle() }, } @@ -231,7 +244,7 @@ const svelteDriver: LiveQueryDriver = { // Reactive reads decide when the query compiles again. comparesQueryIdentity: false, disabledRepresentation: `empty-reactive`, - ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + ops: { eq, gt, count, sum, coalesce, groupKeys, createOptimisticAction }, makeSource, makeDeferredSource, makeLaggingSource: () => diff --git a/packages/svelte-db/tests/useLiveQuery.svelte.test.ts b/packages/svelte-db/tests/useLiveQuery.svelte.test.ts index 1dfd8694c1..01c0e07e5e 100644 --- a/packages/svelte-db/tests/useLiveQuery.svelte.test.ts +++ b/packages/svelte-db/tests/useLiveQuery.svelte.test.ts @@ -1,4 +1,3 @@ -import { afterEach, describe, expect, it } from 'vitest' import { BaseQueryBuilder, DbClient, @@ -10,6 +9,7 @@ import { getStableValueHash, gt, } from '@tanstack/db' +import { afterEach, describe, expect, it } from 'vitest' import { flushSync } from 'svelte' import { useLiveQuery } from '../src/useLiveQuery.svelte.js' import { mockSyncCollectionOptions } from '../../db/tests/utils' diff --git a/packages/vue-db/skills/vue-db/SKILL.md b/packages/vue-db/skills/vue-db/SKILL.md index 3779be9a5d..61dcbbaea8 100644 --- a/packages/vue-db/skills/vue-db/SKILL.md +++ b/packages/vue-db/skills/vue-db/SKILL.md @@ -113,21 +113,23 @@ const handleToggle = (id: number) => { ## Includes (Hierarchical Data) -When a query uses includes (subqueries in `select`), each child field is a live `Collection` by default. Subscribe to it with `useLiveQuery` in a subcomponent: +When a query uses `include()` for a subquery in `select`, each child field is a live `Collection`. Subscribe to it with `useLiveQuery` in a subcomponent: ```vue diff --git a/packages/vue-db/src/useLiveQuery.ts b/packages/vue-db/src/useLiveQuery.ts index e75db9be0e..19c9350de1 100644 --- a/packages/vue-db/src/useLiveQuery.ts +++ b/packages/vue-db/src/useLiveQuery.ts @@ -24,7 +24,7 @@ import type { GetResult, InferResultType, InitialQueryBuilder, - LiveQueryCollectionConfig, + LiveQueryHookConfig, LiveQueryObserver, LiveQueryPersistedStatus, NonSingleResult, @@ -221,7 +221,7 @@ export function useLiveQuery( */ // Overload 2: Accept config object export function useLiveQuery( - config: LiveQueryCollectionConfig, + config: LiveQueryHookConfig, deps?: Array>, ): UseLiveQueryReturn @@ -291,7 +291,7 @@ export function useLiveQuery( configOrQueryOrCollection: any, deps: Array> = [], ): UseLiveQueryReturn | UseLiveQueryReturnWithCollection { - const collection = computed(() => { + const resolveInput = () => { // First check if the original parameter might be a ref/getter // by seeing if toValue returns something different than the original // NOTE: Don't call toValue on functions - toValue treats functions as getters and calls them! @@ -349,6 +349,26 @@ export function useLiveQuery( gcTime: DEFAULT_GC_TIME_MS, }) } + } + // The input's live-query Collection, or the error resolving it threw. A + // Vue computed that throws serves its previous value on the next read, so + // this one never throws; each reader rethrows the error. + const resolved = computed( + (): { + collection?: ReturnType + error?: unknown + } => { + try { + return { collection: resolveInput() } + } catch (error) { + return { error } + } + }, + ) + const collection = computed(() => { + const { collection: current, error } = resolved.value + if (error !== undefined) throw error + return current }) // Reactive state that gets updated granularly through change events @@ -357,17 +377,12 @@ export function useLiveQuery( // Reactive data array that maintains sorted order const internalData = ref>([]) - // Computed wrapper for the data to match expected return type - // Returns single item for singleResult collections, array otherwise - const data = computed(() => { - const currentCollection = collection.value - if (!currentCollection) { - return internalData.value - } - return isSingleResultCollection(currentCollection) - ? internalData.value[0] - : internalData.value - }) + // Returns single item for singleResult collections, array otherwise. The + // effect below sets `singleResult`, so reading data never compiles a query. + const singleResult = ref(false) + const data = computed(() => + singleResult.value ? internalData.value[0] : internalData.value, + ) // Track collection status reactively const status = ref( @@ -390,28 +405,31 @@ export function useLiveQuery( // Watch for collection changes and subscribe to updates watchEffect((onInvalidate) => { - const currentCollection = collection.value - - // Tear down any previous observer. + // Tear down any previous observer and its rows first: a query that fails + // to compile throws below, and Vue's computed then serves the previous + // collection, whose rows must not stay visible. currentObserver?.dispose() currentObserver = null + state.clear() + internalData.value = [] + + const { collection: currentCollection, error } = resolved.value + if (error !== undefined) throw error + singleResult.value = + !!currentCollection && isSingleResultCollection(currentCollection) // Handle null collection (disabled query) if (!currentCollection) { status.value = `disabled` as const persistedStatus.value = `unavailable` persistedError.value = undefined - state.clear() - internalData.value = [] return } - const observer = createLiveQueryObserver(currentCollection) - currentObserver = observer - // Initial rows arrive as the observer's first delta (includeInitialState); // apply them and every subsequent delta granularly to the reactive map. - state.clear() + const observer = createLiveQueryObserver(currentCollection) + currentObserver = observer const unsubscribe = observer.subscribe( (changes: Array> | undefined) => { diff --git a/packages/vue-db/tests/conformance.test.ts b/packages/vue-db/tests/conformance.test.ts index 4bf9ec4c5e..9d06d08645 100644 --- a/packages/vue-db/tests/conformance.test.ts +++ b/packages/vue-db/tests/conformance.test.ts @@ -16,6 +16,7 @@ import { createLiveQueryCollection, createOptimisticAction, eq, + groupKeys, gt, sum, } from '@tanstack/db' @@ -30,6 +31,7 @@ import { createPublicationLog } from '../../db/tests/conformance/publication-log import { makeLaggingSource } from '../../db/tests/conformance/lagging-source' import { expectResultSurface } from '../../db/tests/conformance/result-laws' import { withScopeSetup } from '../../db/tests/conformance/scope-setup' +import type { PooledLiveQueries } from '@tanstack/db' import type { ConformanceResult, ControllableHandle, @@ -190,7 +192,10 @@ function mountCollection(collection: any) { return makeHandle(result, scope) } -function mountConfig(build: QueryBuild, opts?: { gcTime?: number }) { +function mountConfig( + build: QueryBuild, + opts?: { gcTime?: number; pool?: PooledLiveQueries }, +) { const { result, scope } = runInScope(() => useLiveQuery({ query: build, ...opts } as any), ) @@ -208,18 +213,25 @@ function mountDisabled() { function mountControllable

( build: (q: any, param: P) => any, initial: P, + opts?: { pool?: PooledLiveQueries }, ): ControllableHandle

{ const param = ref(initial) as { value: P } const published = createPublicationLog() const { result, scope } = runInScope(() => { - const query = useLiveQuery( - (q: any) => build(q, param.value), - [() => param.value], - ) + const query = + opts?.pool !== undefined + ? useLiveQuery( + { + query: (q: any) => build(q, param.value), + pool: opts.pool, + } as any, + [() => param.value], + ) + : useLiveQuery((q: any) => build(q, param.value), [() => param.value]) // Vue renders the value present at each pre-flush. watch( - () => query.data.value, - (data) => published.record(data), + () => [query.data.value, [...query.state.value.values()]], + ([data]) => published.record(data, query.state.value), { flush: `pre`, immediate: true }, ) return query @@ -228,12 +240,13 @@ function mountControllable

( return { ...handle, publications: published.values, + statePublications: published.stateValues, async setParam(next: P) { param.value = next // The first paint after the change: the framework has flushed its // reactive work, but no later task has run. await nextTick() - published.record(result.data.value) + published.record(result.data.value, result.state.value) await settle() }, } @@ -244,7 +257,7 @@ const vueDriver: LiveQueryDriver = { // Reactive reads decide when the query compiles again. comparesQueryIdentity: false, disabledRepresentation: `empty-reactive`, - ops: { eq, gt, count, sum, coalesce, createOptimisticAction }, + ops: { eq, gt, count, sum, coalesce, groupKeys, createOptimisticAction }, makeSource, makeDeferredSource, makeLaggingSource: () => diff --git a/packages/vue-db/tests/useLiveQuery.test.ts b/packages/vue-db/tests/useLiveQuery.test.ts index 58e43da6fd..1379506972 100644 --- a/packages/vue-db/tests/useLiveQuery.test.ts +++ b/packages/vue-db/tests/useLiveQuery.test.ts @@ -1,4 +1,3 @@ -import { describe, expect, it } from 'vitest' import { count, createCollection, @@ -7,6 +6,7 @@ import { eq, gt, } from '@tanstack/db' +import { describe, expect, it } from 'vitest' import { nextTick, ref, watchEffect } from 'vue' import { useLiveQuery } from '../src/useLiveQuery' import { mockSyncCollectionOptions } from '../../db/tests/utils' diff --git a/scripts/bench/incremental-update.ts b/scripts/bench/incremental-update.ts index c9362604c0..c7284020ee 100644 --- a/scripts/bench/incremental-update.ts +++ b/scripts/bench/incremental-update.ts @@ -11,12 +11,19 @@ import { eq, materialize, } from '../../packages/db/src/index.js' +import * as db from '../../packages/db/src/index.js' import type { ChangeMessageOrDeleteKeyMessage, Collection, SyncConfig, } from '../../packages/db/src/index.js' +// Before include(), a bare subquery in select was a Collection include. +const includeWith = (builder: any): any => + typeof (db as { include?: unknown }).include === `function` + ? (db as any).include(builder) + : builder + type Issue = { id: number status: `open` | `closed` @@ -254,16 +261,18 @@ const queryCases: Array = [ .select(({ issue }) => ({ id: issue.id, title: issue.title, - recentComments: q - .from({ comment: comments }) - .where(({ comment }) => eq(comment.issueId, issue.id)) - .orderBy(({ comment }) => comment.createdAt, `desc`) - .limit(3) - .select(({ comment }) => ({ - id: comment.id, - body: comment.body, - createdAt: comment.createdAt, - })), + recentComments: includeWith( + q + .from({ comment: comments }) + .where(({ comment }) => eq(comment.issueId, issue.id)) + .orderBy(({ comment }) => comment.createdAt, `desc`) + .limit(3) + .select(({ comment }) => ({ + id: comment.id, + body: comment.body, + createdAt: comment.createdAt, + })), + ), })), ) as unknown as BenchmarkCollection, write: insertVisibleComment, @@ -279,15 +288,17 @@ const queryCases: Array = [ .select(({ issue }) => ({ id: issue.id, title: issue.title, - comments: q - .from({ comment: comments }) - .where(({ comment }) => eq(comment.issueId, issue.id)) - .orderBy(({ comment }) => comment.createdAt, `desc`) - .select(({ comment }) => ({ - id: comment.id, - body: comment.body, - createdAt: comment.createdAt, - })), + comments: includeWith( + q + .from({ comment: comments }) + .where(({ comment }) => eq(comment.issueId, issue.id)) + .orderBy(({ comment }) => comment.createdAt, `desc`) + .select(({ comment }) => ({ + id: comment.id, + body: comment.body, + createdAt: comment.createdAt, + })), + ), })), ) as unknown as BenchmarkCollection, write: insertSelectedIssueComment, diff --git a/scripts/check-doc-snippets.mjs b/scripts/check-doc-snippets.mjs new file mode 100644 index 0000000000..4bcebaf0a1 --- /dev/null +++ b/scripts/check-doc-snippets.mjs @@ -0,0 +1,376 @@ +// Typechecks the TypeScript code examples in the guides and the shipped agent +// skills against the workspace packages' source. +// +// Examples are fragments: they use collections and components they never +// declare. The check declares each unknown name as `any`, so it reports what +// an example gets wrong about the TanStack API rather than what it leaves +// out. An unknown name that a TanStack package exports is reported instead: +// the example uses it without importing it. +// +// node scripts/check-doc-snippets.mjs [--verbose] [--update-baseline] +// +// An example that does not parse fails too. Mark a fence ```ts nocheck to +// skip an example that is not meant to compile. +// +// `doc-snippets-baseline.json` lists examples that already failed when the +// check began, by file and a hash of their code. Editing one changes its +// hash, so an edited example must pass; reformatting one keeps it. An example that passes must leave the +// baseline: run with --update-baseline after fixing one. +// +// Debugging: SNIPPET_ONLY=: checks one example, and +// SNIPPET_DUMP=: prints the source the check compiles for it. +import { createHash } from 'node:crypto' +import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs' +import { join, relative } from 'node:path' +import { fileURLToPath } from 'node:url' +import prettier from 'prettier' +import ts from 'typescript' +import { + componentScript, + fingerprint, + frameworkOf, + misspelling, + parses, + selfTest, + tanstackImports, +} from './doc-snippet-syntax.mjs' + +const root = fileURLToPath(new URL(`..`, import.meta.url)) +const verbose = process.argv.includes(`--verbose`) +const updateBaseline = process.argv.includes(`--update-baseline`) +const baselinePath = join(root, `scripts`, `doc-snippets-baseline.json`) +const baseline = existsSync(baselinePath) + ? JSON.parse(readFileSync(baselinePath, `utf8`)) + : {} +const hashOf = (code) => + createHash(`sha256`).update(code).digest(`hex`).slice(0, 16) + +function walk(dir, keep) { + if (!existsSync(dir)) return [] + return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const path = join(dir, entry.name) + if (entry.isDirectory()) + return entry.name === `node_modules` || entry.name === `reference` + ? [] + : walk(path, keep) + return keep(path) ? [path] : [] + }) +} + +const markdown = [ + ...walk(join(root, `docs`), (path) => path.endsWith(`.md`)), + ...readdirSync(join(root, `packages`)).flatMap((pkg) => + walk(join(root, `packages`, pkg, `skills`), (path) => path.endsWith(`.md`)), + ), +] + +// Map each workspace package and its subpath exports to source. CI does not +// build the packages, so an export that falls through to dist/ is missing. +const paths = {} +for (const pkg of readdirSync(join(root, `packages`))) { + const manifest = join(root, `packages`, pkg, `package.json`) + if (!existsSync(manifest)) continue + const { + name, + private: unpublished, + exports = {}, + } = JSON.parse(readFileSync(manifest, `utf8`)) + // Examples document published packages only. + if (unpublished) continue + for (const subpath of Object.keys(exports)) { + if (subpath === `./package.json`) continue + // `.` is src/index; `./x` is src/x.ts or src/x/index.ts. + const stem = subpath === `.` ? `index` : subpath.slice(2) + const entry = [`${stem}.ts`, `${stem}.tsx`, `${stem}/index.ts`] + .map((file) => join(`packages`, pkg, `src`, file)) + .find((file) => existsSync(join(root, file))) + if (!entry) throw new Error(`No source for ${name} export ${subpath}`) + paths[subpath === `.` ? name : `${name}/${stem}`] = [entry] + } +} + +// A fence closes with as many backticks as it opened with, so a ````ts +// example can hold a ``` block. +const fence = + /^(`{3,})(ts|tsx|typescript|svelte|vue)([^\n]*)\n([\s\S]*?)^\1[ \t]*$/gm +const snippets = [] +for (const file of markdown) { + const text = readFileSync(file, `utf8`) + for (const match of text.matchAll(fence)) { + if (match[3].includes(`nocheck`)) continue + let line = text.slice(0, match.index).split(`\n`).length + let code = match[4] + // A component example: check its TypeScript script, not its template. + if (match[2] === `svelte` || match[2] === `vue`) { + const script = componentScript(code) + if (!script) continue + code = script.code + line += script.offset + } + snippets.push({ + file: relative(root, file), + line, + code, + name: join( + root, + `.snippet-check`, + `${snippets.length}.${/<\/|<[A-Z][A-Za-z]*[ />]/.test(code) ? `tsx` : `ts`}`, + ), + }) + } +} + +selfTest() +for (const snippet of snippets) { + const jsx = snippet.name.endsWith(`.tsx`) + snippet.hash = hashOf(fingerprint(snippet.code, jsx)) + snippet.imports = tanstackImports(snippet.code, jsx) +} + +if (process.env.SNIPPET_ONLY) { + const keep = snippets.filter( + (s) => `${s.file}:${s.line}` === process.env.SNIPPET_ONLY, + ) + snippets.length = 0 + snippets.push(...keep) +} + +const options = { + strict: true, + noUnusedLocals: false, + noUncheckedIndexedAccess: false, + allowUnreachableCode: true, + skipLibCheck: true, + noEmit: true, + jsx: ts.JsxEmit.ReactJSX, + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + lib: [`lib.es2022.d.ts`, `lib.dom.d.ts`, `lib.dom.iterable.d.ts`], + baseUrl: root, + paths, +} + +// Modules an example imports but this repo cannot resolve (app files, other +// frameworks) are declared `any`, one by one. A wildcard would also erase +// third-party types that the packages themselves use. +const ambient = join(root, `.snippet-check`, `ambient.d.ts`) +const missingModules = new Set() +const ambientSource = () => + [...missingModules] + .map((name) => `declare module ${JSON.stringify(name)}\n`) + .join(``) || `export {}\n` + +function check(sources) { + const host = ts.createCompilerHost(options) + const read = host.readFile + host.readFile = (path) => sources.get(path) ?? read(path) + const exists = host.fileExists + host.fileExists = (path) => sources.has(path) || exists(path) + const program = ts.createProgram([...sources.keys()], options, host) + return { program, diagnostics: ts.getPreEmitDiagnostics(program) } +} + +// Names the TanStack packages export, to tell "not imported" from "app code". +const exported = new Map() +{ + const probe = join(root, `.snippet-check`, `exports.ts`) + const packages = Object.keys(paths).filter((name) => + name.startsWith(`@tanstack/`), + ) + const imports = packages + .map((name, i) => `import * as m${i} from '${name}'`) + .join(`\n`) + const { program } = check(new Map([[probe, imports + `\n`]])) + const checker = program.getTypeChecker() + program.getSourceFile(probe).statements.forEach((statement, i) => { + const symbol = checker.getSymbolAtLocation(statement.moduleSpecifier) + if (!symbol) return + for (const item of checker.getExportsOfModule(symbol)) { + const owners = exported.get(item.name) ?? [] + owners.push(packages[i]) + exported.set(item.name, owners) + } + }) +} + +// The framework a file documents, for names several adapters export. +function packageFor(name, file) { + const owners = exported.get(name) + if (owners.length === 1) return owners[0] + return ( + owners.find((owner) => owner === `@tanstack/db`) ?? + owners.find((owner) => owner === `@tanstack/${frameworkOf(file)}-db`) ?? + owners[0] + ) +} + +const importsTanStack = (code) => /from ['"]@tanstack\//.test(code) + +function sourceOf(snippet, declared, autoImported) { + const byPackage = new Map() + for (const name of autoImported) { + const pkg = packageFor(name, snippet.file) + byPackage.set(pkg, [...(byPackage.get(pkg) ?? []), name]) + } + return ( + [...byPackage] + .map(([pkg, names]) => `import { ${names.join(`, `)} } from '${pkg}'\n`) + .join(``) + + `import type { Collection as SnippetCollection } from '@tanstack/db'\n` + + [...declared] + .map( + (name) => + `declare const ${name}: ${/collection$/i.test(name) ? `SnippetCollection, string | number>` : `any`}\ntype ${name} = any\n`, + ) + .join(``) + + snippet.code + + `\nexport {}\n` + ) +} + +const declared = snippets.map(() => new Set()) +const unimported = snippets.map(() => new Set()) +const autoImported = snippets.map(() => new Set()) +const misspelled = snippets.map(() => new Map()) +const unknownName = new Set([2304, 2552, 18004, 2749, 2693]) +// Docs often show alternatives side by side in one block, and leave +// callback parameters unannotated where an app would annotate them. +const ignored = new Set([2451, 2300, 2393, 7005, 7006, 7019, 7031, 7034, 7053]) +// An unresolvable dependency is declared as an untyped module, whose names +// cannot be used as types. +ignored.add(2709) +let diagnostics = [] +// Declare unknown names until a pass adds none. +for (let pass = 0; pass < 6; pass++) { + const sources = new Map([[ambient, ambientSource()]]) + snippets.forEach((snippet, i) => + sources.set(snippet.name, sourceOf(snippet, declared[i], autoImported[i])), + ) + diagnostics = check(sources).diagnostics + let added = 0 + for (const diagnostic of diagnostics) { + if ( + diagnostic.code === 2307 && + snippets.some((s) => s.name === diagnostic.file?.fileName) + ) { + const name = ts + .flattenDiagnosticMessageText(diagnostic.messageText, ``) + .match(/'([^']+)'/)?.[1] + if (name && !missingModules.has(name)) { + missingModules.add(name) + added++ + } + continue + } + if (!unknownName.has(diagnostic.code)) continue + const index = snippets.findIndex( + (s) => s.name === diagnostic.file?.fileName, + ) + if (index < 0) continue + const name = ts + .flattenDiagnosticMessageText(diagnostic.messageText, ``) + .match(/'([^']+)'/)?.[1] + if (!name) continue + // A near miss of a TanStack name the example imports is a typo, not app + // code. TypeScript stops suggesting spellings after a few, so compare here. + const suggestion = misspelling(name, snippets[index].imports) + if (suggestion !== undefined) misspelled[index].set(name, suggestion) + if (exported.has(name)) { + if (importsTanStack(snippets[index].code)) unimported[index].add(name) + else if (!autoImported[index].has(name)) { + autoImported[index].add(name) + added++ + } + } else if (!declared[index].has(name)) { + declared[index].add(name) + added++ + } + } + if (added === 0) break +} + +if (process.env.SNIPPET_DUMP) { + const i = snippets.findIndex( + (s) => `${s.file}:${s.line}` === process.env.SNIPPET_DUMP, + ) + console.log(sourceOf(snippets[i], declared[i], autoImported[i])) +} +const failures = [] +let unparsable = 0 +snippets.forEach((snippet, i) => { + const ownDiagnostics = diagnostics.filter( + (d) => d.file?.fileName === snippet.name, + ) + // An example that does not parse cannot be checked. Pseudo-code, such as + // `...` placeholders, is marked nocheck. + if (!parses(snippet.code, snippet.name.endsWith(`.tsx`))) { + unparsable++ + failures.push({ + snippet, + text: `${snippet.file}:${snippet.line}\n does not parse; mark the fence nocheck if it is pseudo-code`, + }) + return + } + const offset = + 1 + + declared[i].size * 2 + + new Set([...autoImported[i]].map((n) => packageFor(n, snippet.file))).size + const messages = ownDiagnostics + .filter((d) => !unknownName.has(d.code) && !ignored.has(d.code)) + .map((d) => { + const { line } = d.file.getLineAndCharacterOfPosition(d.start ?? 0) + return ` line ${snippet.line + line - offset}: TS${d.code} ${ts.flattenDiagnosticMessageText(d.messageText, ` `)}` + }) + for (const name of unimported[i]) + messages.push(` uses \`${name}\` without importing it`) + for (const [name, suggestion] of misspelled[i]) + messages.push(` uses \`${name}\`; did you mean \`${suggestion}\`?`) + if (messages.length) + failures.push({ + snippet, + text: `${snippet.file}:${snippet.line}\n${messages.join(`\n`)}`, + }) +}) + +const known = (snippet) => + baseline[snippet.file]?.includes(snippet.hash) ?? false +const newFailures = failures.filter(({ snippet }) => !known(snippet)) +const failing = new Set( + failures.map(({ snippet }) => `${snippet.file}#${snippet.hash}`), +) +const stale = Object.entries(baseline).flatMap(([file, hashes]) => + hashes + .filter((hash) => !failing.has(`${file}#${hash}`)) + .map((hash) => `${file}#${hash}`), +) + +if (updateBaseline && !process.env.SNIPPET_ONLY) { + const next = {} + for (const { snippet } of failures) + (next[snippet.file] ??= []).push(snippet.hash) + for (const hashes of Object.values(next)) hashes.sort() + const sorted = Object.fromEntries( + Object.entries(next).sort(([a], [b]) => a.localeCompare(b)), + ) + writeFileSync( + baselinePath, + await prettier.format(JSON.stringify(sorted), { + ...(await prettier.resolveConfig(baselinePath)), + filepath: baselinePath, + }), + ) + console.log(`Baseline: ${failures.length} known failing examples`) +} else { + const shown = verbose ? failures : newFailures + if (shown.length) console.log(shown.map(({ text }) => text).join(`\n\n`)) + if (stale.length && !process.env.SNIPPET_ONLY) + console.log( + `\n${stale.length} baseline entries now pass or no longer exist; run with --update-baseline:\n${stale.join(`\n`)}`, + ) + console.log( + `\n${snippets.length} examples in ${markdown.length} files: ${newFailures.length} new failures, ${failures.length - newFailures.length} known, ${unparsable} of them do not parse`, + ) + process.exitCode = + newFailures.length || (stale.length && !process.env.SNIPPET_ONLY) ? 1 : 0 +} diff --git a/scripts/doc-snippet-syntax.mjs b/scripts/doc-snippet-syntax.mjs new file mode 100644 index 0000000000..f271fe222f --- /dev/null +++ b/scripts/doc-snippet-syntax.mjs @@ -0,0 +1,278 @@ +// Syntax helpers for check-doc-snippets.mjs, with the cases that pin their +// judgments. `selfTest()` runs on every check, so a change that makes a helper +// miss a defect, or report a correct example, fails before any doc is read. +import ts from 'typescript' + +const parse = (code, jsx) => + ts.createSourceFile( + jsx ? `example.tsx` : `example.ts`, + code, + ts.ScriptTarget.Latest, + true, + jsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS, + ) + +const isJsx = (node) => + ts.isJsxElement(node) || + ts.isJsxSelfClosingElement(node) || + ts.isJsxFragment(node) + +/** + * The tokens of an example, without what formatting changes, so a reformatted + * example keeps its baseline entry: spacing, comments, trailing commas, + * semicolons, the parentheses around multi-line JSX, and the parentheses + * around a single arrow parameter. Other commas and parentheses stay, because + * moving one changes what the code calls. The tree, not a token scan, finds JSX: a parenthesis in JSX + * text is text. + */ +export function fingerprint(code, jsx) { + const tokens = [] + const visit = (node) => { + if (ts.isParenthesizedExpression(node) && isJsx(node.expression)) + return visit(node.expression) + const children = node.getChildren() + if (children.length === 0) { + if ( + node.kind === ts.SyntaxKind.SemicolonToken || + node.kind === ts.SyntaxKind.EndOfFileToken + ) + return + tokens.push( + ts.isStringLiteral(node) + ? JSON.stringify(node.text) + : // Formatting also reflows JSX text and the HTML in a template. + node.getText().replace(/\s+/g, ``), + ) + return + } + const bareParameter = + ts.isArrowFunction(node) && + node.parameters.length === 1 && + ts.isIdentifier(node.parameters[0].name) && + !node.parameters[0].type + children.forEach((child, index) => { + const parameterParen = + bareParameter && + (child.kind === ts.SyntaxKind.OpenParenToken || + child.kind === ts.SyntaxKind.CloseParenToken) + // A trailing comma ends its list; other commas separate values. + const trailingComma = + child.kind === ts.SyntaxKind.CommaToken && index === children.length - 1 + if (!parameterParen && !trailingComma) visit(child) + }) + } + visit(parse(code, jsx)) + return tokens.join(` `) +} + +/** + * Whether an example parses. Only syntax counts: a semantic error with a 1xxx + * code, such as a missing default export, is still an error to report. + */ +export function parses(code, jsx) { + const { diagnostics = [] } = ts.transpileModule(code, { + reportDiagnostics: true, + fileName: jsx ? `example.tsx` : `example.ts`, + compilerOptions: { jsx: ts.JsxEmit.Preserve }, + }) + return diagnostics.length === 0 +} + +/** The names an example imports from a TanStack package, in every form. */ +export function tanstackImports(code, jsx) { + const names = [] + for (const statement of parse(code, jsx).statements) { + if ( + !ts.isImportDeclaration(statement) || + !ts.isStringLiteral(statement.moduleSpecifier) || + !statement.moduleSpecifier.text.startsWith(`@tanstack/`) + ) + continue + const clause = statement.importClause + if (clause?.name) names.push(clause.name.text) + const bindings = clause?.namedBindings + if (bindings && ts.isNamedImports(bindings)) + names.push(...bindings.elements.map((element) => element.name.text)) + } + return names +} + +function editDistance(a, b) { + let previous = Array.from({ length: b.length + 1 }, (_, j) => j) + for (let i = 1; i <= a.length; i++) { + const row = [i] + for (let j = 1; j <= b.length; j++) + row[j] = Math.min( + previous[j] + 1, + row[j - 1] + 1, + previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1), + ) + previous = row + } + return previous[b.length] +} + +/** + * The imported name that an unknown name misspells, if any. An app's own + * names often differ from an import only in case (`collection` and + * `Collection`) or by a suffix (`counter` and `count`); those are not typos. + */ +export function misspelling(name, imported) { + if (name.length < 5) return undefined + return imported.find( + (candidate) => + candidate.toLowerCase() !== name.toLowerCase() && + !candidate.startsWith(name) && + !name.startsWith(candidate) && + editDistance(name, candidate) <= (name.length >= 8 ? 2 : 1), + ) +} + +/** + * The TypeScript in a Svelte or Vue component example: its `\n

{a}

`, + ) + if (component?.code !== `const a = 1\n` || component.offset !== 2) + throw new Error( + `self-test: componentScript read ${JSON.stringify(component)}`, + ) + if (componentScript(``) !== undefined) + throw new Error( + `self-test: componentScript read a script without lang="ts"`, + ) + if (componentScript(``)?.code !== `x`) + throw new Error( + `self-test: componentScript missed