Skip to content

feat(examples): add op-sqlite persistence to React Native offline-transactions demo#1351

Open
kevin-dp wants to merge 22 commits intokevin/persistencefrom
examples/offline-transactions-react-native-persistence
Open

feat(examples): add op-sqlite persistence to React Native offline-transactions demo#1351
kevin-dp wants to merge 22 commits intokevin/persistencefrom
examples/offline-transactions-react-native-persistence

Conversation

@kevin-dp
Copy link
Contributor

Summary

  • Adds op-sqlite persistence to the React Native offline-transactions example app, wiring it up with the persistence layer so todos survive app restarts
  • Updates the demo's metro config, database setup, server, and UI components to work with local SQLite storage via op-sqlite

Test plan

  • Run the React Native offline-transactions example on Android/iOS
  • Verify todos persist across app restarts
  • Verify offline mutations are queued and submitted when back online

🤖 Generated with Claude Code

@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

⚠️ No Changeset found

Latest commit: ca2c92a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 11, 2026

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/angular-db@1351

@tanstack/db

npm i https://pkg.pr.new/TanStack/db/@tanstack/db@1351

@tanstack/db-browser-wa-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-browser-wa-sqlite-persisted-collection@1351

@tanstack/db-electron-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-electron-sqlite-persisted-collection@1351

@tanstack/db-ivm

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-ivm@1351

@tanstack/db-node-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-node-sqlite-persisted-collection@1351

@tanstack/db-react-native-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-react-native-sqlite-persisted-collection@1351

@tanstack/db-sqlite-persisted-collection-core

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-sqlite-persisted-collection-core@1351

@tanstack/electric-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/electric-db-collection@1351

@tanstack/offline-transactions

npm i https://pkg.pr.new/TanStack/db/@tanstack/offline-transactions@1351

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/powersync-db-collection@1351

@tanstack/query-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/query-db-collection@1351

@tanstack/react-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/react-db@1351

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/rxdb-db-collection@1351

@tanstack/solid-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/solid-db@1351

@tanstack/svelte-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/svelte-db@1351

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/trailbase-db-collection@1351

@tanstack/vue-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/vue-db@1351

commit: 7b3c906

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Size Change: +537 B (+0.57%)

Total Size: 95.1 kB

Filename Size Change
./packages/db/dist/esm/errors.js 4.83 kB +133 B (+2.83%)
./packages/db/dist/esm/index.js 2.79 kB +26 B (+0.94%)
./packages/db/dist/esm/query/compiler/index.js 2.05 kB +13 B (+0.64%)
./packages/db/dist/esm/query/query-once.js 359 B +359 B (new file) 🆕
./packages/db/dist/esm/query/subset-dedupe.js 927 B +6 B (+0.65%)
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.22 kB
./packages/db/dist/esm/collection/events.js 434 B
./packages/db/dist/esm/collection/index.js 3.56 kB
./packages/db/dist/esm/collection/indexes.js 2.35 kB
./packages/db/dist/esm/collection/lifecycle.js 1.75 kB
./packages/db/dist/esm/collection/mutations.js 2.34 kB
./packages/db/dist/esm/collection/state.js 3.49 kB
./packages/db/dist/esm/collection/subscription.js 3.71 kB
./packages/db/dist/esm/collection/sync.js 2.41 kB
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/indexes/auto-index.js 742 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 2.17 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.24 kB
./packages/db/dist/esm/indexes/reverse-index.js 538 B
./packages/db/dist/esm/local-only.js 808 B
./packages/db/dist/esm/local-storage.js 2.1 kB
./packages/db/dist/esm/optimistic-action.js 359 B
./packages/db/dist/esm/paced-mutations.js 496 B
./packages/db/dist/esm/proxy.js 3.75 kB
./packages/db/dist/esm/query/builder/functions.js 733 B
./packages/db/dist/esm/query/builder/index.js 4.1 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 1.05 kB
./packages/db/dist/esm/query/compiler/evaluators.js 1.62 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/group-by.js 2.23 kB
./packages/db/dist/esm/query/compiler/joins.js 2.11 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.45 kB
./packages/db/dist/esm/query/compiler/select.js 1.09 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 673 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 5.55 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 2.42 kB
./packages/db/dist/esm/query/live/internal.js 145 B
./packages/db/dist/esm/query/optimizer.js 2.62 kB
./packages/db/dist/esm/query/predicate-utils.js 2.97 kB
./packages/db/dist/esm/scheduler.js 1.3 kB
./packages/db/dist/esm/SortedMap.js 1.3 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 247 B
./packages/db/dist/esm/strategies/queueStrategy.js 428 B
./packages/db/dist/esm/strategies/throttleStrategy.js 246 B
./packages/db/dist/esm/transactions.js 2.9 kB
./packages/db/dist/esm/utils.js 924 B
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 952 B
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.51 kB
./packages/db/dist/esm/utils/type-guards.js 157 B

compressed-size-action::db-package-size

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Size Change: 0 B

Total Size: 3.85 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 225 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.34 kB
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 559 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

@kevin-dp kevin-dp changed the base branch from cursor/persistence-plan-design-doc-f6d0 to kevin/persistence March 12, 2026 09:22
kevin-dp and others added 2 commits March 12, 2026 11:16
Restore db-electron-sqlite-persisted-collection with IPC bridge,
ElectronCollectionCoordinator for cross-window sync, and full e2e test suite.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add db-node-sqlite-persisted-collection (better-sqlite3 driver) which is
needed by the electron package tests as the SQLite driver for the Node
test environment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kevin-dp kevin-dp force-pushed the examples/offline-transactions-react-native-persistence branch 2 times, most recently from 267666e to df61339 Compare March 12, 2026 10:45
kevin-dp and others added 19 commits March 12, 2026 11:50
…nsactions demo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
react-native 0.79.6 accepts react@^19.0.0 as a peer dependency, so
upgrading from the pinned 19.0.0 to ^19.2.4 is safe and fixes the
sheriff version consistency check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
react-native 0.79.6 bundles a hardcoded react-native-renderer@19.0.0
which must exactly match the react version at runtime. Exclude the RN
example from sherif's version consistency check instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Intent agent skills for TanStack DB

Scaffold 12 SKILL.md files across 7 packages to guide AI coding agents.
Covers core DB concepts (collections, queries, mutations, custom adapters),
5 framework bindings (React, Vue, Svelte, Solid, Angular), meta-framework
integration (Start, Next, Remix, Nuxt, SvelteKit), and offline transactions.

Includes 9 reference files for adapter details, operators, and transaction
API. Updates package.json files arrays to publish skills with packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: apply code review and simplification fixes to skills

- Fix incorrect offline transaction API usage (tx.mutate pattern)
- Remove phantom ne operator, document not(eq(...)) pattern
- Fix Vue template variable reference (data → todos)
- Add export * from @tanstack/db to angular-db source
- Remove nonexistent math functions from skill_tree.yaml
- Remove no-op !skills/_artifacts exclusion from db/package.json
- Add isIdle/isCleanedUp to Angular and React SKILL.md
- Document deprecated query.data in Solid SKILL.md
- Promote Svelte deps mistake severity to CRITICAL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add changeset for intent agent skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

* docs: apply feedback from issues #1333 and #1332

- Document schema-vs-parser dual path for Electric collections
- Add Postgres-to-Electric type mapping table
- Add TanStack Start createServerFn example for write path
- Document z.coerce.date() as Zod-specific simpler alternative
- Add duplicate @tanstack/db instance guidance with Vite alias fix
- Fix Nuxt template variable reference (data → todos)
- Update Electric complete example with schema-first + parser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add Electric client skills reference to electric-adapter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

* docs: remove redundant sections from meta-framework skill

The TanStack Start server functions section duplicates what's already in
electric-adapter.md. The duplicate @tanstack/db instances section isn't
meta-framework specific.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: apply feedback from issues #1334, #1335, #1336

- Add duplicate @tanstack/db troubleshooting to react-db skill (pnpm ls
  diagnostic, pnpm overrides fix, Vite alias fix)
- Reframe localOnlyCollectionOptions: use specified backend directly,
  only fall back to local-only when no backend exists
- Add schema to Electric example in collection-setup (without schema,
  collection types as Record<string, unknown>)
- Add server-side integration pointer to meta-framework skill
- Add Electric proxy route pattern to electric-adapter reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
update @electric-sql/client to 1.5.12

Bump @electric-sql/client from ^1.5.10 to ^1.5.12 across all packages.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.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>
* fix: resolve all eslint warnings in packages/db

Fix 16 lint warnings:
- Rename shadowed variables (no-shadow)
- Remove unnecessary async from functions without await (require-await)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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>
* Fine grained reactivity tests

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix(db): throw error when fn.select() is used with groupBy()

fn.select() with groupBy() silently produced wrong results ({ __key_0: ... })
because the compiler cannot statically analyze an opaque function to discover
aggregate expressions needed by groupBy. This adds a clear error message
directing users to use the standard .select() API instead.

Closes #1189

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

* chore: add changeset for fn.select + groupBy error

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat(db): add queryOnce API for one-shot query execution

Implements the queryOnce function as described in the RFC. This provides
a lightweight wrapper around createLiveQueryCollection that:

- Creates a live query collection with gcTime: 0
- Preloads the data
- Extracts results as an array (or single item for findOne)
- Automatically cleans up the collection

The queryOnce function:
- Accepts either a query function or config object
- Supports all query builder operations (where, select, join, orderBy, etc.)
- Properly handles findOne() queries returning single results
- Ensures cleanup happens even on error via try/finally

Use cases:
- AI/LLM context building
- Data export
- Background processing
- Testing

API:
```typescript
// Simple query
const users = await queryOnce((q) =>
  q.from({ user: usersCollection })
   .where(({ user }) => eq(user.active, true))
)

// Single result with findOne
const user = await queryOnce((q) =>
  q.from({ user: usersCollection })
   .where(({ user }) => eq(user.id, 1))
   .findOne()
)

// Config object form
const orders = await queryOnce({
  query: (q) => q.from({ order: ordersCollection }).limit(100)
})
```

* ci: apply automated fixes

* fix(db): use gcTime 1 for queryOnce cleanup

* docs: add queryOnce to live queries guide

* feat(db): allow queryOnce to accept QueryBuilder

Co-authored-by: sam.willis <sam.willis@gmail.com>

* refactor(db): tighten queryOnce runtime behavior

* ci: apply automated fixes

* fix(db): normalize queryOnce query builder input

* chore: add changeset for queryOnce

* ci: apply automated fixes

* fix(db): remove redundant queryOnce undefined fallback

Drop the unnecessary nullish-coalescing in queryOnce so the single-result path returns the iterator value directly while preserving the existing undefined-on-empty behavior.

Made-with: Cursor

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…f<T> | undefined (#1262)

* test(db): add type tests for left join select ref direct property access

Declarative select callback types currently use `Ref<T> | undefined` for
left-joined tables, forcing optional chaining. Since the callback receives
proxy refs that are always truthy, this is misleading and encourages
runtime conditional patterns that silently fail. These tests assert the
desired behavior: direct property access without optional chaining, with
nullability reflected only in the result type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(db): add right join and full join type tests for select ref direct access

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(db): use Ref<T, Nullable> brand instead of Ref<T> | undefined for nullable join refs

The declarative select() callback receives proxy objects that record property
accesses. These proxies are always truthy, but nullable join sides were typed
as Ref<T> | undefined, misleading users into using ?. and ?? operators that
have no effect at runtime. This changes nullable join refs to Ref<T, true>,
which allows direct property access while correctly producing T | undefined
in the result type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

* chore: add changeset for nullable join ref typing fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* fix: track original predicates in DeduplicatedLoadSubset to prevent unbounded WHERE growth

updateTracking() was called with the modified loadOptions (containing the
minusWherePredicates difference expression) instead of the original request
options. When a "load all" request (where=undefined) was made after
accumulating eq() predicates, the difference expression NOT(IN([...]))
was tracked instead of setting hasLoadedAllData=true. Each subsequent
"load all" request compounded the expression: NOT(IN(...) OR NOT(IN(...))),
producing deeply nested 193KB+ WHERE clauses that crashed Electric's parser.

The fix separates loadOptions (sent to backend, may contain optimized
difference query) from trackingOptions (used for tracking, preserves the
original predicate). This ensures "load all" correctly sets
hasLoadedAllData=true regardless of the optimization applied to the
actual request.

https://claude.ai/code/session_01Vp75RhjVR4tV5FdjKJbBWE

* Apply code review and simplification fixes

- Condense block comment explaining trackingOptions/loadOptions split
- Remove duplicate inline comments at updateTracking call sites
- Remove stray console.log and unused minusWherePredicates import
- Fix "exponentially" → "unboundedly" in test comment (growth is linear)
- Remove product-specific "sessions index page" reference from test
- Strengthen expression assertion to verify full NOT(IN(...)) structure
- Add sync return path test for the tracking fix
- Simplify test by removing unused calls array

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add changeset for unbounded WHERE growth fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* Fix all eslint errors and warnings across the repo

- Remove unnecessary optional chains on non-nullish values (113 instances)
- Remove unnecessary type assertions (2 instances)
- Fix async functions with no await expression
- Fix variable shadowing (rename or eslint-disable where renaming breaks API)
- Fix empty destructuring patterns in vitest fixtures
- Suppress unused vars in vitest fixture dependencies needed for ordering
- Remove stale eslint-disable directive
- Add vite.config.ts to eslint ignores (not in tsconfig)
- Change query-once-api changeset from minor to patch (pre-1.0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: apply automated fixes

* chore: change query-once-api changeset from minor to patch

Pre-1.0 package — patch is appropriate for new features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wrap onDelete/onInsert returns in Promise.resolve()

Removing async changed the return type from Promise<{refetch}> to
{refetch}, which doesn't match the mutation handler types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <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] <github-actions[bot]@users.noreply.github.com>
@kevin-dp kevin-dp force-pushed the examples/offline-transactions-react-native-persistence branch from df61339 to 1c7a974 Compare March 12, 2026 10:53
@kevin-dp kevin-dp force-pushed the kevin/persistence branch from 9ce6ae0 to 38ea25f Compare March 12, 2026 10:57
@kevin-dp kevin-dp force-pushed the kevin/persistence branch from 38ea25f to 9ce6ae0 Compare March 12, 2026 10:59
The renderer-side persistence adapter was using the default 5s timeout,
causing CI failures when running real Electron IPC. Align with the 45s
timeout already used by the other electron E2E tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants