feat(PowerSync): add attachments support#9
Open
Chriztiaan wants to merge 60 commits into
Open
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>
A shared, framework-agnostic conformance suite for the useLiveQuery adapters: one behavioral spec runs against React, Vue, Svelte, Solid, and Angular through a thin per-adapter test harness. Documents current behavior and guards later refactors.
…anStack#1644) * docs: design query collection persistence PR * docs: clarify RFC cluster context * docs: avoid citation syntax in RFC spec * docs: draft query collection RFC * fix(query-db-collection): keep on-demand query meta clone-safe * chore: remove local RFC drafts from PR * Apply query metadata review fixes
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…#1623 step 2) (TanStack#1641) refactor(db): extract shared live-query adapter helpers Add isCollection, isSingleResultCollection, and getLiveQueryStatusFlags to @tanstack/db and migrate all five adapters to use them. - isCollection: one structural, multi-realm-safe collection guard replacing the per-adapter duck-typing (React/Vue/Svelte/Angular) and Solid's `instanceof CollectionImpl` (which gives false negatives across dual-package boundaries — the same hazard the conformance suite hit). - isSingleResultCollection: shared findOne cardinality check. - getLiveQueryStatusFlags: status → {isLoading,isReady,isIdle,isError,isCleanedUp}; used by React's snapshot path (the reactive adapters derive each flag as its own signal/computed, so a shared object-returning helper doesn't fit them — that duplication is reactivity-coupled and belongs to the observer step). No behavior change; guarded by the conformance suite. First slice of the RFC TanStack#1623 extraction (step 2). Status derivation, input/disabled classification, and change→state projection remain — they are reactivity-coupled and land with the observer. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A disabled query (callback returning null/undefined) crashed: the reactive-getter unwrapping (`toValue`) called the callback, and the resulting null was spread into createLiveQueryCollection → throw in getQueryIR. Now a null/undefined unwrapped value is treated as a disabled query, matching React/Vue/Solid/Angular. Clears the svelte-db conformance knownGaps (disabled-explicit, disabled-transition); both now pass as normal tests. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nStack#1638) The `{ query }` config-object branch called createLiveQueryCollection(opts) without defaulting startSync, unlike the query-fn and reactive-options branches (which force startSync: true). A bare `{ query }` therefore never synced and returned empty. Default startSync: true and gcTime: 0 while honoring explicit values in the config. Clears the angular-db conformance knownGap (config-object-input). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Stack#1639) Solid surfaces query errors by throwing (CollectionStateError) on read, for an <ErrorBoundary> to catch, rather than exposing a readable isError flag like React/Vue/Svelte/Angular. That's a framework idiom, not a bug — so instead of forcing Solid into the flag model, parametrize the error-status scenario. Adds driver.errorSurface ('flag' | 'throw', default 'flag'). The error-status scenario asserts a readable isError/status for 'flag', and that reading the errored result throws for 'throw'. Solid declares 'throw' and clears its error-status knownGap; the other four keep the default flag assertions. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…k#1655) * test(query-db-collection): cover invalidation behavior matrix * chore: add query invalidation changeset * ci: apply automated fixes * test(query-db-collection): clean up invalidation observers --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* docs: add query select semantics design * docs(query-db-collection): clarify select row extraction * Apply review simplifications * Add changeset for query select semantics * Strengthen select cache preservation test
…ack#1653) * docs(query-db-collection): document query option compatibility * docs(query-db-collection): clarify option compatibility wording * docs(query-db-collection): add changeset * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…ack#1664) * refactor(query-db-collection): extract query ownership helpers * chore: add query ownership helpers changeset
…tack#1665) * Apply query options review fixes * ci: apply automated fixes * Use flat query option fields * Mount query client during collection sync --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…rn (TanStack#1667) * docs(query-db-collection): document runtime QueryClient factory pattern * Add changeset for runtime QueryClient docs * ci: apply automated fixes * docs(query-db-collection): clarify collection instance lifetime * docs: remove changeset for docs-only update --------- 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>
…esume (TanStack#1493) * fix electric progressive persisted resume * test(electric): cover changes during persisted resume --------- Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com>
…Stack#1660) The live query config builder used `this.config.gcTime || 5000`, which treats an explicit `gcTime: 0` (disable GC) as unset and replaces it with the 5s default. The collection is then garbage collected instead of being kept alive. Use `??` so only `undefined` falls back to the default. Co-authored-by: Kyle Mathews <mathews.kyle@gmail.com>
…up (TanStack#1673) * test: characterize query cancellation cleanup * test: cover query cleanup review findings * Apply query readiness simplification * Add query cleanup changeset
* fix(electric): bound refresh wait for on-demand subsets * ci: apply automated fixes * test(electric): cover bounded refresh completion --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…tack#1671) * docs(query-db-collection): document scoped collection factories * docs: simplify scoped collection guidance * docs: add scoped factory changeset * docs: remove unnecessary changeset
* docs: add contributing guide * docs: address contributing review feedback * docs: order development setup steps
…anStack#1678) * test(db): add failing type tests for generic collection row types (TanStack#1677) Queries over a Collection<T> where T is an unresolved generic type parameter stopped typechecking in 0.6.6: refs inside where/join/select callbacks resolve to a union including RefLeaf, which exposes no row properties, even ones guaranteed by T's constraint. These tests currently fail and should pass once the ref typing is fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(db): restore query typechecking for generic collection row types Fixes TanStack#1677. Since 0.6.6, refs inside where/join/select callbacks broke when the collection's row type is an unresolved generic type parameter: TypeScript defers the IsPlainObject conditional in RefForContextValue and degrades the ref to a union that includes RefLeaf, which exposes no row properties. Two changes: - Make RefForContextValue distribute over T so that, when deferred, TypeScript resolves its constraint by instantiating T with T's own constraint, picking the Ref branch instead of the Ref | RefLeaf union. - Use GetRawResult instead of GetResult in SchemaFromSource for subquery sources: GetResult's Prettify wrapper produces a mapped type that the conditional-constraint resolution above cannot see through. Final query results still go through GetResult, so IDE display is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: add changeset for generic row type fix Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.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(query-db): characterize ownership cleanup lifecycle * test(query-db): cover ownership hydration and cache expiry * fix(query-db): remove empty ownership entries * fix(query-db): bound ownership resolution tracking * fix(query-db): clear retained ownership marker * refactor(query-db): simplify ownership cleanup * chore: add query ownership cleanup changeset * chore: remove workflow artifact * refactor: simplify query ownership bookkeeping * fix(query-db): clean retained ownership on cleanup
* docs(query-db): design initial data semantics * feat(query-db): support eager initial data * Apply code simplification and review fixes * fix(query-db): revalidate persisted initial data * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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.