Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/angular-query-snapshots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/angular-db': major
---

Require Angular 20 or later. Keep live-query result signals coherent with the current query before effects run, and release observer subscriptions through effect cleanup. Track the observer's row-state identity separately so unchanged row state does not rebuild the adapter's defensive Map copy.
39 changes: 39 additions & 0 deletions docs/contributing/oracle-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,28 @@ Angular's JIT test environment. It does not exercise Angular's AOT compiler,
older Angular runtimes, or every scheduler cut. The same driver checks that an
explicitly disabled query reports `disabled` and ready before effects run.

The [Angular snapshot conformance oracle](https://github.com/TanStack/db/blob/main/packages/angular-db/tests/snapshot-conformance-oracle.test.ts)
extends the framework owner to synchronous reads before effects run (maintainer
decision 2026-11-01, stated in the shared conformance contract). Generated
histories cross callback, params, config, and precreated Collection inputs;
list and `findOne` results; opt-in pooling through params; populated, empty,
and disabled selections; and source writes before attachment and while mounted.
An independent fixture-row filter judges selected rows. A refinement relation
also checks keyed state, data shape, Collection rows, status, readiness, and
health meaning at each read. Initial reads, input replacements before effects,
and settled source updates use the same checker. The original copied-signal
adapter fails at the first read; omitting post-subscribe invalidation fails
after a source write between an early read and attachment. Declared reach
measures those checkpoints, including direct Collection, single-result, and
pooled receiving paths. The oracle does not compare pooled view health-node
identities or Collection IDs with their lazily materialized public Collection:
`getPublicCollection` represents these as different objects; it compares health
meaning instead. This does not retire the shared suite's within-query health
identity checks. Limits: synchronous eager sources in Angular's JIT TestBed;
no browser rendering, SSR, asynchronous provider schedules, or new synchronous
read promise for another framework. Existing shared conformance retains
ownership of their settled behavior and teardown laws.

The query identity owner generates both equal and unequal primitive string
pairs. Its fixed pairs also check public `groupBy` rows after initial
publication. End-to-end subset-demand reuse is still unproven: the demand-key
Expand Down Expand Up @@ -7473,3 +7495,20 @@ requests while joined work defers a reentrant pass, then check the real
subscription receiving path. This remains separate from PR #2346's observer
metadata-sharing and discarded-publication scope. The test, grammar and run
budget remain unchanged; a green later random campaign does not close it.

## GC direct-start reach, 2026-11-04

The existing `collection-lifecycle.gc-retention-boundaries` owner now begins
each normal campaign with ten generated direct-start histories, then runs its
unchanged 250 mixed histories. This removes the known sampling gap where no
history starts sync through direct insertion. The model, comparisons, all
declared reach states, and both mixed run budgets remain intact. Explicit path
replays of the mixed property retain its original generator and skip the prefix.
The separately registered
`collection-lifecycle.gc-retention-boundaries.direct-start` property replays
only the prefix, with its own reach recorder. Both identities use the same
model, driver, and comparisons. Seed 706
reproduces the old gap and rejects omission of the prefix; the repaired fixed
and random campaigns reached the state 15 and 17 times. Existing controlled
idle-host and native-browser limits remain in force. See the
[CI repair evidence](oracle-reviews/2026-11-04-gc-startup-reach.md).
144 changes: 144 additions & 0 deletions docs/contributing/oracle-reviews/2026-11-01-angular-snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Angular synchronous snapshot evidence

This self-review covers the Angular changes on `angular-fixes`, based on
`95dbf663877cc0040ae8a502d34ee2448391379c`. The branch also includes the
effect-cleanup refactor and breaking Angular 20 minimum. Tested source SHA-256:
`94a4dddccbb3048fee3c0660319951159a530cdb899188b3519b3a467f1e0f70`.
Test SHA-256:
`730c8aa77c20435c7cf7fadff011e027c942bbbf1431e24538e63c02ee93266d`.

- **Law:** synchronous Angular reads describe the current query, including
before subscription effects run. Rows, keyed state, Collection, status, and
health must agree. The maintainer authorized this change on 2026-11-01;
`packages/db/tests/conformance/contract.ts` records its Angular-only cut.
- **Reach:** fixed and random campaigns each ran 36 histories: 16 shared
regressions plus 20 generated histories. In fixed/random order, initial reads
and pre-attachment source writes reached 36/36 histories each; direct
Collections 9/7; findOne 18/17; pooled reads 2/2; enabled replacements 22/26;
disabled reads 15/16; re-enabling 12/13; settled updates 36/36.
- **RED:** from `packages/angular-db`, run
`../../node_modules/.bin/vitest --run tests/snapshot-conformance-oracle.test.ts`
with the adapter from the base commit. Both campaigns fail by row assertion
before attachment: `data()` is empty while `collection()` contains rows.
Fixed seed `1101`, shrink path `0:0:0:0` reduces to callback/list/unpooled,
initial value 0, selections `[0, 0]`.
Direct replay with `TANSTACK_DB_ORACLE_PROPERTY=angular.current-snapshot`,
`TANSTACK_DB_ORACLE_SEED=1101`, and `TANSTACK_DB_ORACLE_PATH=0:0:0:0`
executes only that history and reproduces the row mismatch on the base adapter.
- **GREEN:** the same command passes with the snapshot adapter. The whole
package passes 97 tests with one existing TODO. `tsc --noEmit -p
packages/angular-db/tsconfig.json`, changed-file lint, ESLint for changed
TypeScript, and formatting checks pass.
- **Mutant:** deleting the post-subscribe `invalidate()` preserves the early
cached value after a source write before attachment. Both campaigns fail the
row comparison after effect setup; this is an assertion failure after the
driver's bounded wait, not the test runner's timeout. Seed `1101`, path
`0:0:2:1:0` reduces to the same two-selection history.
- **Limits:** three synchronous eager fixture rows, finite equality groups,
one ordered query, no joins/includes, and Angular JIT TestBed. No SSR,
browser-rendering, asynchronous-provider, or cross-framework synchronous
claim. Existing shared conformance remains the owner for settled behavior,
errors, required inputs, and teardown. Pooled view and materialized public
Collection IDs/health-node identities differ by design; the new oracle
compares health meaning across them, preserving existing identity checks.

The size comparison against the base commit reports 1023 to 881 gzip bytes
(-142 bytes), and -134 brotli bytes for the Angular package. This is the
reactivity patch's production bundle, excluding dependency bundles and tests.

## Oracle rubric assessment

| Requirement | Evidence or limit |
| --- | --- |
| ORC-001 | Shared contract, maintainer authority, and limits above and beside the oracle. |
| ORC-002 | Fixture-row filtering supplies membership; public Collection observations supply the snapshot-coherence relation. No adapter state or query evaluator computes expected membership. |
| ORC-003 | Opening contract, pure row model, legal histories, real Angular driver, and shared comparison remain distinguishable in the test. |
| ORC-004 | Regressions reconstruct populated swap, disable, empty re-enable, and return. Removing forms, shape, pooling, or the pre-attachment write loses a named path. Groups are 0/1/empty-2/null; initial values are -2 through 2; histories have 2 through 6 selections. Updates after destruction are outside the legal grammar. |
| ORC-005 | Public injectLiveQuery and real environment-injector effects; reads before effects, after attachment, and after mounted writes. Source rows already exist at the synchronous cut. |
| ORC-006 | Original copied-signal adapter and missing-invalidation mutant fail row assertions. |
| ORC-007 | Fixed seed 1101 and unseeded campaigns share options, regressions, grammar, checker, and budget. Shared oracle replay variables select the property and shrink path. |
| ORC-008 | No adapter state machine is mirrored: membership is stateless filtering of fixture rows. |
| ORC-009 | Uses glossary terms. Health meaning explicitly omits identity across pooled view/materialization, not within one query. |
| ORC-010 | ScenarioLifetime preserves primary and cleanup failures and ends the injector before source cleanup. |
| ORC-011 | No named shared semantic fault requiring another formulation; numeric equality on three fixture rows is independent of adapter lifecycle code. |
| ORC-012 | This record describes the tested patch and bounded evidence, not universal correctness. |
| ORC-013 | Populated versus empty/disabled selections and a write between read/attach distinguish stale-data and missed-invalidation designs. |
| ORC-014 | The receiving boundary is Angular's actual JIT scheduler. External-provider and browser claims remain outside scope. |
| ORC-015 | createOracleReach checks all nine declared receiving states; counts above. |
| ORC-016 | Fixed regressions and both campaigns share the same model, driver, and comparison; no separate expected answers. |

## Stability, work, and the separate observer PR

The diagnostic `snapshot-performance.test.ts` instruments real Angular computed
executions and core snapshot reads without replacing snapshot behavior. It
tracks container identities across warmup and measurement phases, so reading an
existing container through another snapshot is not counted as a new allocation.
For 1000 synchronous rows, unchanged reads reuse the same core snapshot, data,
and Map and execute no additional Angular computations. Ten row writes before
reading build one full snapshot; reading after every write builds ten.

This branch includes an intermediate `rowState` computed. Angular's defensive
Map copy follows the observer's row-state reference rather than every full
snapshot. Against the current main-branch observer, metadata snapshots still
supply new row containers, so health reports still rerun row consumers. Keeping
those containers stable belongs to the independent `observer-snapshot-caching`
branch. This PR does not require that change or promise metadata-only identity
that the current core cannot supply. Both branches together were exercised by
the generated Angular metadata receiving checks before the split; those
integration results do not stand in for either branch's standalone evidence.

The defensive copy remains because the public Angular state type is mutable;
this PR does not expose the observer's cached Map as writable state. Scalar
field computations may reevaluate after a notification while unchanged
readiness/Collection consumers and the readiness effect skip execution. These
are finite work counts, not heap bytes, elapsed runtime, or browser rendering.

Validation uses Node 24.8.0 from `.nvmrc` and pnpm 11.1.0 from `package.json`.
The changeset declares a major Angular package change because raising the
minimum supported framework version is breaking. No observer production code,
observer cache changeset, or core metadata-identity requirement belongs to this
branch.

## CI replay registration follow-up, 2026-11-04

CI exposed a missing entry in `oracle-replay-manifest.ts` for the registered
`angular.current-snapshot` property. The oracle itself passed in the framework
job, but the central replay-owner check correctly rejected an unowned name.
The fix maps it to `angular-db/tests/snapshot-conformance-oracle.test.ts`.

Before the fix, `vitest run tests/oracle-replay.test.ts -t 'names an owner for
every registered property' --coverage.enabled=false` failed with
`['angular.current-snapshot']` instead of an empty unowned-property list. After
the fix, the complete replay suite passed all 41 tests. The guarded replay from
`packages/angular-db` also passed with property `angular.current-snapshot`, seed
1101 and path `0`: one completed matching witness and one execution, with the
fixed campaign skipped. The replay guard observed the actual Angular owner,
rather than merely accepting a manifest entry. The existing oracle model,
grammar, assertions and run budgets did not change.

Node 24.8.0 was used. This follow-up changes replay discovery only and adds no
new Angular behavior or observation checkpoint.

## Main integration, 2026-11-04

The merge combines Angular branch `c098d577d241042bc247ac41a770b85e50c0b48f`
with main `8feab6ea3eaae4456f738da39fa93508ddd5cc01`. Main added ordered-window
reuse and shared result-shape/window-history receiving tests after the original
PR base. The resolution retains main's `resolveLiveQueryValue` window ownership
and deferred move effect, and the PR's synchronous observer snapshot reads and
effect-owned subscription cleanup. The conformance-driver comment now names
both its pre-effect shape reads and its effect cleanup accurately.

All replay property/owner entries and both sets of coverage-map additions are
retained. No oracle model, assertion, history grammar, run budget, or expected
result was changed to resolve these conflicts. The merge commit first adding
this section binds the evaluated combined source.

Validation on Node 24.8.0: the full Angular suite passed 129 tests with one
existing TODO, including window history, result shape, and the PR's current-query
snapshot campaigns. Angular typecheck, build, focused ESLint and `lint:changed`
passed. The replay manifest's owner, file mapping and command-location checks
passed (three selected tests), and guarded `angular.current-snapshot` replay
1101/path `0` executed one matching witness. The GC seed-706 campaign passed and
reached `write-started-sync` ten times. Existing SSR/browser and controlled-host
limits remain unchanged; this resolution adds no client/SSR provider code.
Loading
Loading