Skip to content
Merged
11 changes: 11 additions & 0 deletions .changeset/update-skill-docs-and-intent-keyword.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@tanstack/db': patch
'@tanstack/react-db': patch
'@tanstack/angular-db': patch
'@tanstack/solid-db': patch
'@tanstack/svelte-db': patch
'@tanstack/vue-db': patch
'@tanstack/offline-transactions': patch
---

Update all SKILL.md files to v0.6.0 with new documentation for persistence, virtual properties, queryOnce, createEffect, includes, indexing, and sync metadata. Add tanstack-intent keyword to all packages with skills.
3 changes: 2 additions & 1 deletion packages/angular-db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"keywords": [
"optimistic",
"angular",
"typescript"
"typescript",
"tanstack-intent"
],
"scripts": {
"build": "vite build",
Expand Down
2 changes: 1 addition & 1 deletion packages/angular-db/skills/angular-db/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: >
type: framework
library: db
framework: angular
library_version: '0.5.30'
library_version: '0.6.0'
requires:
- db-core
sources:
Expand Down
3 changes: 2 additions & 1 deletion packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"homepage": "https://tanstack.com/db",
"keywords": [
"optimistic",
"typescript"
"typescript",
"tanstack-intent"
],
"scripts": {
"build": "vite build",
Expand Down
6 changes: 4 additions & 2 deletions packages/db/skills/db-core/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: >
createPacedMutations. Entry point for all TanStack DB skills.
type: core
library: db
library_version: '0.5.30'
library_version: '0.6.0'
---

# TanStack DB — Core Concepts
Expand All @@ -33,6 +33,7 @@ hooks. In framework projects, import from the framework package directly.
| Query data with where, join, groupBy, select | db-core/live-queries/SKILL.md |
| Insert, update, delete with optimistic UI | db-core/mutations-optimistic/SKILL.md |
| Build a custom sync adapter | db-core/custom-adapter/SKILL.md |
| Persist collections to SQLite (offline cache) | db-core/persistence/SKILL.md |
| Preload collections in route loaders | meta-framework/SKILL.md |
| Add offline transaction queueing | offline/SKILL.md (in @tanstack/offline-transactions) |

Expand All @@ -54,8 +55,9 @@ For framework-specific hooks:
- Using React hooks? → react-db
- Preloading in route loaders (Start, Next, Remix)? → meta-framework
- Building an adapter for a new backend? → db-core/custom-adapter
- Persisting collections to SQLite? → db-core/persistence
- Need offline transaction persistence? → offline

## Version

Targets @tanstack/db v0.5.30.
Targets @tanstack/db v0.6.0.
41 changes: 30 additions & 11 deletions packages/db/skills/db-core/collection-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ description: >
(ElectricSQL real-time sync), powerSyncCollectionOptions (PowerSync SQLite),
rxdbCollectionOptions (RxDB), trailbaseCollectionOptions (TrailBase),
localOnlyCollectionOptions, localStorageCollectionOptions. CollectionConfig
options: getKey, schema, sync, gcTime, autoIndex, syncMode (eager/on-demand/
progressive). StandardSchema validation with Zod/Valibot/ArkType. Collection
lifecycle (idle/loading/ready/error). Adapter-specific sync patterns including
Electric txid tracking and Query direct writes.
options: getKey, schema, sync, gcTime, autoIndex (default off), defaultIndexType,
syncMode (eager/on-demand, plus progressive for Electric). StandardSchema validation
with Zod/Valibot/ArkType. Collection lifecycle (idle/loading/ready/error).
Adapter-specific sync patterns including Electric txid tracking, Query direct
writes, and PowerSync query-driven sync with onLoad/onLoadSubset hooks.
type: sub-skill
library: db
library_version: '0.5.30'
library_version: '0.6.0'
sources:
- 'TanStack/db:docs/overview.md'
- 'TanStack/db:docs/guides/schemas.md'
Expand Down Expand Up @@ -98,11 +99,29 @@ queryCollectionOptions({
})
```

| Mode | Best for | Data size |
| ------------- | ---------------------------------------------- | --------- |
| `eager` | Mostly-static datasets | <10k rows |
| `on-demand` | Search, catalogs, large tables | >50k rows |
| `progressive` | Collaborative apps needing instant first paint | Any |
| Mode | Best for | Data size |
| ------------- | -------------------------------------------------------------- | --------- |
| `eager` | Mostly-static datasets | <10k rows |
| `on-demand` | Search, catalogs, large tables | >50k rows |
| `progressive` | Collaborative apps needing instant first paint (Electric only) | Any |

## Indexing

Indexing is opt-in. The `autoIndex` option defaults to `"off"`. To enable automatic indexing, set `autoIndex: "eager"` and provide a `defaultIndexType`:

```ts
import { BasicIndex } from '@tanstack/db'

createCollection(
queryCollectionOptions({
autoIndex: 'eager',
defaultIndexType: BasicIndex,
// ...
}),
)
```

Without `defaultIndexType`, setting `autoIndex: "eager"` throws a `CollectionConfigurationError`. You can also create indexes manually with `collection.createIndex()` and remove them with `collection.removeIndex()`.

## Core Patterns

Expand Down Expand Up @@ -255,7 +274,7 @@ app.post('/api/todos', async (req, res) => {
})
```

`pg_current_xact_id()` must be queried inside the same SQL transaction as the mutation. Otherwise the txid doesn't match and `awaitTxId` stalls forever.
`pg_current_xact_id()` must be queried inside the same SQL transaction as the mutation. Otherwise the txid doesn't match and `awaitTxId` times out (default 5 seconds).

Source: docs/collections/electric-collection.md

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ await tx.commit()
await tx.isPersisted.promise
```

## On-Demand Sync Mode

PowerSync supports `on-demand` sync mode (query-driven sync), where only rows matching active live query predicates are loaded from SQLite into the collection. This can be combined with Sync Streams via `onLoad` (eager) or `onLoadSubset` (on-demand) hooks to also control which data the PowerSync Service syncs to the device. Use `extractSimpleComparisons` or `parseWhereExpression` to derive Sync Stream parameters dynamically from live query predicates.

## Complete Example

```typescript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,38 @@ const productsCollection = createCollection(
)
```

## Common Mistakes

### HIGH Function-based queryKey without shared prefix

Wrong:

```ts
queryCollectionOptions({
queryKey: (opts) => {
if (opts.where) {
return ['products-filtered', JSON.stringify(opts.where)]
}
return ['products-all']
},
})
```

Correct:

```ts
queryCollectionOptions({
queryKey: (opts) => {
if (opts.where) {
return ['products', JSON.stringify(opts.where)]
}
return ['products']
},
})
```

When using a function-based `queryKey`, all derived keys must share the base key (`queryKey({})`) as a prefix. TanStack Query uses prefix matching for cache operations; if derived keys don't share the base prefix, cache updates silently miss entries, leading to stale data.

## Key Behaviors

- `queryFn` result is treated as **complete state** -- missing items are deleted
Expand Down
67 changes: 58 additions & 9 deletions packages/db/skills/db-core/custom-adapter/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
name: db-core/custom-adapter
description: >
Building custom collection adapters for new backends. SyncConfig interface:
sync function receiving begin, write, commit, markReady, truncate primitives.
ChangeMessage format (insert, update, delete). loadSubset for on-demand sync.
LoadSubsetOptions (where, orderBy, limit, cursor). Expression parsing:
parseWhereExpression, parseOrderByExpression, extractSimpleComparisons,
parseLoadSubsetOptions. Collection options creator pattern. rowUpdateMode
(partial vs full). Subscription lifecycle and cleanup functions.
sync function receiving begin, write, commit, markReady, truncate, metadata
primitives. ChangeMessage format (insert, update, delete). loadSubset for
on-demand sync. LoadSubsetOptions (where, orderBy, limit, cursor). Expression
parsing: parseWhereExpression, parseOrderByExpression,
extractSimpleComparisons, parseLoadSubsetOptions. Collection options creator
pattern. rowUpdateMode (partial vs full). Subscription lifecycle and cleanup
functions. Persisted sync metadata API (metadata.row and metadata.collection)
for storing per-row and per-collection adapter state.
type: sub-skill
library: db
library_version: '0.5.30'
library_version: '0.6.0'
sources:
- 'TanStack/db:docs/guides/collection-options-creator.md'
- 'TanStack/db:packages/db/src/collection/sync.ts'
Expand Down Expand Up @@ -38,7 +40,7 @@ function myBackendCollectionOptions<T>(config: {
return {
getKey: config.getKey,
sync: {
sync: ({ begin, write, commit, markReady, collection }) => {
sync: ({ begin, write, commit, markReady, metadata, collection }) => {
let isInitialSyncComplete = false
const bufferedEvents: Array<any> = []

Expand Down Expand Up @@ -157,6 +159,53 @@ Mutation handlers must not resolve until server changes have synced back to the
4. **Version/timestamp**: wait until sync stream catches up to mutation time
5. **Provider method**: `await backend.waitForPendingWrites()`

### Persisted sync metadata

The `metadata` API on the sync config allows adapters to store per-row and per-collection metadata that persists across sync transactions. This is useful for tracking resume tokens, cursors, LSNs, or other adapter-specific state.

The `metadata` object is available as a property on the sync config argument alongside `begin`, `write`, `commit`, etc. It is always provided, but without persistence the metadata is in-memory only and does not survive reloads. With persistence, metadata is durable across sessions.

```ts
sync: ({ begin, write, commit, markReady, metadata }) => {
// Row metadata: store per-row state (e.g. server version, ETag)
metadata.row.get(key) // => unknown | undefined
metadata.row.set(key, { version: 3, etag: 'abc' })
metadata.row.delete(key)

// Collection metadata: store per-collection state (e.g. resume cursor)
metadata.collection.get('cursor') // => unknown | undefined
metadata.collection.set('cursor', 'token_abc123')
metadata.collection.delete('cursor')
metadata.collection.list() // => [{ key: 'cursor', value: 'token_abc123' }]
metadata.collection.list('resume') // filter by prefix
}
```

Row metadata writes are tied to the current transaction. When a row is deleted via `write({ type: 'delete', ... })`, its row metadata is automatically deleted. When a row is inserted, its metadata is set from `message.metadata` if provided, or deleted otherwise.

Collection metadata writes staged before `truncate()` are preserved and commit atomically with the truncate transaction.

**Typical usage — resume token:**

```ts
sync: ({ begin, write, commit, markReady, metadata }) => {
const lastCursor = metadata.collection.get('cursor') as string | undefined

const stream = subscribeFromCursor(lastCursor)
stream.on('data', (batch) => {
begin()
for (const item of batch.items) {
write({ type: item.type, key: item.id, value: item.data })
}
metadata.collection.set('cursor', batch.cursor)
commit()
})

stream.on('ready', () => markReady())
return () => stream.close()
}
```

### Expression parsing for predicate push-down

```ts
Expand Down Expand Up @@ -282,4 +331,4 @@ Source: packages/db/src/collection/sync.ts:110

Getting-started simplicity (localOnly, eager mode) conflicts with production correctness (on-demand sync, race condition prevention, proper markReady handling). Agents optimizing for quick setup tend to skip buffering, markReady, and cleanup functions.

See also: db-core/collection-setup/SKILL.md -- for built-in adapter patterns to model after.
See also: db-core/collection-setup/SKILL.md for built-in adapter patterns to model after.
Loading
Loading