Skip to content

feat(PowerSync): add attachments support - #9

Open
Chriztiaan wants to merge 141 commits into
mainfrom
feat/powersync-attachments
Open

Chriztiaan wants to merge 141 commits into
mainfrom
feat/powersync-attachments

Conversation

@Chriztiaan

@Chriztiaan Chriztiaan commented Jun 18, 2026

Copy link
Copy Markdown

Derived from Steven's efforts in powersync-ja/powersync-js#983, and addresses TanStack#1563.

Problem

PowerSync ships an attachment helper for syncing files (photos, documents) between local and remote storage. It's separate from regular synced tables: a local-only attachments table tracks each file's lifecycle (QUEUED_UPLOAD, SYNCED, QUEUED_DELETE), and an AttachmentQueue drives uploads/downloads in the background.

TanStackDB, on the other hand, gives you an optimistic, reactive, joinable view over synced data. For users who want to use the attachment helper alongside the PowerSync+TanstackDB integration there are blockers. Saving a file (in the local-only attachments table) and associating it with a record (e.g. setting user.photo_id) are two independent writes which could make data races and fatal errors a problem for data consistency.

The original POC (powersync-js#983) proved this integration was viable. This PR productionises a a subset of it as reusable functionality.

Solution

A TanStackDBAttachmentQueue that extends the SDK's AttachmentQueue (for saving and deleting a file) and backs it with a TanStack DB collection.

The package owns the collection-backed saveFile/delete implementation and leaves the wiring to the application (covered in documentation).

Future Work

After this has been released, we can merge the changes made to the PowerSync JS TanstackDB demo.

AI Disclosure

I used Claude Opus to help investigate, implement, and verify this work.

kevin-dp and others added 7 commits June 17, 2026 14:42
…#1593)

* fix(db): use safe randomUUID helper for non-secure browser contexts (TanStack#1541)

* fix(db-sqlite-persistence-core): use safe randomUUID helper (TanStack#1541)

* fix(browser-db-sqlite-persistence): use safe randomUUID helper (TanStack#1541)

* fix(electron-db-sqlite-persistence): use safe randomUUID helper (TanStack#1541)

* fix(offline-transactions): use safe randomUUID helper (TanStack#1541)

* ci: apply automated fixes

* refactor: rename randomUUID helper to safeRandomUUID and add crypto-undefined test

---------

Co-authored-by: Kevin De Porre <kevin-dp@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…ck#1151)

* feat(db): add subtract, multiply, divide math functions

Add missing math functions that were implemented in evaluators but not
exported. These enable computed columns in orderBy for ranking algorithms
like HN-style scoring that balances recency and rating.

- Add subtract(a, b) function
- Add multiply(a, b) function
- Add divide(a, b) function (with null on divide-by-zero)
- Export from query/index.ts
- Add to operators list
- Add comprehensive tests including orderBy usage

* docs: document subtract, multiply, divide math functions

- Add documentation for new math functions in live-queries.md
- Include example of computed columns in orderBy for ranking algorithms
- Add changeset for the new minor feature

* ci: apply automated fixes

* fix: align math function return types

* ci: apply automated fixes

* docs: clarify ranking snapshot semantics

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@Chriztiaan
Chriztiaan marked this pull request as ready for review June 23, 2026 12:02
@Chriztiaan Chriztiaan changed the title feat: add attachments support feat(PowerSync): add attachments support Jun 23, 2026

@stevensJourney stevensJourney left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I'm happy with the approach here. Left a few comments, mostly nits.

Comment thread packages/powersync-db-collection/src/attachments.ts Outdated
Comment thread packages/powersync-db-collection/src/attachments.ts Outdated
Comment thread docs/collections/powersync-collection.md Outdated
kevin-dp and others added 3 commits June 23, 2026 10:58
* test: cover re-preloading a live query after cleanup

Add a regression test asserting that a live query loads its data again
when it is preloaded after the live query and its source collection were
cleaned up (e.g. when switching data sets at runtime).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: reset live query error state on sync restart so preload recovers after cleanup (TanStack#1576)

A source collection that is cleaned up while a live query depends on it
pushes the live query into an error state and latches `isInErrorState`.
That flag was never reset, so when sync restarted via preload() after
cleanup (e.g. switching profiles without a page refresh),
updateLiveQueryStatus() returned early, markReady() was never called and
the preload promise hung forever.

Reset `isInErrorState` at the start of each sync session so the live
query can become ready again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@autofix-troubleshooter

Copy link
Copy Markdown

Hi! I'm the autofix logoautofix.ci troubleshooter bot.

It looks like you correctly set up a CI job that uses the autofix.ci GitHub Action, but the autofix.ci GitHub App has not been installed for this repository. This means that autofix.ci unfortunately does not have the permissions to fix this pull request. If you are the repository owner, please install the app and then restart the CI workflow! 😃

Comment thread packages/powersync-db-collection/src/attachments.ts
stevensJourney
stevensJourney previously approved these changes Jun 24, 2026
github-actions Bot and others added 8 commits June 25, 2026 08:50
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…sage (TanStack#1518)

The README described the package as "Phase 7 single-tab browser wiring",
which suggested multi-tab was not supported. In fact, single-tab is just
the default — passing a `BrowserCollectionCoordinator` via the
`coordinator` option enables multi-tab coordination today.

Drop the internal phase reference, list `BrowserCollectionCoordinator` in
the public API, split the quick start into single-tab and multi-tab
sections, and link to the offline-transactions example for the multi-tab
case.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#1580)

- Add a "materialize" subsection to the Includes guide in live-queries.md
  with array vs singleton (findOne) examples and notes on reactivity and
  expression-context restrictions.
- Add the materialize() entry to docs/reference/index.md. The generated
  functions/materialize.md page is produced by the release workflow's
  generate-docs step, so it is not committed here.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ry + regression test (closes TanStack#1587) (TanStack#1594)

* fix(react-db): defer eager onStoreChange to a microtask in useLiveQuery

Closes TanStack#1587.

`useLiveQuery`'s `subscribeRef` calls `onStoreChange()` synchronously
inside the `useSyncExternalStore` subscribe function when the
underlying collection is already `ready`. That synchronous notification
lands during the render-to-commit window when subscribe runs under
StrictMode double-render or cold/throttled loads, which React surfaces
as:

  Can't perform a React state update on a component that hasn't
  mounted yet. This indicates that you have a side-effect in your
  render function that asynchronously tries to update the component.
  Move this work to useEffect instead.

The fix is to defer the eager notification to a microtask so it lands
after the current commit. While doing so, also guard the late notify
path against an in-flight `subscribeChanges` callback firing after
React unsubscribes — track a local `unsubscribed` flag and drop both
the eager microtask and any in-flight subscription event after teardown,
so React never sees a state update post-unsubscribe.

No public API change; the contract of `useLiveQuery` is preserved (an
already-ready collection still notifies React once after mount, just
asynchronously instead of mid-commit).

Verified `pnpm test` in packages/react-db — 94/94 pass, no type
errors. Existing tests don't cover the race directly (it's a
StrictMode-double-render / cold-load condition observed via Lighthouse
in the issue), so the existing suite is the regression guard for
existing behavior and the issue's repro is the behavioral validation.

* test(react-db): add regression test for useLiveQuery eager onStoreChange (TanStack#1587)

Captures the subscribe callback that useLiveQuery passes to
React.useSyncExternalStore and asserts that onStoreChange is not invoked
synchronously when the collection is already in the 'ready' state — it
is instead deferred to a microtask. Without the fix, the eager notify
lands during the render-to-commit window and React surfaces:

  Can't perform a React state update on a component that hasn't mounted
  yet. ... Move this work to useEffect instead.

* chore(react-db): tighten comments around deferred onStoreChange

* chore(react-db): drop TanStack#1587 reference from comment

* chore(react-db): drop issue refs from eager-onStoreChange test

---------

Co-authored-by: tsushanth <78000697+tsushanth@users.noreply.github.com>
Co-authored-by: Kevin <kevin-dp@users.noreply.github.com>
…ck#1584) (TanStack#1595)

* test: reproduce prototype pollution via select() alias (TanStack#1584)

Adds a failing test demonstrating that .select() alias paths like
`__proto__.polluted` or `constructor.prototype.polluted` are split
on '.' and walked into the result object without sanitization,
allowing prototype pollution through queryOnce().

This commit intentionally fails CI to demonstrate the vulnerability;
the next commit fixes it.

* fix(db): reject unsafe alias path segments in select() compiler

Adds a new `UnsafeAliasPathError` (extends QueryCompilationError) and an
`assertSafeAliasSegments` helper invoked in three places in
packages/db/src/query/compiler/select.ts:

- `addFromObject` validates each non-spread key at compile time, including
  dotted keys, before recording any select operation.
- `processNonMergeOp` validates the split alias path before walking into
  the result object.
- `processMerge` validates `targetPath` for the same reason.

Segments matching `__proto__`, `prototype`, or `constructor` are rejected,
which prevents prototype pollution via aliases like `__proto__.polluted`
or `constructor.prototype.polluted` going through queryOnce() /
createLiveQueryCollection().

Fixes TanStack#1584

* ci: apply automated fixes

* test: address CodeRabbit review on prototype-pollution tests

- Import UnsafeAliasPathError and assert that the rejection is exactly
  that error class instead of a permissive .rejects.toThrow().
- Drop the `({} as any).polluted` pattern in favour of
  Object.prototype.hasOwnProperty.call(Object.prototype, 'polluted'),
  which is type-safe and a more explicit assertion that
  Object.prototype itself was not mutated.

* chore: add changeset for TanStack#1584 fix

* ci: apply automated fixes

* test: move prototype-pollution tests into select.test.ts

Fold the select() alias prototype-pollution cases into the existing
select.test.ts integration suite, reusing its createUsers() fixture,
and drop the standalone select-prototype-pollution.test.ts file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: drop issue number from prototype-pollution describe

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: kevin-dp <kevin-dp@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ves to (TanStack#1614)

* test: lazy-join index warning must not blame an already-indexed collection

Add a failing reproduction: when a subquery used in a JOIN clause selects
its join key from the joined side of the subquery, the outer join key
resolves to a collection that is already indexed. The lazy-join loader
should load through that index, but today it emits a "Join requires an
index" warning naming the already-indexed collection (and falls back to a
full load). Data is still correct via the fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: apply automated fixes

* test: fix BTreeIndex import path and collection typing

Import BTreeIndex from src/indexes/btree-index.js (not collection/index)
and type the collections via factory-wrapper ReturnType so the test passes
typecheck. The index-warning assertion still fails as intended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(query): drive lazy-join loading through the resolved collection

When a subquery used in a JOIN clause selects its join key from a joined
source rather than its own from clause, followRef traced the index
requirement to the resolved collection while the lazy loader still
subscribed to the subquery's from alias. The two diverged, producing a
misleading "Join requires an index" warning that named an already-indexed
collection and an unnecessary full-load fallback.

followRef now also returns the alias of the source the ref resolves to;
getLazyLoadTargets uses it as the subscription alias (falling back to the
from-clause remapping when the key resolves directly to the from source),
so lazy loading drives through the correct collection's index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: apply automated fixes

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…anStack#1582)

* test: add failing tests for index-optimized queries mixing indexed and non-indexed conditions

These tests assert the expected results of currentStateAsChanges for
AND/OR where clauses that combine conditions on indexed fields with
conditions that cannot be served by an index. They currently fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: add failing tests for range query boundary handling

Adds expected-behaviour tests for range conditions:
- compound ranges sharing a boundary value must apply the strictest
  bound regardless of argument order, including for date values
- one-sided compound ranges must return the matching rows
- strict comparisons (gt) on date fields must exclude the boundary row

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: add failing test for compound range query with undefined bound

A compound range condition where one bound is undefined (e.g.
gt(score, undefined) AND lt(score, 90)) must match nothing, since a
comparison against undefined is never true. The index-optimized path
must agree with a full scan. This test currently fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add failing tests for nullish values in indexed eq/in/range queries

A comparison against null/undefined is never true, but BTree indexes
store and return rows with nullish indexed values (they sort as the
smallest key). These tests assert that the index-optimized snapshot
matches a full predicate scan for:
- eq against undefined
- IN with an undefined member
- a range comparison over a field that has rows with undefined values
- an upper-bounded compound range over such a field

They currently fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add failing tests for locale string range and NaN index queries

Two more cases where an index-optimized snapshot must match a full
predicate scan:
- a string range predicate (e.g. name > 'z') must return a row whose
  value satisfies the JS relational comparison ('ö' > 'z'), even though
  a locale-collated index orders that value differently
- eq and IN against NaN must not match a NaN-valued row, since NaN is
  never equal to itself

They currently fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add failing tests for range predicates over non-orderable index domains

Three more cases where an index-optimized range query must match a
full predicate scan:
- an array-valued field (the evaluator compares with standard relational
  operators, which differ from the index's recursive array ordering)
- a field indexed with a custom comparator
- a numeric field that also contains a NaN value

They currently fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add failing tests for ordering values that have no natural order

NaN and invalid Dates have no natural order. They should still get a
consistent, well-defined position (alongside nulls) so that:
- the comparator produces a stable total order;
- ordering a collection by such a field is deterministic;
- a range query on a field that contains such a value can still be
  served by the index rather than falling back to a full scan.

These tests currently fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: enforce all where conditions when index optimization is partial

OR expressions now require every disjunct to be index-optimizable;
otherwise the query falls back to a full scan, since rows matched only
by a non-optimizable disjunct cannot be recovered from index lookups.

AND expressions keep partial index optimization but the optimizer now
reports whether the matching keys are exact. When they are a superset
(some conjuncts could not use an index, or a compound range was
combined with other conditions), currentStateAsChanges re-checks each
candidate row against the full where expression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: apply strictest bound in compound range queries and fix related range edge cases

Compound range conditions sharing a boundary value (gte(x,5) AND
gt(x,5)) now keep the strict bound regardless of argument order. Bound
values are compared with the same comparator the indexes use so dates
and locale strings behave correctly.

Two further issues surfaced by the regression tests:

- One-sided compound ranges passed an explicit undefined bound to
  rangeQuery, which treats present-but-undefined as the undefined
  sentinel and returned an empty result. Bounds are now only passed
  when they exist.

- BTreeIndex's exclusive lower bound check compared the normalized
  indexed value against the raw query value, so gt on date fields
  included the boundary row. It now compares against the normalized
  key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: add failing test for exclusive lower bound without a from bound

rangeQuery with only an upper bound but fromInclusive: false must not
drop the minimum key, as there is no lower bound to exclude against.
This regression was introduced when the exclusive lower-bound check
started comparing against the normalized fromKey (which defaults to
minKey when no from bound is given). This test currently fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: re-filter compound range queries that use a null/undefined bound

A comparison against null/undefined is never true, but in an index
those values sort as the smallest key, so an index range query cannot
represent such a bound. Compound range optimization now tracks
selected bounds with explicit hasFromBound/hasToBound flags (separate
from the bound values) and marks the result inexact when any bound
value is null/undefined, so the caller re-filters against the full
expression. The inexactness now also propagates through the AND
combiner, which previously ignored the compound range's exactness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: only exclude exclusive lower bound when a from bound is provided

BTreeIndex.rangeQuery dropped the minimum key when called with
fromInclusive: false but no from bound, because fromKey defaults to
the minimum key and the exclusion check did not verify a lower bound
was actually given. The exclusion is now guarded by hasFrom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: re-filter index results that can include nullish-keyed rows

BTree indexes store and return rows with a null/undefined indexed
value (they sort as the smallest key), but a comparison against
null/undefined is never true. The simple-comparison, IN, and
compound-range optimizers now report such results as inexact so the
caller re-checks candidates against the full expression:

- eq/gt/gte: inexact when the query value is nullish (gt/gte with a
  non-null bound stay exact, since the bound excludes the bottom-sorted
  nullish rows)
- lt/lte: conservatively inexact, as the open lower bound includes
  nullish-keyed rows
- IN: inexact when any listed value is nullish
- compound range: exact only when a non-null lower bound is present to
  exclude the nullish rows

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: apply automated fixes

* fix: avoid locale string range index lookups and re-filter NaN results

Two more index-optimization correctness issues:

- A BTree index orders strings with localeCompare under the default
  'locale' collation, but the WHERE evaluator compares strings with JS
  relational operators (code-point order). For range predicates these
  orders disagree (e.g. 'oe-umlaut' > 'z' is true in JS but sorts before
  'z' under locale), so an index range lookup can omit matching rows -
  which re-filtering cannot recover. Locale-backed string range
  predicates are now left for a full scan (eq/IN use exact equality and
  are unaffected).

- eq/IN against NaN returned isExact: true, but NaN is never equal to
  itself while the index still returns NaN-keyed rows (SameValueZero map
  equality). NaN is now treated like a nullish value for exactness, so
  such results are re-filtered against the full expression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: only use indexes for range predicates when ordering is trustworthy

Range optimization assumed the index orders values the same way the
WHERE evaluator's relational operators do. That holds for numbers,
booleans, bigints, lexical strings and valid Dates, but not for:

- non-primitive operands (arrays, plain objects, Temporal, invalid
  Dates), which the evaluator compares via string coercion / identity
  while the index compares recursively;
- indexes created with a custom comparator, whose order is opaque;
- fields containing a NaN or invalid Date, which compare equal to every
  value and break the strict-weak-ordering range traversal relies on.

In all three cases an index range lookup can omit genuine matches, which
re-filtering cannot recover, so they now fall back to a full scan. The
index exposes a supportsRangeOptimization capability (false for custom
comparators or when an unorderable value is stored), and the optimizer
additionally checks the operand domain. eq/IN are unaffected (exact
equality, not ordering).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: apply automated fixes

* fix: give NaN and invalid Dates a stable sort position

The comparator returned 0 for NaN against any value (and NaN from
invalid-Date subtraction), so NaN had no consistent order. That made
ordering by a field containing NaN non-deterministic and, worse,
corrupted the strict-weak-ordering that B-tree range traversal relies
on, so a stored NaN could make a range query drop genuinely matching
rows.

ascComparator now places NaN and invalid Dates alongside nulls, giving
a well-defined total order. With a valid order the index traversal is
correct again, so range queries on a field containing such values no
longer deopt to a full scan: NaN simply sorts to the nulls end, where
the existing exactness logic excludes it from lower-bounded ranges and
re-filters it out of open-bottom (lt/lte) ranges. The index capability
therefore only needs to deopt for custom comparators, so the
stored-value check is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: adopt PostgreSQL float semantics for NaN (supersedes TanStack#1617)

Fold the NaN/invalid-Date semantics from the TanStack#1617 proposal into this PR
so the index-optimization correctness work is built on the final, coherent
contract instead of the interim JS-semantics handling.

Previously this branch gave NaN/invalid Dates a stable sort position *for
sorting only* while the WHERE evaluator still rejected them (NaN != NaN),
relying on re-filtering to drop index-returned NaN rows. That left a
JS/SQL hybrid where joins/groupBy/distinct (which match NaN = NaN via the
hash index) disagreed with WHERE/ordering.

Following PostgreSQL, NaN (and invalid Dates, whose timestamp is NaN) is
now equal to itself and greater than every other non-null value:

- comparison.ts: ascComparator orders NaN/invalid Dates as the greatest
  non-null value (was: alongside nulls); isUnorderable is exported.
- evaluators.ts: eq/gt/gte/lt/lte/in implement the same via valuesEqual.
- index-optimization.ts: because the index and evaluator now agree on
  NaN/invalid Dates, they are treated as exact (no re-filter) and invalid
  Dates are no longer range-divergent. This resolves the reviewer's
  invalid-Date eq/IN issue (indexed == full-scan) and simplifies the
  NaN-specific defensiveness down to the remaining nullish cases.

null/undefined are unchanged: still three-valued logic (UNKNOWN).

Tests: new nan-semantics.test.ts (numeric NaN + invalid Date, asserting
indexed == full-scan), PG-semantics comparison.test.ts and evaluator NaN
block; updated the NaN tests in collection-indexes.test.ts and
deterministic-ordering.test.ts that encoded the old JS behavior. Docs and
a minor changeset added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: mark NaN-semantics changeset as patch (no minor before 1.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: fold nan-semantics tests into existing well-suited test files

Move the unique NaN/invalid-Date coverage (lt on NaN, invalid-Date
eq/IN/range index parity) into collection-indexes.test.ts alongside the
existing NaN index tests, and drop the standalone nan-semantics.test.ts
whose other cases were already covered by collection-indexes,
deterministic-ordering and evaluators tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
github-actions Bot and others added 30 commits August 23, 2026 15:26
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* test(db): add loadSubset and pagination oracles

* test(db): tighten loadSubset oracle boundaries

* test(db): close loadSubset oracle gaps

* test(db): finish loadSubset review coverage

* test(db): tighten loadSubset coverage oracle

* fix(db): propagate initial query errors

* chore: add error propagation changeset

* test(db): exclude empty distinct windows

* test(db): address oracle review findings

* test(db): close oracle review gaps

* test: account for load subset abort signal

* test(db): tighten loadSubset oracle boundaries

* test: align includes preload error expectation

* test(db): harden loadSubset oracle boundaries

* fix(db): harden initial sync error lifecycles

* fix(db): report incremental subset errors

* chore: add incremental subset error changeset

* fix(db): harden subset error lifecycles

* fix(db): clean up failed subset operations

* fix(db): harden subset failure cleanup

* fix(db): recover from subset replay failures

* fix(db): make subset replay generation-safe

* fix(db): harden subset replay generations

* fix(db): harden subset replay recovery

* fix(db): preserve effect startup errors

* test(db): expand subset failure matrices

* fix(db): close subset lifecycle gaps

* test(db): align subset error oracles

* test(svelte-db): wait for hydrated source

---------

Co-authored-by: Tanner Linsley <tannerlinsley@gmail.com>
* fix: preserve correlated include route identity

* chore: add changeset for correlated includes

* fix: thread correlated routes through query boundaries

* fix: preserve union route metadata

* test: generate route context grammar

* fix: close correlated route coverage gaps

* ci: apply automated fixes

* fix: preserve routes for scalar derived rows

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Apply canonical demand identity review fixes

* docs: add canonical demand identity changeset

* fix(db): fall back when runtime crypto is incomplete

* fix(db): preserve observable demand identity

* fix(db): address demand identity review findings

* fix(db): keep demand history immutable

* fix(db): preserve observable query identity
* Apply canonical demand identity review fixes

* docs: add canonical demand identity changeset

* fix(db): fall back when runtime crypto is incomplete

* fix(db): settle subset loads after publication

* docs: add applied settlement changeset

* fix(db): preserve observable demand identity

* fix(db): address demand identity review findings

* fix(db): preserve cancellation through settlement

* fix(db): keep demand history immutable

* test(electric): type deferred helper

* fix(db): preserve observable query identity

* fix(db): close applied settlement gaps

* fix(db): distinguish canceled sync receipts

* test(db): tighten applied receipt coverage
* fix(db): reuse collection descriptors by id

* chore: add collection descriptor changeset
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* fix(db): lazily initialize runtime reference identities

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(db): match index collation options semantically

* chore: add changeset for index collation matching

* fix(db): narrow locale compare options

* fix(db): canonicalize index locale identifiers
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…anStack#1797)

* test(db): make lifecycle coverage independently observable

* test(db): expose subset acquisition availability gaps

* test(db): strengthen async lifecycle observations

* test(db): complete subset acquisition phase table

* test(db): complete async demand lifecycle histories

* test(db): consolidate subset lifecycle model

* test(db): make demand lifecycle traces independent

* test(db): complete subset lifecycle census

* test(db): close subset lifecycle census gaps

* test(db): make acquisition census executable

* test(db): tighten lifecycle interaction evidence

* test(db): complete lifecycle acquisition census

* test(db): assert terminal failure cleanup

* test(db): require compound lifecycle reach

* test(db): extend lifecycle red suffixes

* test(db): observe restart interleavings

* test(db): tighten restart settlement evidence

* test(db): extend publication lifecycle suffixes

* test(db): prove publication red suffixes execute

* test(db): map the publication lifecycle product

* test(db): close publication product audit gaps

* test(db): expose settled replay peer retirement gap

* test(db): verify reentrant replay demand readiness

* test(db): verify replay error ownership and publication trace

* test(db): verify settled peer recovery through includes graph

* test(db): cover ordered consumer lifecycle product

* test(db): close ordered lifecycle observation gaps

* docs: record lifecycle stress results and audit limits

* test(db): keep failed replay private in lifecycle model

* fix(db): reject unfinished preload during cleanup

* test(db): isolate startup cleanup cancellation

* fix(db): reject preload when ordered refinement fails

* docs: record boundary repair loss audit and controls

* fix(db): reconcile missing eager rows after restart

* docs: record restart audit and overall lifecycle progress

* fix(db): skip subset release for eager collections

* docs: record eager release audit and stress budget limits

* test(db): expose peer replay loss as a failing assertion

* docs: record peer replay assertion loss audit

* fix(db): ignore snapshot requests cancelled before acquisition

* docs: record pre-abort ownership loss audit

* fix(db): retain demand until the subset loader is installed

* fix(db): reject deferred subset requests when cancelled

* docs: record queued cancellation audit and limits

* fix(db): fence adapter retirement across sync reentry

* docs: record session retirement loss audit

* fix(db): defer source demand until initial error recovery

* docs: record initial error gate audit and baseline controls

* fix(db): report pending results for unavailable subset demand

* test(db): verify recovery wait publication and restart boundaries

* docs: record recovery notification follow-up audit

* test(db): align truncate oracle with replay ownership contract

* docs: record truncate contract loss audit

* test(db): model queued replay setup in lifecycle histories

* docs: record queued history model audit and generator gap

* fix(db): avoid acquiring canceled replay demand

* docs: record canceled replay ownership loss audit

* test(db): model cancellation settlement in replay histories

* docs: record cancellation contract loss audit

* test(db): distinguish readiness and replay publication waits

* docs: record publication readiness loss audit

* test(db): align publication oracle with batch and cancellation contracts

* test(db): preserve raw publication diagnostics after loss audit

* fix(db): preserve private replay rows and scope demand retirement

* refactor(db): remove unused replay key snapshot after loss audit

* fix(db): reconcile known snapshots and retained reset rows

* docs: record publication lifecycle loss audit

* fix(db): scope ordered recovery to each source consumer

* docs: record ordered recovery loss audit

* fix(db): retain ordered tie boundaries across row arrivals

* docs: record boundary retention loss audit

* fix(db): guard queued ordered recovery publication

* docs: record queued recovery loss audit

* test(db): arm incremental failures after ordered startup

* docs: record incremental phase loss audit

* fix(db): finish release attempts before reporting errors

* test(db): enforce release error identity after loss audit

* fix(db): retain failed cleanup within its retiring session

* test(db): preserve cleanup error message contract

* fix(db): guard window reentry and retain cleanup cancellation

* docs: record window lifecycle loss audit and remaining witnesses

* fix(db): join pending window leases during preload

* docs: record controller loss audit and projection baseline

* test(db): map functional include projection boundaries

* docs: record projection boundary loss audit

* fix(db): retain functional include projection inputs

* test(db): guard scalar projections before moving materialization

* docs: record scalar projection compatibility audit

* test(db): specify functional projection consumer boundaries

* docs: record projection compatibility specification audit

* fix(db): materialize inline inputs before functional projection

* docs: record inline projection repair loss audit

* test(db): pin already-public facade projection controls

* docs: record public facade control loss audit

* test(db): pin facade snapshot isolation gate

* docs: record facade snapshot experiment loss audit

* test(db): pin draft facade identity contract

* docs: record draft facade experiment loss audit

* refactor(db): replace deferred projections with live input views

* docs: reconcile slim projection loss audit

* fix(db): read draft facade helpers through their view

* docs: record draft read API loss audit

* fix(db): reject index creation on draft projection inputs

* docs: reconcile draft index guard loss audit

* test(db): check retained facade APIs across publication

* docs: record retained facade API loss audit

* test(db): cover facade subscriptions through failure and restart

* test(db): fence pending facade loads across graph restart

* test(db): check chained facade continuation boundaries

* docs: record continuation boundary loss audit

* perf(db): snapshot draft facade rows once per input view

* test(db): align retention oracle with restart reconciliation

* docs: record snapshot audits and oracle campaign scope

* test(db): preserve publication state across no-op lifecycle commands

* fix(db): establish the source prefix on explicit window moves

* test(db): model raw updates after private replay retirement

* docs: record oracle audits and prefix transfer cost gate

* test(db): bound provider row volume during pagination

* fix(db): continue pagination from settled loading boundaries

* docs: record pagination boundary loss audit

* test(db): model raw truncate events after replay retirement

* test(db): keep publication model state aligned with callbacks

* docs: record publication model verification and loss audits

* docs: record clean full oracle campaign with thread workers

* docs: preserve full campaign audit limits

* test(db): restore the package test typecheck gate

* docs: record type gate audits and unit failure boundaries

* test(db): assert authoritative ordered retry work

* fix(db): keep failed windows private through source replay

* test(db): distinguish replay failure ownership boundaries

* fix(db): isolate failed window state across restart

* docs: record closed unit groups and green stress gate

* docs: close integration campaign loss audit

* docs: record measured code-weight reduction plan

* docs: retain code-weight audit proof obligations

* refactor(db): retain subset acquisitions as lease objects

* docs: record acquisition reduction audit and stress gate

* refactor(db): centralize ordered startup failure state

* docs: record ordered failure reduction audit and stress results

* refactor(db): reuse callback handling for subscription teardown

* refactor(db): derive ordered invalidation from contributed rows

* docs: record teardown and pagination reduction stress gate

* refactor(db): flatten replay pending state

* fix(db): preserve retained replay startup participants

* fix(db): prune released failures from retained replay attempts

* refactor(db): retain only current replay failures

* docs: record replay reduction validation and next weight target

* refactor(db): share grouped and global aggregate pipelines

* docs: record group pipeline loss audits and validation

* refactor(db): trim group mapping and evaluation helpers

* docs: record group helper audit and ordered request candidate

* refactor(db): name ordered source request kinds

* docs: record ordered request audit and stress results

* refactor(db): reuse published rows as replay baseline

* docs: record replay baseline audit and stress gates

* refactor(db): share graph loader callback dispatch

* docs: record graph callback audit and validation

* refactor(db): keep one scheduling dependency set

* test(db): preserve dependency trace failure snapshots

* fix(db): wait for reentrant scheduler dependencies

* docs: record scheduler audit and full-suite follow-ups

* test(db): await ordered join-subquery readiness

* docs: record ordered join test audit and identity decision

* test(db): define inline includes value and publication guarantees

* docs: record inline identity audit and stress gate

* fix(db): leave subset row retention to the source

* docs: record source retention stress gate and loss audit

* test(db): run order-by cases without automatic indexes

* docs: record order-by unskip loss audit

* test(db): preserve settled empty-window work bounds

* docs: record empty-window work loss audit

* test(db): preserve replacement startup progress

* docs: record recovery design loss audit

* docs: assess snapshot acquisition boundaries

* docs: record snapshot split loss audit

* docs: record snapshot handoff experiment

* docs: record handoff experiment loss audit

* docs: trace loading lifecycle formation

* docs: record wider loading design grammars and audit

* docs: record wider readout loss audit

* docs: freeze ordered lifecycle refactor baseline

* refactor(db): isolate ordered source loading

* refactor(db): make ordered retry failure explicit

* docs: record ordered state refactor validation

* refactor(db): distinguish ordered settlement from recovery

* docs: close ordered loader refactor checkpoint

* refactor(db): make replay acquisition handoff explicit

* test(db): preserve exact options identity in handoff matrix

* docs: record audited acquisition handoff checkpoint

* fix(db): isolate ordered publication settlement by session

* docs: close audited integration handoff checkpoint

* test(db): pin demand message boundaries before D2 batching

* docs: record demand-presence spike audit limits

* fix(db): align index cursors and clear readiness lint

* test(db): pin source cleanup recovery boundary

* docs: record consolidated readiness gates and audit

* refactor(db): share descriptor-safe array snapshots

* docs: record array snapshot validation and loss audit

* refactor(db): name public container property record

* docs: record route property type loss audit

* docs: prepare consolidated lifecycle PR

* ci: apply automated fixes

* fix(db): wait for initial rows before fetching the next page

A still-loading empty snapshot does not prove there is no next page. Coalesce startup fetches, wait for preload, and discard deferred expansion after reset or disposal. Add the startup/data/lifecycle matrix that the preloaded core fixtures missed; React and Vue conformance retain their immediate replacement-fetch assertions. Align React's opaque-value test with the existing runtime-reference identity contract and assert reuse versus separation.

* test: await settled framework window normalization

A framework flush starts subscriptions but cannot await asynchronous core window refinement. Wait for the settled window before checking rows and metadata in the Vue and Svelte precreated-query tests; retain their exact expected results. No framework runtime changes.

* test: retain post-flush window assertions

* test(query-db): expect cancellation of abandoned preloads

Cleanup rejects unfinished live-query preload with AbortError; releasing listeners must not report a successful load. Observe both preload promises before cleanup, retain the immediate observer-count and late-result assertions, and verify that late transport success or rejection leaves the original cancellation outcome unchanged. No production changes.

* fix: address review findings and retire unused subset algebra

Align draft membership with publication, preserve foreign opaque value identity when snapshotting demands, and compare only own enumerable object keys. Keep primitive and cached hashing free of traversal-context allocation and bound index removal searches to comparator-equal buckets.

Remove the unused public subset-algebra exports and their API-only tests/docs, with a changeset migration note. Retain behavioral oracles, strengthen load-work assertions and cleanup coverage, remove dead demand replacement wiring, and fix review lint findings.

Validation: 4,679 DB tests pass with type checking; DB build and ESM/CJS export checks pass. Earlier hash changes passed all 343 IVM tests. Production source is 1,213 lines above main, including 740 core DB lines.

* fix: close publication and subset ownership review gaps

* fix: settle adapter waits and preserve acquisition ownership

* fix: fence cancelled demand and expose broken source tracking

* test: call the acquired PowerSync release helper

* refactor: attempt each subset release once

Retire physical acquisitions before adapter callbacks and drop retained release debt and replacement rollback. Keep first-error reporting and complete sibling teardown. Throwing adapters must manage their own resource cleanup; failed releases are not retried by core.

Preserve lifecycle and replay test matrices under the explicit one-attempt contract, and probe failures before/after resource release with reentrant teardown. Full DB: 4690 tests; Query 349, Electric 507, persistence 128, PowerSync 113. Runtime reduction: 54 lines; diagnostic core bundle -893 minified / -181 gzip bytes.

* refactor: reject structural cycles and hash Collection handles by reference

Replace cyclic traversal contexts with active-path rejection and completed-subtree caching. Keep depth/work bounds and avoid publishing structural cache entries after failure. Collections register as opaque references in the existing weak hash cache, preserving downstream projection without hashing mutable internals or relying on globally unique collection IDs.

Preserve cyclic fixtures as rejection tests and acyclic sharing controls. Add independent fixed/random graph oracles, work and failed-cache probes, and public error/snapshot and Collection instance tests. Full gates: DB 4692, IVM 355, Query 349, Electric 507, persistence 128, PowerSync 113; no type errors. Net runtime cut: 157 lines.

* docs: align subset ownership and hashing contracts

* refactor(db): remove dead state and consolidate exact duplicates

Preserve acquisition phases, reentrant failure handling, explicit range bounds, and all oracle cases. Share only lifecycle fixture defaults; keep adapter timing and writes explicit. Wire manual retention and hash probes and organize demand-plane contracts.

* refactor(db): restrict functional select to inline include inputs

Reject compiled Collection-valued fn.select inputs before callbacks run, including nested, ignored and pass-through inputs. Remove temporary facade views and graph continuations; keep ordinary live child Collections and inline materialization.

Document upstream toArray/materialize and parent-only functional work before adding live includes. Preserve public facade membership, indexes, retained readers, rollback and retention tests; replace removed-support cells with rejection checks and add chained inline controls.

Verified 4705 DB, 355 IVM, 349 Query DB, 507 Electric, 128 persistence and 113 PowerSync tests, package types, lint and retention probe. Removes 235 source lines including migration JSDoc and 1068 gzip bytes from the diagnostic all-export core bundle.

* perf(db): encode group representatives once per contribution

Move the existing representative ordering key into aggregate preMap. Preserve exact tie-breaking without repeatedly serializing every retained member on each group change. No new cache or lifecycle state.

Add insert/delete work bounds at 16, 1024 and 5000 members: red at up to 5002 JSON encodings, green at no more than four. Existing correctness-only oracles did not bound encoding work. Full DB: 4708 tests pass.

* perf(db): compare binary equality operands without string encoding

Use existing byte equality for eq and IN while retaining binary Map-key normalization. Share Uint8Array/host Buffer detection across the three users; keep content equality for all sizes without mutable caches or thresholds.

Add a 76-case work matrix covering binary forms, offset views, equality and mismatch, size boundaries, mutation, and normalization-like strings. Red at 2 MiB encoded per equal 1 MiB pair, green at zero. Existing tests checked answers but not normalization work. Full DB: 4784 tests pass; package types and lint pass.

* perf(db): stop BasicIndex filtering when the page is full

Sort comparator ties before invoking the filter and stop after enough accepted keys. Preserve deterministic ordering without a new retained index. Sorting still scans the full tie group; this fixes excess filter calls, not that separate cost.

Work matrix covers 30 to 100000 rows, reversed insertion, both directions and selective filters. Red/green reduces 33334 filter calls to 10 for a ten-key page. Focused index gates: 68 tests pass.

* perf(db): retain ordered bucket ownership for exact index values

Each exact-value bucket points to its existing comparator bucket. Repeated inserts and non-final removals avoid tree searches; final removal and representative replacement still update the tree. No new per-row state; one owner reference per distinct exact value.

Work tests cover 300 and 100000 keys with one or two exact values per comparator position. Zero comparisons for measured warm inserts/removals; existing index property tests preserve lookup, range and representative laws.

* perf(db): avoid intermediate arrays when comparing own keys

Append enumerable symbols to the existing Object.keys array. Preserve per-key own-enumerable checks: an attempted positional shortcut was rejected after getters changed a later property visibility.

Add allocation work bounds and string/symbol getter regressions. Four intermediate filter calls become zero in the nested fixture; three isolated comparisons measured about 20 percent lower runtime. Full DB 4804 and all four adapter suites pass; no new skipped tests.

* fix(db): observe deduplicated load callback failures

* perf(db): remove proxy debug logging overhead

* perf(db): remove automatic index diagnostics

* refactor(db): remove unused helpers and exercise production test paths

* refactor(db): share narrow helpers and require exact release

* test(db): preserve indexed range recovery after mixed values

* test(db): protect reference-key matches across public container cleanup

* refactor(db): restrict cursor construction to single-column requests

* refactor(db): retain immutable subset request data

* test(db): cover replay failure isolation and consumer recovery

* refactor(db): replace replay acquisitions sequentially

* test(powersync): cover pending writes at final demand release

* test(db-ivm): preserve work limits across rejected hash retries

* refactor(db): clear disposed effect references without deferred state

* refactor(query-db): reuse eager observers across cache removal

* test(db): cover retired load status across settlement orders

* refactor(db): remove live-query run-count diagnostic

* test(db): verify resource cleanup across failed teardown retries

* refactor(db): trim unused internals and move test inspection out of runtime

Consolidate compiler routing and source traversal, narrow resolved indexes to the exported IndexReader interface, trim the internal BTree fork, and remove unused diagnostics and release helpers. Query replay delegates publication without retaining duplicate row snapshots; adapter cleanup keeps its existing retry boundary.

Retain BTree/Map, replay publication, and ordered acquisition tests. Add native Map/Set live-iteration laws. Deliberately exclude the snapshot-based proxy rewrite: standard iteration semantics remain part of the draft contract.

Verified 4646 runtime tests and 256 type checks plus rebuilt-core adapter gates. Pinned bundle saves 1354 gzip bytes versus d525f37. Existing proxy and overlapping lifecycle findings remain tracked separately.

* fix(db): preserve live Map and Set draft iteration

Keep owned entry copies in place instead of deleting and reinserting Set members on nested edits. Share the native iterator path across entries, values, default iteration, and forEach, and resolve yielded handles for membership and mutations. Track Map.get values as well as iterator values.

Fix duplicate forEach callbacks and false changes on reads, dropped Map for-of edits, Set write/revert loops, and invalid or duplicated post-edit Set handles. Preserve sibling edits on reversion and remove the eager Map.values scan.

The 58-law matrix fails 38 cases at the unchanged baseline and passes on this fix. Preserve native additions, deletions, and clear/re-add during iteration; retain all existing proxy tests. Core: 4700 runtime tests and 256 type checks. Rebuilt-core Query, Electric, PowerSync, and SQLite suites pass. Production source is 106 lines smaller; pinned bundle gzip changes by +13 bytes. No snapshot semantics or test waivers.

* fix(db): join reentrant effect disposal outcomes

Install the shared disposal promise before abort and source-release callbacks can reenter. Reuse the deferred helper and keep physical cleanup synchronous and one-shot. Calls during the attempt share its failure and wait for in-flight handlers.

Add a 12-cell oracle across abort/release reentry, pending handlers, and success/Error/undefined outcomes; eight cells fail on the baseline. The old test counted unloads without checking the nested promise. All 85 effect tests and 256 type checks pass. Pinned gzip increases 15 bytes.

* fix(db): preserve ordered recovery across late finite success

A finite page or boundary request can settle behind full-source repair. Let it settle its publication participant without clearing the repair failure or starting redundant refinement. Reuse existing failure and full-source state; add no lifecycle fields.

Extend the request-kind by completion-order by outcome oracle and a real setWindow/adapter publication trace. Removing the guard reproduces five matrix failures plus an extra sixth transport in the public trace. Explicit retry releases the failed full-source acquisition once and publishes the replacement once.

Combined gates: 4721 runtime tests, 256 type checks, rebuilt Query/Electric/PowerSync/SQLite adapter suites, build, lint, and format. Register both lifecycle oracle suites in test:oracles. Pinned gzip increases six bytes for this fix (21 bytes including the preceding disposal fix).

* fix(db): reject restart during active collection cleanup

Recursive cleanup could admit a replacement sync session that old teardown then erased. Reject start and preload during physical retirement instead of supporting nested graph replacement. Keep restart after cleanup and from its final status event. Add a 14-cell admission oracle covering abort/release reentry, retries, ownership, peer isolation, and normal recovery.

* test(db): match cleanup oracle collection key type

* chore(db): classify API removals as a minor release

* fix(db): isolate persistence and observer failures

Stage local-storage writes before promoting the shared cache. Deliver queued publications to peer observers before reporting the first error. Strengthen iterator assertions and retain direct optimistic write-back regressions for subsequent mutations.

* fix: close demand settlement and reference identity gaps

* perf(db): retire finite demands after authoritative recovery

* fix(db): preserve draft value sharing and detach completed updates

Use one original-to-draft map per row. Keep existing data private, preserve normal references for new objects during the callback, and detach completed changes afterward. Preserve sparse self-link changes and publish sibling aliases in both edit directions, including Set values.

Document the approved ownership boundary and add raw insertion, multi-row sharing, callback failure, alias identity, and post-callback isolation laws. Focused tests: 193 passed; TypeScript and lint passed. Net proxy source change: +8 lines.

The wider local review worktree still has four retained core failures: two prefix-retirement cases, repair-error reporting, and insert-after-truncate. Those separate pending review changes are not included in this commit.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(db): finish review recovery and draft follow-ups

* fix: preserve acknowledgment and recovery invariants

Distinguish acknowledged inserts from stale synced keys, reconcile negative rows after PowerSync tracking outages, and keep one-shot persistence refreshes out of permanent demand ownership.

Finish successful ordered-prefix bookkeeping even when retiring older leases throws. Add red/green regressions for each boundary and strengthen peer-delivery and detached-cycle laws.
…wnership

Hold a targeted live-query demand through transaction confirmation for eager and on-demand collections. Reject same-ID overlapping saves across queues sharing a database and preserve SDK filename-based relocation. Add intent oracles and separate native/integration repros for the unresolved upstream upload-completion race.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…Stack#1807)

* fix(db): reconcile optimistic fields and queued derived updates

* fix(db): preserve optimistic field ownership through settlement

* fix(db): keep successful mutation origins separate from active changes

Provisional direct-handler attribution uses active local changes. Only successful mutations retain origin evidence; failure and queued sync writes no longer erase or prematurely promote it. Cover nonoptimistic sibling completion, failed-only writes, synchronous acknowledgements and the retained rollback publication law.

* fix(db): preserve optimistic snapshots across settlement and sync

Keep captured whole-row snapshots instead of rebasing their fields onto synced rows. Track the exact creating-insert dependency for retained updates, preserve local attribution through rollback and truncate, and retire completed snapshots before rebuilding the active overlay.

Generalize regressions into shared model-driven histories that compare reads, event reconstruction, downstream queries, request snapshots, and publication counts. Build queued membership lazily for balanced graph deltas.

Verified 5,263 core tests and a 100x campaign of 36,000 generated histories, plus standalone types, lint, and formatting. Sync queue timing is unchanged.
* fix: reject ignored infinite-query page callbacks

* ci: apply automated fixes

* test: cover server pagination ties and clarify cursor hints

* docs: reconcile server pagination verification history

* docs: give RFC follow-up notes a distinct path

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…nStack#1805)

* fix(trailbase): retire errored subscription readers

* fix(trailbase): isolate stream lifetimes with lifecycle oracle

* chore: add TrailBase stream cleanup changeset

* ci: apply automated fixes

* fix(trailbase): report startup failures before cancellation settles

* docs: give RFC follow-up notes a distinct path

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(db): reclaim collections that sync before anything subscribes

`startGCTimer` had one caller: `removeSubscriber`, on the edge where the
last subscriber leaves. A collection whose subscriber count went from zero
straight back to zero never crossed that edge and so never armed the timer.

Sync starts without a subscriber in three places -- `startSync: true`,
`preload()` and `startSyncImmediate()`. A live query started that way keeps
a subscription on every collection it reads from, so it both survives and
reprocesses every source change forever, however short its `gcTime` is.

Framework adapters build their live query collection while rendering and
subscribe when that render commits, which makes every render React discards
before committing -- a suspended subtree, a render that throws, a
time-sliced render restarted by an interleaved update -- leak one compiled
query graph rooted at a long-lived source collection.

`startSync` is the single point every route into sync passes through, so it
now arms the timer whenever it runs unsubscribed. The delay is floored at
50ms so a subscriber arriving with the commit cancels it first; the floor
does not apply to the last-subscriber-leaves timer, which still fires on
`gcTime`. `gcTime: 0` still disables GC.

`CleanupQueue.cancel` also retires the shared root timer once it empties the
queue, instead of leaving it armed with nothing to run.

* docs(db): trim the GC comments to what the code cannot say

The comments introduced with the unsubscribed-sync fix explained the defect
three times over, at a length the surrounding methods do not use. What is
left is the part that is invisible from the code: why the floor exists and
cannot be `gcTime`, why a non-empty cleanup queue keeps a timer that may
wake early, and that `addSubscriber` counts itself in before starting sync.

`startGCTimerIfUnsubscribed` now reads like its siblings -- what it does,
then when it runs. Its history belongs in the log, not above the method.

* docs(db): correct when sync starts, and what gcTime 0 opts out of

`meta-framework` said an unpreloaded collection starts syncing when the
component mounts. It starts on the component's first render -- `useLiveQuery`
constructs with `startSync: true` from the render body -- which is why a render
that never commits can still start sync. Mount is only where teardown is keyed.

`live-queries` documented that `gcTime: 0` disables collection for a derived
collection. It now also opts out of reclaiming a collection that synced without
ever gaining a subscriber, so say so, along with the fact that `gcTime: 0`
means the opposite in TanStack Query -- prompt collection there, none here.

* fix(db): retain pending preloads and tighten GC regression tests

* docs: summarize safe orphan collection cleanup

* fix(db): refresh retention when preloading ready collections

* docs: include warm preload retention in changeset

* fix(db): invalidate detached snapshots across collection cleanup

---------

Co-authored-by: edzis <edgars.simsons@ui.com>
* docs: remove duplicate page headings

* docs: keep generated page titles disabled

* docs: leave API reference output to the generator
* fix: harden collection lifecycle invariants

* fix(db): derive collection key paths from getKey

* fix(db): remove speculative getKey optimization

* ci: apply automated fixes

* refactor(electric-db): simplify lifecycle ownership

* fix(electric-db): preserve evidence for ignored updates

* fix: restore persisted resume contracts

* test: allow exhaustive oracles time under CI load

* docs: record adapter settlement and resume contracts

* ci: apply automated fixes

* fix: reconcile Electric presence and descriptor ownership

Use the applied collection baseline with a transient pending-write overlay so peer persistence publications accept later partial updates without resurrecting pending removals. Remove full-key refreshes on acquisition and warn once when persisted hydration cannot be verified.

Keep utilities and tag visibility collection-local, preserve compatible restart tags, and replace stale cache rows on fresh snapshots. Cover actual persisted insert acknowledgements, independent coordinator publications, parked removals, and reset epoch partitioning with regression and mutation laws.

Verified 4865 core, 368 Electric, and 75 persistence runtime tests, Electric TypeScript, and focused lint. Update adapter docs and the review reconciliation record.

* fix(electric): keep buffered move-outs in the snapshot transaction

* fix(electric): isolate callbacks and recover cold tagged state

Extend the descriptor, persisted-tag, and callback-reentry oracles before fixing their failures. Keep copied materialized configs bound to their outer owner and fence callbacks and replacement waiters by lifecycle epoch.

Refetch cold tagged or legacy state behind cached rows, preserving offset resume for known untagged and compatible warm state. Persist reset before recovery and wait for the full snapshot rather than subset completion. Verify real SDK reset framing separately from synthetic robustness traces.

Verified 717 Electric tests, type checking, and 10x fixed/random oracle histories. Includes docs and changeset; exploratory review probes remain untracked.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…w-fixes

fix: preserve attachment files across startup and concurrent saves
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.