Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
30 changes: 30 additions & 0 deletions .changeset/first-rc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'@tanstack/angular-db': major
'@tanstack/browser-db-sqlite-persistence': major
'@tanstack/capacitor-db-sqlite-persistence': major
'@tanstack/cloudflare-durable-objects-db-sqlite-persistence': major
'@tanstack/db': major
'@tanstack/db-ivm': major
'@tanstack/db-sqlite-persistence-core': major
'@tanstack/electric-db-collection': major
'@tanstack/electron-db-sqlite-persistence': major
'@tanstack/expo-db-sqlite-persistence': major
'@tanstack/node-db-sqlite-persistence': major
'@tanstack/offline-transactions': patch
'@tanstack/powersync-db-collection': major
'@tanstack/query-db-collection': patch
'@tanstack/react-db': major
'@tanstack/react-native-db-sqlite-persistence': major
'@tanstack/react-router-with-db': major
'@tanstack/rxdb-db-collection': major
'@tanstack/solid-db': major
'@tanstack/svelte-db': major
'@tanstack/tauri-db-sqlite-persistence': major
'@tanstack/trailbase-db-collection': major
'@tanstack/vue-db': major
---

Release the first 1.0 candidate. Remove deprecated pre-1.0 APIs and give
source metadata precedence over colliding selected fields in live queries.
Move the two packages already on stable 1.x to a compatible peer dependency on
`@tanstack/db`, so they can share the app's DB instance during the RC.
2 changes: 2 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3
- name: Check lint
run: pnpm run lint:check
- name: Check release versioning
run: pnpm run test:release
minified-db:
name: Minified DB public API
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3
- name: Check lint
run: pnpm run lint:check
- name: Check release versioning
run: pnpm run test:release

test-db:
name: Test (${{ matrix.group }})
Expand Down
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ If you want to run an example without installing dependencies for the whole repo

This repo uses [Changesets](https://github.com/changesets/changesets) to automate releases. If your PR should release a new package version (patch, minor, or major), please run `pnpm changeset` and commit the generated file. If your PR affects docs, examples, styles, etc., you probably don't need to generate a changeset.

During the 1.0 release candidate period, use the same Changeset types. For a
package at `1.0.0-rc.1`, a patch produces `1.0.0-rc.1.1`, and another patch
produces `1.0.0-rc.1.2`. A minor or major change advances the candidate number:
`1.0.0-rc.1.2` becomes `1.0.0-rc.2`. Packages already on stable 1.x versions,
including `@tanstack/query-db-collection` and
`@tanstack/offline-transactions`, continue to use normal semantic versioning
and publish under npm's `latest` tag.

## Pull requests

Maintainers merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface.
Expand All @@ -159,3 +167,12 @@ Use an appropriate commit type. Be especially careful with breaking changes.
## Releases

For each new commit added to `main`, a GitHub Workflow is triggered which runs the [Changesets Action](https://github.com/changesets/action). This generates a preview PR showing the impact of all changesets. When this PR is merged, the package will be published to npm.

The first 1.0 candidate Changeset enters Changesets prerelease mode through
`pnpm run changeset:version`. Leave `.changeset/pre.json` in prerelease mode
for subsequent candidate releases; the normal release workflow handles each
patch or minor Changeset. When the candidates are ready for stable 1.0, run
`pnpm changeset pre exit` and commit the resulting `.changeset/pre.json` change.
The next Version Packages PR promotes the candidate packages to `1.0.0`.
Already stable packages keep their versions unless a pending Changeset
explicitly releases them. Merge that PR to publish 1.0.
2 changes: 1 addition & 1 deletion docs/collections/electric-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ const matchFn = (message) => {

### Common Issue: awaitTxId Stalls or Times Out

A frequent issue developers encounter is that `awaitTxId` (or the transaction's `isPersisted.promise`) stalls indefinitely, eventually timing out with no error messages. The data persists correctly to the database, but the optimistic mutation never resolves.
A frequent issue developers encounter is that `awaitTxId` (or the transaction's `when('settled')`) stalls indefinitely, eventually timing out with no error messages. The data persists correctly to the database, but the optimistic mutation never resolves.

**Root Cause:** This happens when the transaction ID (txid) returned from your API doesn't match the actual transaction ID of the mutation in Postgres. This mismatch occurs when you query `pg_current_xact_id()` **outside** the same transaction that performs the mutation.

Expand Down
10 changes: 5 additions & 5 deletions docs/collections/indexed-db-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,18 @@ const insert = todos.insert({
text: 'Write a draft',
completed: false,
})
await insert.isPersisted.promise
await insert.when('settled')

const update = todos.update(id, (draft) => {
draft.completed = true
})
await update.isPersisted.promise
await update.when('settled')

const remove = todos.delete(id)
await remove.isPersisted.promise
await remove.when('settled')
```

Await `isPersisted.promise` to observe persistence success or failure. The returned transaction itself is not a Promise.
Await `when('settled')` to observe persistence success or failure. The returned transaction itself is not a Promise.

If you provide `onInsert`, `onUpdate`, or `onDelete`, the handler runs before persistence. A rejected handler leaves durable rows unchanged and rolls back its optimistic changes. Each accepted Collection batch writes rows and metadata in one IndexedDB transaction. A failed write aborts the batch.

Expand Down Expand Up @@ -210,7 +210,7 @@ For a transaction involving several Collections, call each Collection's acceptan

Automatic writes in one Collection persist in mutation order, even when their
handlers finish out of order. Later writes wait for earlier handlers and
persistence to settle before reporting `isPersisted`. Rejection contributes no
persistence to settle before reporting `when('settled')`. Rejection contributes no
durable write and allows the next mutation to proceed. Do not await a later
automatic write from an earlier handler in the same Collection: each would wait
for the other. This rule does not order separate Collections, manual acceptance,
Expand Down
2 changes: 1 addition & 1 deletion docs/collections/local-storage-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ localStorage before the mutation method returns. A later handler failure rejects
its transaction promptly, even if an earlier handler is still pending.

A handler can start another mutation on the same Collection, then return without
awaiting that mutation's `isPersisted` promise. Do not await that promise or a
awaiting that mutation's `when('settled')` promise. Do not await that promise or a
later `acceptMutations()` call inside the earlier handler. Those writes wait for
the earlier handler to return, so awaiting them would leave both pending.

Expand Down
8 changes: 4 additions & 4 deletions docs/collections/powersync-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ await documents.insert(
timestamp: Date.now(),
},
}
).isPersisted.promise
).when('settled')

// Update with metadata
await documents.update(
Expand All @@ -461,12 +461,12 @@ await documents.update(
(doc) => {
doc.name = "Report Q4 (Updated)"
}
).isPersisted.promise
).when('settled')

// Delete with metadata
await documents.delete(docId, {
metadata: { deletedBy: "user-789", reason: "duplicate" },
}).isPersisted.promise
}).when('settled')
```

#### Accessing Metadata During Upload
Expand Down Expand Up @@ -568,7 +568,7 @@ batchTx.mutate(() => {
await batchTx.commit()

// Wait for the changes to be persisted
await batchTx.isPersisted.promise
await batchTx.when('settled')
```

This approach allows you to:
Expand Down
25 changes: 13 additions & 12 deletions docs/collections/query-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ const todosCollection = createCollection(
onInsert: async ({ transaction }) => {
const newItems = transaction.mutations.map((m) => m.modified)
await api.createTodos(newItems)
// Auto-refetch happens after handler completes (pre-1.0 behavior)
// Auto-refetch happens after the handler completes
},

onUpdate: async ({ transaction }) => {
Expand All @@ -588,13 +588,13 @@ const todosCollection = createCollection(
)
```

> **Note**: QueryCollection currently auto-refetches after handlers complete. See [Controlling Refetch Behavior](#controlling-refetch-behavior) for details on this transitional behavior.
> **Note**: QueryCollection auto-refetches after handlers complete. See [Controlling Refetch Behavior](#controlling-refetch-behavior) for details.

### Controlling Refetch Behavior

> **⚠️ Transitional API**: QueryCollection currently auto-refetches after handlers complete. This behavior is deprecated and will be removed in v1.0. See the migration notes below.
> **Note:** QueryCollection 1.x retains auto-refetch after handlers complete. See the options below to control this behavior.

#### Current Behavior (Pre-1.0)
#### Current Behavior

By default, QueryCollection automatically refetches after each handler completes. To **skip** auto-refetch, return `{ refetch: false }`:

Expand All @@ -609,16 +609,17 @@ onInsert: async ({ transaction }) => {

If you don't return `{ refetch: false }`, auto-refetch happens automatically.

#### v1.0 Behavior (Future)
#### Explicit Refetch Inside a Handler

In v1.0, auto-refetch will be **removed**. Handlers will need to explicitly call `collection.utils.refetch()` when refetching is needed:
Handlers can explicitly call `collection.utils.refetch()` when they need its result before returning. Return `{ refetch: false }` to prevent a second automatic refetch:

```typescript
onInsert: async ({ transaction, collection }) => {
await api.createTodos(transaction.mutations.map((m) => m.modified))

// Explicitly trigger refetch when you need server state
await collection.utils.refetch()
return { refetch: false }
}
```

Expand All @@ -636,7 +637,7 @@ the Query fetch boundary. This rule applies whether the mutation or the refetch
starts first. It includes calls through the handler parameter, captured or
external Collection references, manual transaction mutation functions, and
`createOptimisticAction` mutation functions. Code can await the mutation's
`isPersisted.promise` when it needs the complete transaction and its queued
`when('settled')` when it needs the complete transaction and its queued
Collection application. Utilities that refetch, including `clearError()`,
inherit the same phase boundary.

Expand All @@ -655,13 +656,13 @@ boundary, `throwOnError` applies only to the Query fetch because application
happens after that call settles. A later application failure is recorded by the
Collection's error utilities.

To skip refetch in v1.0, simply don't call `refetch()`:
To skip refetch after a handler, return `{ refetch: false }`:

```typescript
onInsert: async ({ transaction }) => {
await api.createTodos(transaction.mutations.map((m) => m.modified))

// No refetch call = no refetch (v1.0 behavior)
return { refetch: false }
}
```

Expand Down Expand Up @@ -861,7 +862,7 @@ const todosCollection = createCollection(

// No need to refetch - we've already synced the server response via direct writes
// (optimistic state is automatically replaced when handler completes)
return { refetch: false } // Transitional pre-1.0 opt-out
return { refetch: false }
},

onUpdate: async ({ transaction, collection }) => {
Expand All @@ -879,7 +880,7 @@ const todosCollection = createCollection(
})

// No refetch needed since we used direct writes
return { refetch: false } // Transitional pre-1.0 opt-out
return { refetch: false }
},
})
)
Expand Down Expand Up @@ -1279,7 +1280,7 @@ has a subset owner or not. It refetches that Query when the subset is acquired,
then applies the new server result. The earlier direct write still starts no
request by itself.

To use direct writes with persistence handlers, return `{ refetch: false }` to avoid the handler's separate automatic refetch during the pre-1.0 transition. An on-demand `queryFn` still needs to return the complete current result for its pushed-down predicate, order, limit, and offset when it does run. For robust real-time synchronization, use a collection backed by a sync engine that owns snapshot and change-stream ordering.
To use direct writes with persistence handlers, return `{ refetch: false }` to avoid the handler's separate automatic refetch. An on-demand `queryFn` still needs to return the complete current result for its pushed-down predicate, order, limit, and offset when it does run. For robust real-time synchronization, use a collection backed by a sync engine that owns snapshot and change-stream ordering.

## Complete Direct Write API Reference

Expand Down
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
{
"label": "Guides",
"children": [
{
"label": "Migrate to the 1.0 RC",
"to": "guides/migrate-to-1-0-rc"
},
{
"label": "Live Queries",
"to": "guides/live-queries"
Expand Down
Loading
Loading