Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
34 changes: 31 additions & 3 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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: |
Expand All @@ -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 \
Expand All @@ -70,15 +98,15 @@ 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"
else
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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/contributing/oracle-coverage.md

Large diffs are not rendered by default.

156 changes: 156 additions & 0 deletions docs/contributing/oracle-reviews/2026-11-05-release-ci.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions examples/angular/todos/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions examples/electron/offline-first/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions examples/react-native/offline-transactions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions examples/react-native/shopping-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion examples/react/action-enforcement/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
4 changes: 2 additions & 2 deletions examples/react/next-ssr-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions examples/react/offline-transactions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions examples/react/paced-mutations-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion examples/react/projects/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion examples/react/start-ssr-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions examples/react/todo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading
Loading