feat(PowerSync): add attachments support - #9
Open
Chriztiaan wants to merge 141 commits into
Open
Chriztiaan wants to merge 141 commits into
Chriztiaan wants to merge 141 commits into
Conversation
…#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
marked this pull request as ready for review
June 23, 2026 12:02
stevensJourney
left a comment
Collaborator
There was a problem hiding this comment.
Overall I'm happy with the approach here. Left a few comments, mostly nits.
* 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>
|
Hi! I'm the 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! 😃 |
Chriztiaan
commented
Jun 24, 2026
stevensJourney
previously approved these changes
Jun 24, 2026
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>
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
TanStackDBAttachmentQueuethat 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.