diff --git a/.changeset/pre.json b/.changeset/pre.json index 689d84fce7..b0c1ce4f14 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -72,6 +72,7 @@ "powersync-failed-load-release", "preact-db-adapter", "preserve-reentrant-ordering-repair", + "quiet-documents-return", "react-db-strip-dev-warnings", "react-db-unhashable-throws", "recover-interleaved-electric-source-commits", diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 19bab01b7f..48e1a8a6ab 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -8,6 +8,12 @@ on: - 'packages/db-ivm/**' - 'scripts/bench/**' - '.github/workflows/benchmark.yml' + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - '.npmrc' + - 'patches/**' + - '*config*' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -35,17 +41,38 @@ jobs: with: fetch-depth: 0 persist-credentials: false + - name: Test benchmark input selection + run: node --test scripts/bench/inputs-changed.test.mjs + - name: Detect benchmark input changes + id: inputs + env: + BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }} + EVENT_NAME: ${{ github.event_name }} + run: | + if [ "$EVENT_NAME" = workflow_dispatch ]; then + echo 'changed=true' >> "$GITHUB_OUTPUT" + else + BASE_SHA=$(git merge-base "origin/$BASE_REF" HEAD) + node scripts/bench/inputs-changed.mjs "$BASE_SHA" HEAD >> "$GITHUB_OUTPUT" + fi + - name: Report unchanged benchmark inputs + if: steps.inputs.outputs.changed == 'false' + run: echo 'Benchmark inputs are unchanged. Version and changelog edits do not require a latency comparison.' >> "$GITHUB_STEP_SUMMARY" - name: Setup Tools + if: steps.inputs.outputs.changed == 'true' uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 - name: Test benchmark regression gate + if: steps.inputs.outputs.changed == 'true' run: node --test scripts/bench/compare-incremental-update.test.mjs - name: Run benchmark on PR + if: steps.inputs.outputs.changed == 'true' run: | pnpm --filter @tanstack/db-ivm build pnpm exec tsx scripts/bench/incremental-update.ts \ --iterations=100 --warmup=20 --repetitions=3 \ --outFile="$RUNNER_TEMP/candidate.json" - name: Run benchmark on base + if: steps.inputs.outputs.changed == 'true' env: BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }} run: | @@ -62,6 +89,7 @@ jobs: --outFile="$RUNNER_TEMP/base.json" - name: Compare results id: comparison + if: steps.inputs.outputs.changed == 'true' continue-on-error: true run: | pnpm exec tsx scripts/bench/compare-incremental-update.ts \ @@ -70,7 +98,7 @@ jobs: --outFile="$RUNNER_TEMP/comparison.md" \ --failOnRegression=true - name: Publish summary - if: always() && steps.comparison.outcome != 'skipped' + if: always() && steps.inputs.outputs.changed == 'true' && steps.comparison.outcome != 'skipped' run: | if [ -f "$RUNNER_TEMP/comparison.md" ]; then cat "$RUNNER_TEMP/comparison.md" >> "$GITHUB_STEP_SUMMARY" @@ -78,7 +106,7 @@ jobs: echo 'Benchmark comparison failed before producing a report; see the comparison log.' >> "$GITHUB_STEP_SUMMARY" fi - name: Upload reports - if: always() + if: always() && steps.inputs.outputs.changed == 'true' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: benchmark-reports @@ -87,7 +115,7 @@ jobs: ${{ runner.temp }}/candidate.json ${{ runner.temp }}/comparison.md - name: Comment on PR - if: always() && github.event_name == 'pull_request' && steps.comparison.outcome != 'skipped' + if: always() && steps.inputs.outputs.changed == 'true' && github.event_name == 'pull_request' && steps.comparison.outcome != 'skipped' # PRs from forks get a read-only token; the job summary and artifacts # still carry the results. continue-on-error: true diff --git a/docs/contributing/oracle-coverage.md b/docs/contributing/oracle-coverage.md index 476b339646..42ca17d789 100644 --- a/docs/contributing/oracle-coverage.md +++ b/docs/contributing/oracle-coverage.md @@ -2078,7 +2078,7 @@ generic visitor behavior outside the named collectors. | Correlated include path identity | `packages/db/tests/query/includes-context-transport-oracle.test.ts` | The fixed path witnesses live in `packages/db/tests/query/includes-context-transport.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`, 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 four is a forced history that strips the grouping compiler everywhere or from `orderBy` only, with no, one, or two group keys in turn, to sample each path 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 operator states with `createOracleReach`. Each fixed and random campaign first executes twelve complete structural histories through the shared model, driver and comparison, then 160 generated histories. One reach requires all twenty-one states in the structural set. This guarantee concerns initial publication or compilation only: these inputs have no mutation steps, shadowed alias, or union. A separate generated reach requires the 160 generated histories themselves to enter all twenty-one states and seven additional checkpoints after parent updates, source inserts, updates, deletes, source route moves, a shadowed child alias comparison, and a union-source comparison. Those checkpoints count only after the public model comparison succeeds. Generated coverage remains sampled and can miss a state for a seed; a miss fails and retains the chosen seed and counts. The seven checkpoints do not require every operator state after every mutation or on every route; 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. The former seed-dependent string-order reach gap recurred in the main release run on 2026-11-02. The structural histories guarantee the initial premises. They cannot satisfy the separate generated reach. The model, comparisons, reach minimum, fixed seed 2094, and 160 generated runs per campaign remain unchanged. Generated history choices now use named fields rather than a positional tuple. Shared parameter helpers report each selected seed before execution, so a final reach failure after fast-check success has a seed-only campaign replay. Statistics use the exact value `TANSTACK_DB_ORACLE_STATISTICS=1` and survive comparison or reach failures. Removing the structural string history fails the fixed-reach check even when generated histories reach strings. Selecting code-unit order instead of the documented locale order fails the ordinary published-row comparison; seed 2094/path 11 replays that failure directly. The [original repair record](oracle-reviews/2026-11-02-aggregate-select-reach.md) preserves historical evidence. The [nine-item review record](oracle-reviews/2026-11-02-aggregate-select-review.md) records separate counts, generator ablations, replay diagnostics, reproducible commands, and remaining limits. | +| 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 four is a forced history that strips the grouping compiler everywhere or from `orderBy` only, with no, one, or two group keys in turn, to sample each path 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 operator states with `createOracleReach`. Each fixed and random campaign first executes twelve complete structural histories through the shared model, driver and comparison, then twelve seed-generated required histories and the unchanged 160 broad generated histories. One reach requires all twenty-one states in the structural set. This guarantee concerns initial publication or compilation only: these inputs have no mutation steps, shadowed alias, or union. A separate generated reach requires the generated histories themselves to enter all twenty-one states and seven additional checkpoints after parent updates, source inserts, updates, deletes, source route moves, a shadowed child alias comparison, and a union-source comparison. Those checkpoints count only after the public model comparison succeeds. The required grammar constructs one varied history per structural family, adding legal mutations and forcing shadowing and union on the string-ordered family. It guarantees the declared premises without relying on broad sampling; both reaches still fail on a miss and retain the selected seed and counts. The seven checkpoints do not require every operator state after every mutation or on every route; 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. The former seed-dependent string-order reach gap recurred in the main release run on 2026-11-02. The structural histories guarantee the initial premises. They cannot satisfy the separate generated reach. The model, comparisons, reach minimum, fixed seed 2094, and 160 generated runs per campaign remain unchanged. Generated history choices now use named fields rather than a positional tuple. Shared parameter helpers report each selected seed before execution, so a final reach failure after fast-check success has a seed-only campaign replay. Statistics use the exact value `TANSTACK_DB_ORACLE_STATISTICS=1` and survive comparison or reach failures. Removing the structural string history fails the fixed-reach check even when generated histories reach strings. Selecting code-unit order instead of the documented locale order fails the ordinary published-row comparison; seed 2094/path 11 replays that failure directly. The [original repair record](oracle-reviews/2026-11-02-aggregate-select-reach.md) preserves historical evidence. The [nine-item review record](oracle-reviews/2026-11-02-aggregate-select-review.md) records historical counts, generator ablations, replay diagnostics, reproducible commands, and remaining limits. The [release CI repair](oracle-reviews/2026-11-05-release-ci.md) records the recurrent sampled-reach failure, constructive generated-prefix repair, measured reach, and unchanged ordering mutant. | | 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`, shared `subquery-literal-value-oracle.ts`, supporting `subquery-literal-value-driver.ts`, correlated `subquery-literal-correlated-oracle.test.ts` / `subquery-literal-correlated-oracle.ts` with concrete factory/driver support, supplemental `subquery-user-value.test.ts` and `transactions.test.ts`, [repair record](oracle-reviews/2026-09-30-queryref-publication-repair.md), [chunk 19](oracle-reviews/2026-10-28-pr-2171-oracle-chunks.md) | The independent relational models own joined DISTINCT with outer WHERE, aggregate-source updates and joined findOne histories. Other fixed container/no-select/functional/predicate-literal examples are supplemental ordinary tests, not independent oracle scope. Chunk 19 adds one ordinary-JavaScript literal model with 17 forms × two positions × root/QueryRef receivers: 68 histories and three cuts each. It checks complete ordinary rows, event bags/current/previous values, event-time reads and immutable QueryRef callback inputs, with all 204 receiving and 102 callback states reached once. Strict exact-driver admission passes; unchanged production has eight undefined-to-null REDs (P11), ten empty-container-loss REDs (P12), 50 passing new histories and six passing relational neighbors. Twenty-four hostile and four lawful controls pass; partial selections still fail mandatory reach. Chunk 20 receives the same literal law through toArray, array/singleton materialize and Collection facades: 272 exact histories, all 3,264 path/cut states once, 200 main passes and 72 intended P11/P12 REDs; 33 checker controls and six independent facet controls pass. Legal QueryRef children have their own correlation WHERE and preserve the authored routeGroup input until actual projection removes it. Facade layouts are ordered, startup callbacks permit canonical empty values but owe complete ready inputs, and all 272 titles remain distinct for precise selection. Chunk 21 adds a separate source-public type judgment (`subquery-literal-types-oracle.ts` / `.test.ts`, supporting `subquery-literal-types-support.ts`) for those exact 340 authored receivers: 1,870 actual QueryResult, Collection-row, contextual-input, inline and facade/API markers. All consumers compile without diagnostics under TypeScript 5.9.3, and all 2,550 syntax/admission/marker states execute once. Unchanged production has 280 intended RED histories: 40 raw-nullish precision failures and 272 contextual-input or propagated-inline metadata failures, with 32 overlapping; 60 native histories and 55 checker/source controls pass. Independent review exposed erased call/construct signatures on records; the recorder now retains those declared capabilities and eight existing empty/nonempty × root/nested record controls reject invented signatures, after all eight failed under the original checker. Only the separate four virtual fields at named query-output or published/source Collection row roots are excluded from this ordinary-value judgment; callbacks and inline children remain strict. The full static callback metadata grammar remains open under existing `virtual-row-fields-types-oracle.test.ts`, with the literal sources as receiving witnesses of its accepted P10 law. Source-public inference does not establish declaration portability; an exported inferred factory currently gives TS4058 and is preserved for O22, including exported Collection and built/installed controls. Broader mixed-expression admission, sparse authored arrays, symbols, ordinary-record readonly modifiers, descriptor flags, arbitrary recursion and provider/framework schedules remain open with their sequential receiving owners. Metadata attribution stays with its separate owner; the four reserved fields are excluded only at each observed root row. No production repair or closure is claimed. | | 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. | diff --git a/docs/contributing/oracle-reviews/2026-11-05-release-ci.md b/docs/contributing/oracle-reviews/2026-11-05-release-ci.md new file mode 100644 index 0000000000..2578709d83 --- /dev/null +++ b/docs/contributing/oracle-reviews/2026-11-05-release-ci.md @@ -0,0 +1,156 @@ +# Release CI repair, 2026-11-05 + +This record binds the oracle source hash below to the commit containing this +record. Parent/main: `bfb660a1e8074064cd1fd93a645273c17aaec98c`. +Release PR #2359 originally held `3525bc0bc92af65754ab90ec636bad3a345b7a16`. +Oracle source: `packages/db/tests/query/aggregate-select-oracle.property.test.ts`, +SHA256 `818b6ad798ad4caae9be4f084b6c1d50427a2dc0b47d465202f123f279ffcc4c`. +The repair changes test instrumentation and CI only; it adds no shipped JavaScript. + +## Failure and repair + +**Law and authority:** The existing aggregate-select law, architecture's +aggregate-select contract, and query guide's “Ordering by SELECT Fields” and +“Multiple Column Ordering” stay unchanged. Published rows match independent +aggregate recomputation; explicit string ordering uses `en-US` collation. +ORC-015 requires the declared premises to be reached. + +**RED:** [DB shard 2](https://github.com/TanStack/db/actions/runs/42122151300/job/127587486662) +reported seed `411440032`, 14,845 passing tests, one failure, and no type errors. +Every query comparison passed. The final generated reach failed: +`a string field orders A against a lowercase key (0)`. +Replaying that seed on unchanged main reproduced the same reach failure. +The existing twelve structural histories reached it, but could not satisfy the +separate generated reach. The earlier November 2 repair documented that limit. + +**Repair:** Each campaign retains its twelve structural examples and all 160 +broad random histories. It adds twelve required histories generated from the +structural families: varied literals and order directions, parent/source writes, +optional shadowing and union, and zero to two tail mutations. The string-ordered +triple-group family forces shadowing and union, so mutation and receiving +checkpoints cannot depend on a lottery. Each required history runs the existing +independent model, production driver, and comparisons. `fc.sample` uses the +campaign seed; appending its results to fast-check examples preserves direct +seed/path replay and the twelve old structural indices. These randomized +examples are not independently shrunk. Broad histories retain ordinary shrinking. +No model rule, comparison, reach minimum, fixed seed, or broad budget changes. + +**GREEN:** The captured seed passes all six tests, as do the normal fixed seed +2094 and fresh seed -1074795262. Full DB TypeScript checking reports no errors. +A diagnostic that temporarily ran only the structural and required prefixes +also passed with seed 411440032. The broad budget was restored immediately; +that diagnostic supplies the final table column, not normal campaign evidence. + +**Mutant:** Changing the driver-supplied string collation from `locale` to +`lexical` fails the published-row comparison at +`initial: top-level rows in orderBy position 0`, seed 2094/path 11. This is an +assertion failure at the intended checkpoint, not a setup failure or timeout. +Restoring collation makes the same direct replay pass (one test, five replay +skips). The comparison and model were never changed to accept that mutant. + +Reproduce from the repository root: + +```sh +TANSTACK_DB_ORACLE_SEED=411440032 pnpm --filter @tanstack/db exec vitest run \ + tests/query/aggregate-select-oracle.property.test.ts \ + --coverage.enabled=false --maxWorkers=1 +TANSTACK_DB_ORACLE_STATISTICS=1 pnpm --filter @tanstack/db exec vitest run \ + --config vitest.aggregate.config.mjs \ + tests/query/aggregate-select-oracle.property.test.ts --disableConsoleIntercept +TANSTACK_DB_ORACLE_PROPERTY=aggregate.select-shapes \ +TANSTACK_DB_ORACLE_SEED=2094 TANSTACK_DB_ORACLE_PATH=11 \ +pnpm --filter @tanstack/db exec vitest run \ + tests/query/aggregate-select-oracle.property.test.ts \ + --coverage.enabled=false --maxWorkers=1 +``` + +## Measured reach + +The normal run uses twelve structural plus 172 generated histories per campaign. +Structural counts are identical in both campaigns. Every generated count +includes only generated histories, never structural observations. + +| State | Structural (each campaign) | Fixed generated | Fresh generated | Prefix only, captured seed | +| --- | ---: | ---: | ---: | ---: | +| bare include: field | 1 | 12 | 9 | 1 | +| bare include: nested | 1 | 13 | 15 | 1 | +| bare include: case | 1 | 10 | 6 | 1 | +| toArray include in a conditional branch | 1 | 12 | 9 | 1 | +| bare group keys without an aggregate | 1 | 18 | 27 | 1 | +| bare group keys beside an aggregate | 1 | 64 | 68 | 1 | +| include() wrap | 1 | 24 | 33 | 1 | +| materialize() wrap | 1 | 23 | 32 | 1 | +| grouping stripped alone: grouped | 1 | 30 | 16 | 1 | +| grouping stripped alone: one group | 1 | 10 | 13 | 1 | +| grouping stripped alone: HAVING aggregate | 1 | 28 | 20 | 1 | +| include runtime stripped | 1 | 14 | 18 | 1 | +| orderBy aggregate stripped beside a select aggregate | 1 | 33 | 25 | 1 | +| an aggregate orders the published rows | 1 | 25 | 22 | 1 | +| a number select field orders the published rows | 1 | 12 | 9 | 1 | +| a string select field orders the published rows | 1 | 3 | 8 | 1 | +| a string field orders A against a lowercase key | 1 | 3 | 5 | 1 | +| two orderBy terms order the rows | 1 | 10 | 9 | 1 | +| an include query orders its rows | 2 | 32 | 31 | 2 | +| two group keys publish several groups | 1 | 8 | 14 | 1 | +| three group keys publish several groups | 1 | 7 | 7 | 1 | +| published after a parent update | — | 37 | 28 | 4 | +| published after a source insert | — | 36 | 32 | 4 | +| published after a source update | — | 36 | 35 | 4 | +| published after a source delete | — | 34 | 36 | 4 | +| published after a source route move | — | 12 | 9 | 2 | +| a shadowed child alias publishes rows | — | 16 | 16 | 2 | +| a union source publishes rows | — | 34 | 30 | 4 | + +## Grammar controls and limits + +Reconstruction preserves the twelve original structural histories verbatim and +rebuilds their families with generated literals, direction and legal writes. +Removing the required prefix reproduces the captured reach miss on the parent; +removing broad sampling still reaches all 28 generated requirements. Literals +and direction vary expression results and ordering; source and parent writes +exercise incremental publication; route moves, shadowing and union distinguish +receiving paths. Omitting the string family removes the prefix's string witness. +Literals use the existing bounded domain, directions are ascending/descending, +route targets are `b`/`A`, and tail length is zero through two. Existing broad +histories retain their wider grammar. Union plus a nested include stays excluded +from required histories because it is rejected before compilation; the broad +grammar continues to check that rejection. + +**Limits:** These are settled, controlled-core observations at the existing +initial and after-write checkpoints, not real-provider or framework scheduling +claims. Required premises are covered, but every operator × mutation × route +combination is not guaranteed. The existing unsupported ordering domains in the +coverage map remain open under the aggregate-select owner. No new product +mismatch or missing-input hypothesis was identified. + +ORC-001/002/003/005: authority, independent model, literate responsibilities, +production paths and comparisons are unchanged and named in the executable +owner. ORC-004 controls are above. ORC-006/013: the A/lowercase ordering witness +and lexical mutant distinguish the promised collation. ORC-007: normal fixed +and fresh campaigns share grammar/check/budget; direct replay was verified. +ORC-008/009: no model states or vocabulary change. ORC-010: no cleanup or +failure-capture changes; statistics remain in `finally`. ORC-011: no new +shared-semantic-fault hypothesis; this is a reach repair. ORC-014: the claim +stays at the controlled-core boundary. ORC-015: measured counts above and both +reach assertions remain. ORC-016: all inputs use the shared model/driver/check; +no separate expected answers were added. This record supplies ORC-012 evidence. + +## Benchmark failure + +[Benchmark run 42122151381](https://github.com/TanStack/db/actions/runs/42122151381) +compared the release merge `c0a13e3af` with main `bfb660a1e`. Overall ratios were +1.02x writes and 1.03x cold hydration. The 1,000-row manual/synced document case +failed the persistent cold gate: base median 7.346ms, candidate median 16.005ms. +All three candidate repetitions exceeded the base range and both gate floors. +The release changed no runtime, harness, or resolved dependency input. This +establishes a false positive for this pair; it does not establish a noise ceiling. + +CI now compares inputs before measuring, ignoring package versions, changelog +text, and changed workspace specifiers only when the resolved `link:` path +stays equal. External dependency specifiers/resolutions, code, build/config, +workflow and harness changes remain significant. Manual runs always measure. +An input-read failure fails CI. The real main-to-release comparison returns +`changed=false`; pre-2356-to-main returns `changed=true`. Three selector tests +and all nineteen existing regression-gate tests pass. Gate thresholds and +repetitions remain unchanged. Identical-input pairs add no regression evidence; +noise on changed-input pairs remains a calibration limit. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index 5467d30e50..131eefa6c1 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -27,8 +27,8 @@ "@angular/forms": "^20.3.34", "@angular/platform-browser": "^20.3.34", "@angular/router": "^20.3.34", - "@tanstack/angular-db": "^1.0.0-rc.1.3", - "@tanstack/db": "^1.0.0-rc.1.3", + "@tanstack/angular-db": "^1.0.0-rc.1.4", + "@tanstack/db": "^1.0.0-rc.1.4", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index 7ef4792128..cef8e258fe 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 41.0.0 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^1.0.0-rc.1.3", - "@tanstack/node-db-sqlite-persistence": "^1.0.0-rc.1.3", + "@tanstack/electron-db-sqlite-persistence": "^1.0.0-rc.1.4", + "@tanstack/node-db-sqlite-persistence": "^1.0.0-rc.1.4", "@tanstack/offline-transactions": "^1.2.0", "@tanstack/query-db-collection": "^1.5.1", - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 29e863e868..96c458ec78 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^1.0.0-rc.1.3", + "@tanstack/db": "^1.0.0-rc.1.4", "@tanstack/offline-transactions": "^1.2.0", "@tanstack/query-db-collection": "^1.5.1", - "@tanstack/react-db": "^1.0.0-rc.1.3", - "@tanstack/react-native-db-sqlite-persistence": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", + "@tanstack/react-native-db-sqlite-persistence": "^1.0.0-rc.1.4", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 9b19113a2f..d212336ce5 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^1.0.0-rc.1.3", - "@tanstack/electric-db-collection": "^1.0.0-rc.1.3", + "@tanstack/db": "^1.0.0-rc.1.4", + "@tanstack/electric-db-collection": "^1.0.0-rc.1.4", "@tanstack/offline-transactions": "^1.2.0", - "@tanstack/react-db": "^1.0.0-rc.1.3", - "@tanstack/react-native-db-sqlite-persistence": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", + "@tanstack/react-native-db-sqlite-persistence": "^1.0.0-rc.1.4", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/action-enforcement/package.json b/examples/react/action-enforcement/package.json index 975463b215..462cda7e12 100644 --- a/examples/react/action-enforcement/package.json +++ b/examples/react/action-enforcement/package.json @@ -13,7 +13,7 @@ "dependencies": { "@tanstack/query-core": "^5.90.20", "@tanstack/query-db-collection": "^1.5.1", - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", "react": "^19.2.4", "react-dom": "^19.2.4" }, diff --git a/examples/react/next-ssr-e2e/package.json b/examples/react/next-ssr-e2e/package.json index 532513c3cb..a41a9a0070 100644 --- a/examples/react/next-ssr-e2e/package.json +++ b/examples/react/next-ssr-e2e/package.json @@ -9,8 +9,8 @@ "test:e2e": "pnpm --filter @tanstack/db build && pnpm --filter @tanstack/react-db build && playwright test" }, "dependencies": { - "@tanstack/db": "^1.0.0-rc.1.3", - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/db": "^1.0.0-rc.1.4", + "@tanstack/react-db": "^1.0.0-rc.1.4", "next": "^16.3.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index e02d0717e8..669496bb33 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^1.0.0-rc.1.3", - "@tanstack/db": "^1.0.0-rc.1.3", + "@tanstack/browser-db-sqlite-persistence": "^1.0.0-rc.1.4", + "@tanstack/db": "^1.0.0-rc.1.4", "@tanstack/offline-transactions": "^1.2.0", "@tanstack/query-db-collection": "^1.5.1", - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.170.41", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 08da2b1f5a..d89cd48733 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^1.0.0-rc.1.3", - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/db": "^1.0.0-rc.1.4", + "@tanstack/react-db": "^1.0.0-rc.1.4", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 5b950d88ab..45c2373b2d 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -18,7 +18,7 @@ "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", "@tanstack/query-db-collection": "^1.5.1", - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", "@tanstack/react-router": "^1.170.41", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/start-ssr-e2e/package.json b/examples/react/start-ssr-e2e/package.json index cb6dd0220b..cd15c6ec78 100644 --- a/examples/react/start-ssr-e2e/package.json +++ b/examples/react/start-ssr-e2e/package.json @@ -10,7 +10,7 @@ "test:e2e:hosted": "playwright test" }, "dependencies": { - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", "@tanstack/react-router": "^1.170.41", "@tanstack/react-router-with-db": "^1.0.0-rc.1", "@tanstack/react-start": "^1.168.60", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index de029e5d10..89fe2561c9 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.31", "dependencies": { - "@tanstack/electric-db-collection": "^1.0.0-rc.1.3", + "@tanstack/electric-db-collection": "^1.0.0-rc.1.4", "@tanstack/query-core": "^5.90.20", "@tanstack/query-db-collection": "^1.5.1", - "@tanstack/react-db": "^1.0.0-rc.1.3", + "@tanstack/react-db": "^1.0.0-rc.1.4", "@tanstack/react-router": "^1.170.41", "@tanstack/react-start": "^1.168.60", - "@tanstack/trailbase-db-collection": "^1.0.0-rc.1.3", + "@tanstack/trailbase-db-collection": "^1.0.0-rc.1.4", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index 3eafe48234..9d164d7ca0 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.39", "dependencies": { - "@tanstack/electric-db-collection": "^1.0.0-rc.1.3", + "@tanstack/electric-db-collection": "^1.0.0-rc.1.4", "@tanstack/query-core": "^5.90.20", "@tanstack/query-db-collection": "^1.5.1", - "@tanstack/solid-db": "^1.0.0-rc.1.3", + "@tanstack/solid-db": "^1.0.0-rc.1.4", "@tanstack/solid-router": "^1.170.38", "@tanstack/solid-start": "^1.168.57", - "@tanstack/trailbase-db-collection": "^1.0.0-rc.1.3", + "@tanstack/trailbase-db-collection": "^1.0.0-rc.1.4", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 18843290a6..b94413f3fd 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 3ac4da5f7b..91dc250208 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index b54660cd04..887d81afab 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index f33cd2e3eb..dcd9d1c913 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index c5119b956f..cc9e22aee3 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 979bd32541..98676eaf4b 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index f9e748cd08..d96a14347e 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 228860eb70..f1bdc577ba 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/convex-db-collection/CHANGELOG.md b/packages/convex-db-collection/CHANGELOG.md index d7a9621819..927c1a91f7 100644 --- a/packages/convex-db-collection/CHANGELOG.md +++ b/packages/convex-db-collection/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/convex-db-collection +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + - @tanstack/offline-transactions@1.2.0 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/convex-db-collection/package.json b/packages/convex-db-collection/package.json index 88d3632a24..71b36602e0 100644 --- a/packages/convex-db-collection/package.json +++ b/packages/convex-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/convex-db-collection", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Convex collection for TanStack DB", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index d7714f15ac..d64a31a8d0 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 38267b2ba4..61a06f899a 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index 4a7a15493f..0ce0c3aa5a 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/db +## 1.0.0-rc.1.4 + +### Patch Changes + +- Restore nested-row query performance by keeping source payloads opaque during metadata cleanup and resolving includes along compiled paths. Intermediate projections, functional callback inputs, inline results and Effect outputs retain authored fields, while published Collection rows receive genuine computed metadata. ([#2356](https://github.com/TanStack/db/pull/2356)) + + Preserve child identities through outer joins and fan-out, and retain aggregate result types through expression wrappers. + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 421ce711aa..011a7904dd 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", 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 cd25d6a81b..da9c76d130 100644 --- a/packages/db/tests/query/aggregate-select-oracle.property.test.ts +++ b/packages/db/tests/query/aggregate-select-oracle.property.test.ts @@ -170,10 +170,12 @@ import { declareOracle } from '../oracle-declaration.js' * differ. Each campaign first runs twelve complete structural histories through * this same model, driver, and comparison. They guarantee the 21 initial * premises, including string ordering, independently of the seed, with no - * mutations, shadowed alias, or union. A separate reach requires the 160 + * mutations, shadowed alias, or union. A separate reach requires the * generated histories themselves to enter those states and the mutation and - * route checkpoints below. Their coverage is sampled: a miss fails with the - * selected campaign seed and counts, rather than borrowing structural reach. + * route checkpoints below. A generated prefix constructs one history for each + * structural family, varying literals, order direction and legal mutations. + * The remaining 160 histories explore the broad grammar. Neither generated + * reach nor structural reach can borrow the other's observations. * * 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`, @@ -1681,6 +1683,50 @@ const structuralHistories = [ const examples = structuralHistories.map((entry) => [entry] as [History]) const structuralInputs = new Set(structuralHistories) +/** + * Required premises are constructive, rather than a lottery at campaign end. + * Each family varies its authored literal, ordering direction and source/parent + * writes. The final family also crosses union and shadowed-alias boundaries. + * These generated examples use the campaign seed and the ordinary comparison; + * they add to the broad run budget and remain available to seed/path replay. + */ +const requiredHistories = fc.tuple( + ...structuralHistories.map((template) => + fc + .record({ + value: literal, + direction: fc.constantFrom(`asc` as const, `desc` as const), + route: fc.constantFrom(`b`, `A`), + shadow: fc.boolean(), + union: fc.boolean(), + tail: fc.array(step, { maxLength: 2 }), + }) + .map(({ value, direction, route, shadow, union, tail }): History => { + const vary = (s: Shape): Shape => ({ + ...s, + fields: { ...s.fields, generated: { k: `lit`, n: value } }, + orderBy: s.orderBy?.map((term) => ({ ...term, dir: direction })), + }) + return { + ...template, + top: vary(template.top), + nested: vary(template.nested), + shadow: template.grouping === `triple` || shadow, + union: + template.grouping === `triple` || + (template.top.include === undefined && union), + steps: [ + { t: `parent`, id: 1, x: value }, + { t: `insert`, k: route, v: value }, + { t: `update`, pick: 0, k: route, v: value }, + { t: `delete`, pick: 0 }, + ...tail, + ], + } + }), + ), +) + /** The states the operator laws need; each campaign must reach all of them. */ const reachStates = [ `bare include: field`, @@ -1736,6 +1782,9 @@ async function runCampaign( [...reachStates, ...generatedCheckpoints], ) try { + const generatedExamples = fc + .sample(requiredHistories, { seed: parameters.seed, numRuns: 1 })[0]! + .map((entry) => [entry] as [History]) await fc.assert( fc.asyncProperty(history, async (entry) => { const fixed = structuralInputs.has(entry) @@ -1743,7 +1792,14 @@ async function runCampaign( reach.beginHistory({ fixed }) await runHistory(entry) }), - parameters, + { + ...parameters, + examples: [...parameters.examples, ...generatedExamples], + numRuns: + parameters.path === undefined + ? parameters.numRuns + generatedExamples.length + : parameters.numRuns, + }, ) structuralReach.assertReached() generatedReach.assertReached() @@ -1771,7 +1827,7 @@ async function runCampaign( declareOracle({ check: [runHistory], model: [modelGroup, modelError], - grammar: [history], + grammar: [history, requiredHistories], }) describe(`aggregate select shapes`, () => { diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 6cacd52cb2..c24d3ead8b 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index 96eba10373..71d057d8c2 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 61394b3ea9..2659a4ddd0 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index f02ff86276..8ac991d2de 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index 88fc0063f3..1f2376bada 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index aed14d752f..a7251a0ba2 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index 6b8d89881d..a676a6043a 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index a77b1d6c65..73b9db9900 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index c83461bce7..c18b81870a 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 325a95acff..ea84de54b7 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/preact-db/CHANGELOG.md b/packages/preact-db/CHANGELOG.md index 5398038149..ac65afe141 100644 --- a/packages/preact-db/CHANGELOG.md +++ b/packages/preact-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/preact-db +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/preact-db/package.json b/packages/preact-db/package.json index 019f5651e1..7ac6322c1c 100644 --- a/packages/preact-db/package.json +++ b/packages/preact-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/preact-db", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Preact integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index 734ce1541d..eddcaff5c3 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 2e883f08ea..87de33affe 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 9f3c673355..aacd93f8d2 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index ec7862a4c7..ddadedb046 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index e0027c85ae..58239a74b8 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index c1d00f2275..0d7a465c2c 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 62e86e52b8..37eb1c642b 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index ca3d80fee5..eae4dc0eb7 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index aa32679530..589092cc22 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index a0fc01b0a5..0c7f0119af 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 6510e1240f..66b7ff36ca 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index af6af93035..d0620eef15 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index 30380afeec..04d3baf0df 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index e4726d07fe..aa51670285 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 4bf5a785d9..82183163bc 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 1.0.0-rc.1.4 + +### Patch Changes + +- Updated dependencies [[`bfb660a`](https://github.com/TanStack/db/commit/bfb660a1e8074064cd1fd93a645273c17aaec98c)]: + - @tanstack/db@1.0.0-rc.1.4 + ## 1.0.0-rc.1.3 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index b6089dd545..163b62de67 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "1.0.0-rc.1.3", + "version": "1.0.0-rc.1.4", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db794bcdab..18f987c198 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,10 +168,10 @@ importers: specifier: ^20.3.34 version: 20.3.34(@angular/common@20.3.34(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.34(@angular/common@20.3.34(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.34(@angular/compiler@20.3.34)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -208,10 +208,10 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': specifier: ^1.2.0 @@ -220,7 +220,7 @@ importers: specifier: ^1.5.1 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -299,7 +299,7 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/db '@tanstack/offline-transactions': specifier: ^1.2.0 @@ -308,10 +308,10 @@ importers: specifier: ^1.5.1 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -396,19 +396,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.7)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': specifier: ^1.2.0 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -487,7 +487,7 @@ importers: specifier: ^1.5.1 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db react: specifier: ^19.2.4 @@ -536,10 +536,10 @@ importers: examples/react/next-ssr-e2e: dependencies: '@tanstack/db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db next: specifier: ^16.3.1 @@ -570,10 +570,10 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/db '@tanstack/offline-transactions': specifier: ^1.2.0 @@ -582,7 +582,7 @@ importers: specifier: ^1.5.1 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -640,10 +640,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -683,7 +683,7 @@ importers: specifier: ^1.5.1 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.170.41 @@ -813,7 +813,7 @@ importers: examples/react/start-ssr-e2e: dependencies: '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.170.41 @@ -859,7 +859,7 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 @@ -868,7 +868,7 @@ importers: specifier: ^1.5.1 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.170.41 @@ -877,7 +877,7 @@ importers: specifier: ^1.168.60 version: 1.168.60(esbuild@0.28.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.64.12)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1))(webpack@5.112.0(esbuild@0.28.1)(lightningcss@1.33.0)) '@tanstack/trailbase-db-collection': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -980,7 +980,7 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 @@ -989,7 +989,7 @@ importers: specifier: ^1.5.1 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.170.38 @@ -998,7 +998,7 @@ importers: specifier: ^1.168.57 version: 1.168.57(@tanstack/react-router@1.170.41(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(esbuild@0.28.1)(rollup@4.64.12)(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.7(@types/node@25.2.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.1))(webpack@5.112.0(esbuild@0.28.1)(lightningcss@1.33.0)) '@tanstack/trailbase-db-collection': - specifier: ^1.0.0-rc.1.3 + specifier: ^1.0.0-rc.1.4 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 diff --git a/scripts/bench/README.md b/scripts/bench/README.md index 4924fff81b..d0f1be889e 100644 --- a/scripts/bench/README.md +++ b/scripts/bench/README.md @@ -21,8 +21,16 @@ The nested-document case joins all open issues to 20-block documents and selects whole documents without a limit. Synced updates and optimistic updates with rollback run at all scales and index modes, like the existing flat cases. -CI runs the same benchmark code on the candidate and merge base on one runner. -It fails for either of these conditions: +CI compares benchmark inputs before installing or measuring. Package versions, +changelogs, and workspace dependency specifiers whose resolved `link:` target +is unchanged do not affect that comparison. Runtime, dependency resolution, +configuration, workflow, and harness changes still run the gate. A manual run +always measures. Input-selection errors fail the job; branch names never grant +an exemption. Run `node --test scripts/bench/inputs-changed.test.mjs` to check +this selection. + +When inputs differ, CI runs the same benchmark code on the candidate and merge +base on one runner. It fails for either of these conditions: - A query's geometric mean across its configurations increases by more than 30% for writes, or 50% for cold hydration. Improvements in another query or diff --git a/scripts/bench/inputs-changed.mjs b/scripts/bench/inputs-changed.mjs new file mode 100644 index 0000000000..11504768c5 --- /dev/null +++ b/scripts/bench/inputs-changed.mjs @@ -0,0 +1,76 @@ +import { execFileSync, spawnSync } from 'node:child_process' +import { fileURLToPath } from 'node:url' + +// Compare inputs, not branch names: a release PR with a runtime change must +// still run the benchmark. Local workspace specifiers do not change the code +// that the installed link supplies; external resolutions remain significant. +export function normalizeBenchmarkInput(path, content) { + if (content === null) return null + if (path.endsWith('/CHANGELOG.md')) return '' + if (path.endsWith('package.json')) { + const { version: _version, ...manifest } = JSON.parse(content) + return JSON.stringify(manifest) + } + if (path === 'pnpm-lock.yaml') + return content.replace( + /^( +)specifier: .*\n\1version: (link:.*)$/gm, + '$1specifier: workspace\n$1version: $2', + ) + return content +} + +export function changedBenchmarkInputs(base, head) { + const paths = execFileSync( + 'git', + [ + 'diff', + '--name-only', + '-z', + base, + head, + '--', + 'packages/db', + 'packages/db-ivm', + 'scripts/bench', + 'package.json', + 'pnpm-lock.yaml', + 'pnpm-workspace.yaml', + '.npmrc', + 'patches', + '.github/workflows/benchmark.yml', + ':(glob)*config*', + ], + { encoding: 'utf8' }, + ) + .split('\0') + .filter(Boolean) + const read = (ref, path) => { + const result = spawnSync('git', ['show', `${ref}:${path}`], { + encoding: 'utf8', + }) + if (result.status === 0) return result.stdout + if ( + result.status === 128 && + /does not exist|exists on disk, but not in/.test(result.stderr) + ) + return null + throw new Error(`Cannot read ${ref}:${path}: ${result.stderr}`) + } + return paths.filter( + (path) => + normalizeBenchmarkInput(path, read(base, path)) !== + normalizeBenchmarkInput(path, read(head, path)), + ) +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const [base, head] = process.argv.slice(2) + if (!base || !head) throw new Error('Usage: inputs-changed.mjs BASE HEAD') + const changed = changedBenchmarkInputs(base, head) + console.log(`changed=${changed.length > 0}`) + console.error( + changed.length > 0 + ? `Benchmark inputs changed: ${changed.join(', ')}` + : 'Benchmark inputs are unchanged; no latency comparison is needed.', + ) +} diff --git a/scripts/bench/inputs-changed.test.mjs b/scripts/bench/inputs-changed.test.mjs new file mode 100644 index 0000000000..18ee136ba7 --- /dev/null +++ b/scripts/bench/inputs-changed.test.mjs @@ -0,0 +1,79 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { normalizeBenchmarkInput } from './inputs-changed.mjs' + +test('package version bumps preserve benchmark inputs', () => { + const manifest = { + name: '@tanstack/db', + version: '1.0.0-rc.1.3', + dependencies: { helper: '1.0.0' }, + } + assert.equal( + normalizeBenchmarkInput( + 'packages/db/package.json', + JSON.stringify(manifest), + ), + normalizeBenchmarkInput( + 'packages/db/package.json', + JSON.stringify({ ...manifest, version: '1.0.0-rc.1.4' }), + ), + ) + assert.notEqual( + normalizeBenchmarkInput( + 'packages/db/package.json', + JSON.stringify(manifest), + ), + normalizeBenchmarkInput( + 'packages/db/package.json', + JSON.stringify({ ...manifest, dependencies: { helper: '2.0.0' } }), + ), + ) +}) + +test('only linked workspace specifiers are ignored in the lockfile', () => { + const lock = (specifier, version) => + ` '@tanstack/db':\n specifier: ${specifier}\n version: ${version}\n` + const normalize = (text) => normalizeBenchmarkInput('pnpm-lock.yaml', text) + assert.equal( + normalize(lock('^1.0.0-rc.1.3', 'link:../../db')), + normalize(lock('^1.0.0-rc.1.4', 'link:../../db')), + ) + assert.notEqual( + normalize(lock('^1.0.0', '1.0.0')), + normalize(lock('^2.0.0', '1.0.0')), + ) + assert.notEqual( + normalize(lock('^1.0.0', '1.0.0')), + normalize(lock('^1.0.0', '1.0.1')), + ) + assert.notEqual( + normalize(lock('*', 'link:../../db')), + normalize(lock('*', 'link:../../other')), + ) + assert.notEqual( + normalize(lock('*', 'link:../../db')), + normalize(lock('*', '1.0.0')), + ) +}) + +test('changelog edits are ignored while code, harness and missing inputs differ', () => { + assert.equal( + normalizeBenchmarkInput('packages/db/CHANGELOG.md', 'old'), + normalizeBenchmarkInput('packages/db/CHANGELOG.md', 'new'), + ) + for (const path of [ + 'packages/db/src/index.ts', + 'scripts/bench/incremental-update.ts', + 'pnpm-workspace.yaml', + '.github/workflows/benchmark.yml', + ]) { + assert.notEqual( + normalizeBenchmarkInput(path, 'old'), + normalizeBenchmarkInput(path, 'new'), + ) + assert.notEqual( + normalizeBenchmarkInput(path, null), + normalizeBenchmarkInput(path, ''), + ) + } +})