You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/blog/tanstack-db-0.6-app-ready-with-persistence-and-includes.md
+48-34Lines changed: 48 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
---
2
2
title: TanStack DB 0.6 Now Includes Persistence, Offline Support, and Hierarchical Data
3
3
excerpt: TanStack DB 0.6 adds SQLite-backed persistence across runtimes, hierarchical includes for projecting normalized data into UI-shaped trees, reactive effects, virtual props for sync state, and more.
4
-
published: 2026-03-19
4
+
published: 2026-03-25
5
5
draft: true
6
6
authors:
7
7
- Sam Willis
@@ -10,14 +10,14 @@ authors:
10
10
11
11

12
12
13
-
TanStack DB 0.6 is the release that lands some highly anticipated features that many of you have been asking for, making it a lot more ergonomic for app development.
13
+
TanStack DB 0.6 is the release that brings some highly anticipated features many of you have been asking for, making it much more ergonomic for app development.
14
14
15
-
You can now project normalized data into the same hierarchical structure as your UI. You can optionally persist local state with a SQLite-backed persistence layer across runtimes. You can trigger reactive side effects from live queries. You can build outbox views and WhatsApp-style delivery indicators directly from row metadata. And a few APIs that used to rely on implicit magic are now getting more explicit and uniform.
15
+
You can now project normalized data into the same hierarchical structure as your UI. You can optionally persist local state with a SQLite-backed persistence layer across runtimes. You can trigger reactive side effects from live queries. You can build outbox views and WhatsApp-like delivery indicators directly from row metadata. And a few APIs that used to rely on implicit magic are now explicit and uniform.
16
16
17
17
Here is what shipped:
18
18
19
-
-[Persisted local state](#persisted-local-state) with an optional SQLite-backed persistence layer across browser, React Native, Expo, Node, Electron, Capacitor, Tauri, and Cloudflare Durable Objects
20
-
-[Includes](#includes-project-your-data-into-the-same-shape-as-your-ui) for projecting normalized data into the same hierarchical structure as your UI. Similar to GraphQL, but without the need for new infrastructure.
19
+
-[Persistent local state](#persistent-local-state) with adapters for SQLite persistence across browser, React Native, Expo, Node, Electron, Capacitor, Tauri, and Cloudflare Durable Objects
20
+
-[Includes](#includes-project-your-data-into-the-same-shape-as-your-ui) for projecting normalized data into the hierarchical structure of your UI. Similar to GraphQL, but without the need for new infrastructure.
21
21
-[`createEffect`](#createeffect-reactive-side-effects-for-workflows-tools-and-agents) for workflows, side effects, and agent-style automation
22
22
-[Virtual props](#virtual-props-outboxes-delivery-state-and-row-provenance) like `$synced` and `$origin` for outbox views, sync indicators, and provenance-aware queries
23
23
-[`queryOnce`](#queryonce) for one-shot queries using the same query language as live queries
@@ -33,28 +33,36 @@ Finally, we are also putting out [a call for server-side rendering (SSR) design
33
33
34
34
One of the best examples of what 0.6 unlocks is our React Native shopping list demo.
It starts up from persisted SQLite state through `op-sqlite`, projects normalized data into a hierarchical UI shape with [includes](#includes-project-your-data-into-the-same-shape-as-your-ui), and still keeps TanStack DB's fine-grained reactivity underneath. But the really important thing is what that persistence unlocks when you pair it with [`@tanstack/offline-transactions`](https://github.com/TanStack/db/tree/main/packages/offline-transactions).
46
+
It starts from persisted SQLite state through `op-sqlite`, projects normalized data into a hierarchical UI shape with [includes](#includes-project-your-data-into-the-same-shape-as-your-ui), and still keeps TanStack DB's fine-grained reactivity underneath. But the really important thing is what that persistence unlocks when you pair it with [`@tanstack/offline-transactions`](https://github.com/TanStack/db/tree/main/packages/offline-transactions).
39
47
40
48
TanStack DB already had the query engine, transaction model, optimistic updates, and the offline transaction API. Persistence was the missing piece. Once local state is durable, that stack can add up to something fully local-first instead of only feeling local while the app is open.
41
49
42
50
### More than local-first
43
51
44
-
Persistence is the feature people asked for, but it does not define TanStack DB. The core idea is simpler: put a real query engine and transaction engine on the client, and let storage and synchronization live wherever they belong. Local-first is one configuration of that. Server-authoritative with fast optimistic updates is another. The same primitives support both.
52
+
Persistence is the feature people asked for, but it does not define TanStack DB. The core idea is simpler: put a real transactional query engine on the client, and let storage and synchronization live wherever they belong. Local-first is one configuration of that. Server-authoritative with fast optimistic updates is another. Both are supported by the same primitives.
45
53
46
-
## Persisted local state
54
+
## Persistent local state
47
55
48
56
Persistence is the biggest practical unlock in 0.6.
49
57
50
-
We have wanted a persistence story for a while, and a lot of you have asked for it too. The problem space was always broader than just "save some rows to disk":
58
+
We wanted a persistence layer for a while, and a lot of you have asked for it too. The problem space was always broader than just "save some rows to disk":
51
59
52
60
- persistence is not only about faster startup
53
61
- it needs to compose with synced remote state and optimistic local state
54
62
- it needs to work across multiple runtimes
55
63
- it needs to support large datasets without assuming everything lives in memory
56
64
- it needs to work across multiple tabs and windows
57
-
- and it needs a sane story for schema evolution
65
+
- and it needs a sane approach to schema evolution
58
66
59
67
That led us to a pragmatic choice: **use SQLite as the persistence layer**.
60
68
@@ -65,10 +73,10 @@ That gives TanStack DB one persistence model that can span:
65
73
- Node
66
74
- Electron
67
75
- Tauri
68
-
-Capacitior
76
+
-Capacitor
69
77
- Cloudflare Durable Objects
70
78
71
-
Instead of inventing a different storage story for each environment, we can keep one persistence model and swap in runtime-specific adapters. The result is optional persisted local state that can enable a local-first application, without limiting TanStack DB to only local-first use cases.
79
+
Instead of implementing a different storage layer for each environment, we can keep one persistence model and swap in runtime-specific adapters. The result is optional persistent local state that enables local-first applications, without limiting TanStack DB to local-first use cases.
72
80
73
81
For synced collections, persistence does **not** change the source of truth. The server is still authoritative. Persistence gives you a durable local base to start from quickly, work against offline, and then reconcile back to the upstream source of truth when sync resumes.
That gives you a durable local base for a synced collection. Pair it with `@tanstack/offline-transactions`, and you also get durable writes for a fully local-first flow.
125
+
That gives you a durable local base for a synced collection. Pair it with `@tanstack/offline-transactions`, and you also get durable writes for a local-first flow.
118
126
119
127
You can also use `persistedCollectionOptions(...)` without wrapping another synced collection config at all. In that mode, it is simply local state persisted to SQLite:
`schemaVersion` is the switch that keeps those two modes honest. For synced collections, changing it tells TanStack DB to clear the persisted local copy and re-sync from the server. For unsynced local-only collections, changing it throws and requires the application to migrate the data itself.
133
141
134
-
That same persistence story also opens the door to runtimes outside the UI. As you'll see later in [createEffect](#createeffect-reactive-side-effects-for-workflows-tools-and-agents), a persisted TanStack DB running in something like a Cloudflare Durable Object starts to look a lot like a state engine for workflows and agents.
142
+
That same persistence story also opens the door to runtimes outside the UI. As you'll see later in [createEffect](#createeffect-reactive-side-effects-for-workflows-tools-and-agents), a persistent TanStack DB running in something like a Cloudflare Durable Object starts to look a lot like a state engine for workflows and agents.
135
143
136
144
### Why SQLite
137
145
@@ -141,18 +149,18 @@ We considered a split design where the browser would use IndexedDB directly to a
141
149
142
150
Standardizing on one persistence engine keeps the design simpler and lets us carry the same persistence model into mobile, desktop, server, edge, and agent-style runtimes instead of inventing a different system for each one.
143
151
144
-
We also weighed the cost of the WASM bundle. In practice, if users are already syncing data to the user's device, the extra cost of shipping SQLite WASM is relatively small. They are already pulling down meaningful application data, so paying a bit more upfront for a much cleaner persistence and query model felt like the right tradeoff.
152
+
We also weighed the cost of the WASM bundle. In practice, if users are already syncing data to their devices, the extra cost of shipping SQLite WASM is relatively small. They are already pulling down meaningful application data, so paying a bit more upfront for a much cleaner persistence and query model feels like the right tradeoff.
145
153
146
154
### Why this matters
147
155
148
156
In practice, 0.6 gives you:
149
157
150
-
-apps can restart warm instead of cold
158
+
-fast restarts for your apps
151
159
- local state, both synced and pending mutations, can survive reloads and app restarts
152
160
- offline-friendly UX becomes much more practical
153
-
- the same DB mental model can move between mobile, browser, desktop, server, edge, and agent runtimes
161
+
- the same DB mental model applies to all runtimes: mobile, browser, desktop, server, edge, and even AI agents
154
162
155
-
This is the first _alpha_ release of persistence, and so we are looking for feedback and testing - we want to hear your feedback.
163
+
This is the first _alpha_ release of persistence, and so we want to hear your feedback.
156
164
157
165
## Includes: project your data into the same shape as your UI
158
166
@@ -162,7 +170,7 @@ But most data systems make you choose between flat relational queries that you t
162
170
163
171
GraphQL tackles a similar problem from the server side: give the UI a hierarchical shape without forcing every client to manually stitch flat records back together.
164
172
165
-
`includes` is TanStack DB's answer to that same problem from the client side. It lets you retrieve normalized data and project it directly into the hierarchical structure your UI wants to render, over any data source TanStack DB can sit on top of, without needing GraphQL-specific infrastructure.
173
+
`includes` is TanStack DB's answer to that same problem from the client side. It lets you retrieve normalized data and project it directly into the hierarchical structure rendered by your UI, over any TanStack DB data source, without needing GraphQL-specific infrastructure.
166
174
167
175
Instead of flattening `projects`, `issues`, and `comments` into repeated rows and rebuilding the tree yourself, you can express the hierarchy directly in the query:
The query above fetches all projects and, for each one, includes its issues by means of a nested query on the issues collection. The result is a collection of `{ id, name, issues }` objects where the issues themselves are also collections.
202
+
The query above fetches all projects and, for each one, includes its issues and each issue's comments through nested sub-queries. The result is a collection of `{ id, name, issues }` objects where the nested fields are also collections.
188
203
189
204
### Why this is different
190
205
@@ -195,7 +210,7 @@ The key thing here is that the whole nested query is executed as **one increment
195
210
- if the engine has to go back to the server for multiple rows of an include, it does that once, not once per row
196
211
- it keeps the same fine-grained incremental update model as the rest of TanStack DB
197
212
198
-
So this is not just a nicer projection API. It is also a performance and systems story.
213
+
So this is not just a nicer projection API. It is also a performance and systems improvement.
199
214
200
215
### Fine-grained reactivity by default
201
216
@@ -243,9 +258,9 @@ function IssueList({ issuesCollection }) {
243
258
}
244
259
```
245
260
246
-
### `toArray()` when you want materialised projections
261
+
### `toArray()` when you want materialized projections
247
262
248
-
Sometimes you do not want a child collection. For simple aggregates, short lists like tags, or other places where you do not want a child render boundary, `toArray()` lets you materialize the child query directly in the projection layer.
263
+
Sometimes you do not want a child collection. For simple aggregates, short lists like tags, or other places where it's better to avoid a child render boundary, `toArray()` lets you materialize the child query directly in the projection layer.
@@ -273,12 +288,11 @@ With `toArray()`, the parent row is re-emitted when the child data changes. With
273
288
274
289
Includes in 0.6 support:
275
290
276
-
- nested child collections by default
291
+
-arbitrarily nested subqueries with nested child collections by default
277
292
-`toArray()` when you want materialized arrays instead
278
293
- aggregates in child subqueries
279
294
-`orderBy()` and `limit()` inside subqueries
280
295
- child subqueries that filter based on their parent row
281
-
- arbitrarily nested subqueries
282
296
- usage patterns that preserve fine-grained updates at each level across all supported frameworks
283
297
284
298
Taken together, this is one of the biggest features in the release. It makes TanStack DB more suitable for building application-shaped views over normalized data.
@@ -287,7 +301,7 @@ Taken together, this is one of the biggest features in the release. It makes Tan
287
301
288
302
`createEffect` adds a reactive side-effect layer on top of live queries.
289
303
290
-
You can think of it a little bit like a database trigger, except it runs on the result of an arbitrary live query instead of only on writes to a single table. That means you can define side effects from the shape of the data you care about, not just from raw mutations at the storage layer.
304
+
You can think of it like a database trigger, except it runs on the result of an arbitrary live query instead of only on writes to a single table. That means you can define side effects from the shape of the data you care about, not just from raw mutations at the storage layer.
291
305
292
306
Effects also do **not** materialize the full result of the query into a collection first. They run incrementally on query-result deltas, which keeps them low-memory and makes them a much better fit for workflow logic than "subscribe to a whole collection and diff it yourself", especially because the query engine itself is already incremental.
293
307
@@ -320,7 +334,7 @@ const effect = createEffect({
320
334
awaiteffect.dispose()
321
335
```
322
336
323
-
Combined with [persisted local state](#persisted-local-state) in something like a Cloudflare Durable Object, TanStack DB starts to look like a durable state engine for agent workflows, not just a UI data layer. This is only one example, but it shows why the 0.6 features matter together: [includes](#includes-project-your-data-into-the-same-shape-as-your-ui), [virtual props](#virtual-props-outboxes-delivery-state-and-row-provenance), and reactive effects all compose into something much more powerful than any one feature on its own.
337
+
Combined with [persistent local state](#persistent-local-state) in something like a Cloudflare Durable Object, TanStack DB starts to look like a durable state engine for agent workflows, not just a UI data layer. This is only one example, but it shows why the 0.6 features matter together: [includes](#includes-project-your-data-into-the-same-shape-as-your-ui), [virtual props](#virtual-props-outboxes-delivery-state-and-row-provenance), and reactive effects all compose into something much more powerful than any one feature on its own.
324
338
325
339
## Virtual props: outboxes, delivery state, and row provenance
326
340
@@ -333,11 +347,11 @@ They are:
333
347
-`$key`: the row key for the result
334
348
-`$collectionId`: the source collection ID
335
349
336
-
That gives you access to state that used to be awkward or bolted on.
350
+
That gives you access to state that used to be hidden.
337
351
338
352
You can use them for workflow automation together with `createEffect`, but they are also immediately useful for UI:
339
353
340
-
- an outbox view of un-persisted data
354
+
- an outbox view of unpersisted data
341
355
- a delivery or sync state badge
342
356
- the little double-tick style UI we are used to from apps like WhatsApp
343
357
@@ -375,7 +389,7 @@ Not every query needs to stay live.
375
389
- tests
376
390
- AI and LLM context building
377
391
378
-
It is a small feature, but it rounds out the API in an important way. You can now use the same query language for both reactive and one-off reads.
392
+
It is a small feature, but it completes the API in an important way. You can now use the same query language for both reactive and one-off reads.
We are also removing the "magic return" behavior from mutation handlers in favor of the more explicit and uniform model. The explicit options were already there. They are not new in 0.6. What is changing is that we are standardizing on one clear way to do it.
454
+
We are also removing the "magic return" behavior from mutation handlers in favor of a more explicit and uniform model. The explicit options were already there. They are not new in 0.6. What is changing is that we are standardizing on one clear way to do it.
441
455
442
456
The important rule is simple:
443
457
@@ -479,9 +493,9 @@ But there is still one major missing piece on the path to v1: **server-side rend
479
493
480
494
TanStack DB is different from TanStack Query and from a classic API-driven application architecture. The SSR story is not just "do what Query does, but for DB". DB has a different execution model, a different relationship between local and remote state, and a different set of tradeoffs around hydration, persistence, and live updates.
481
495
482
-
So rather than rushing into a shallow solution, we want design partners. We are actively exploring the shape of SSR support for TanStack DB, and we want to hear from teams who are interested in using it seriously.
496
+
So rather than rushing into a shallow solution, we want to think this through with design partners. We are actively exploring the shape of SSR support for TanStack DB, and we want to hear from teams interested in using it seriously.
483
497
484
-
If that is you, please fill out the design partner form and tell us about your app, your constraints, and what a good SSR story for DB would need to look like. We will set up calls with teams, interview them to understand the requirements, and run proposals past them as we shape the design.
498
+
If that is you, please fill out the design partner form and tell us about your app, your constraints, and what a good SSR story for DB would need to look like. We will set up calls with teams, interview them to understand the requirements and run proposals past them as we shape the design.
485
499
486
500
-[Fill out the SSR design partner form](https://docs.google.com/forms/d/e/1FAIpQLSdoCZ_Z5uODArGpGkVI4tbU7q9qHAcGAXYYEoP9HFq3aKNs3A/viewform?usp=publish-editor).
0 commit comments