Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
00389a4
fix: safe randomUUID helper for non-secure browser contexts (#1593)
kevin-dp Jun 17, 2026
3124c9e
Added attachments support.
Chriztiaan Jun 12, 2026
d18ab19
Types and tests.
Chriztiaan Jun 18, 2026
2147345
Export and document subtract, multiply, divide math functions (#1151)
KyleAMathews Jun 18, 2026
9a5e8e5
ci: Version Packages (#1596)
github-actions[bot] Jun 18, 2026
91c24c6
Docs.
Chriztiaan Jun 23, 2026
3770d3b
changeset.
Chriztiaan Jun 23, 2026
307fdf8
fix: preload hangs forever after cleanup (#1576) (#1606)
kevin-dp Jun 23, 2026
45617c4
ci: Version Packages (#1612)
github-actions[bot] Jun 23, 2026
38af8c5
Rename saveFileTanStack => save, deleteFIleTanStack => delete.
Chriztiaan Jun 24, 2026
d82ffc2
Made `updateHook` synchronous and updated `AttachmentQueueRow` typing.
Chriztiaan Jun 24, 2026
cd8b191
Merge branch 'main' into feat/powersync-attachments
Chriztiaan Jun 25, 2026
3e59ca5
docs: regenerate API documentation (#1477)
github-actions[bot] Jun 25, 2026
403be75
docs(browser-db-sqlite-persistence): document multi-tab coordinator u…
kevin-dp Jun 25, 2026
267b36b
docs: document materialize() helper for includes subqueries (#1580)
kevin-dp Jun 25, 2026
99d33d5
fix(react-db): defer eager onStoreChange to a microtask in useLiveQue…
kevin-dp Jun 26, 2026
ac09b11
fix(db): prevent prototype pollution via select() alias paths (#1584)…
kevin-dp Jun 26, 2026
36fb29a
fix(query): lazy-join loads through the collection the join key resol…
kevin-dp Jun 26, 2026
d79b0cd
fix: enforce all where conditions when index optimization is partial …
kevin-dp Jun 26, 2026
b05139f
ci: Version Packages (#1621)
github-actions[bot] Jun 26, 2026
e6a8cc2
docs: regenerate API documentation (#1622)
github-actions[bot] Jun 26, 2026
2b27dd1
fix: reconcile duplicate live query child inserts (#1600)
KyleAMathews Jun 26, 2026
928afa4
ci: Version Packages (#1624)
github-actions[bot] Jun 26, 2026
99e9afe
fix(db): preserve discriminated union types through .select() (#1511)…
kevin-dp Jun 29, 2026
816b667
fix(db): nested toArray includes drop children when sibling groups sh…
kevin-dp Jun 29, 2026
7ce457e
Fix unused vars (#1627)
Jaime02 Jun 30, 2026
2b3d5e5
ci: Version Packages (#1628)
github-actions[bot] Jun 30, 2026
f7da776
fix(sqlite-persistence): preserve query owner metadata on insert (clo…
kevin-dp Jul 1, 2026
397e12a
fix(db): avoid full row origin snapshots (#1640)
samwillis Jul 2, 2026
95e25bd
ci: Version Packages (#1632)
github-actions[bot] Jul 2, 2026
3ec3e7a
test: cross-adapter useLiveQuery conformance suite
kevin-dp Jul 7, 2026
18ba740
fix(query-db-collection): keep on-demand query metadata clone-safe (#…
KyleAMathews Jul 7, 2026
a93abf6
ci: Version Packages (#1651)
github-actions[bot] Jul 8, 2026
6d4c096
refactor(db): extract shared live-query adapter helpers (RFC #1623 st…
kevin-dp Jul 9, 2026
b2f305a
fix(svelte-db): disabled useLiveQuery no longer crashes (#1637)
kevin-dp Jul 9, 2026
42fb59c
fix(angular-db): config-object input to injectLiveQuery now syncs (#1…
kevin-dp Jul 9, 2026
f3ade65
test(conformance): parametrize how adapters surface query errors (#1639)
kevin-dp Jul 9, 2026
e147721
test(query-db-collection): cover query invalidation behavior (#1655)
KyleAMathews Jul 9, 2026
b2b8923
docs(query-db-collection): clarify select row extraction (#1654)
KyleAMathews Jul 9, 2026
580f9d0
docs(query-db-collection): document query option compatibility (#1653)
KyleAMathews Jul 9, 2026
eabcea7
Clarify local write status docs and pending sync contract (#1652)
KyleAMathews Jul 9, 2026
aa41c49
fix: use patch bump for adapter helpers (#1668)
KyleAMathews Jul 13, 2026
cbd6081
refactor(query-db-collection): extract query ownership helpers (#1664)
KyleAMathews Jul 13, 2026
d64e1ab
feat(query-db-collection): support more top-level query options (#1665)
KyleAMathews Jul 13, 2026
e51d008
docs(query-db-collection): document runtime QueryClient factory patte…
KyleAMathews Jul 13, 2026
09d7d67
ci: Version Packages (#1661)
github-actions[bot] Jul 13, 2026
6ed8b15
fix(electric-db-collection): preserve persisted rows on progressive r…
pkudinov Jul 13, 2026
286964d
fix(db): preserve explicit gcTime of 0 on live query collections (#1660)
spokodev Jul 13, 2026
73e5b67
test(query-db-collection): characterize cancellation and subset clean…
KyleAMathews Jul 16, 2026
d2a11b1
fix(electric): bound refresh wait for on-demand subsets (#1575)
KyleAMathews Jul 16, 2026
82b90a9
docs: regenerate API documentation (#1629)
github-actions[bot] Jul 16, 2026
4d0b1c0
docs(query-db-collection): document scoped collection factories (#1671)
KyleAMathews Jul 16, 2026
b850340
docs: add contributing guide (#1620)
KyleAMathews Jul 16, 2026
8258d09
fix(db): restore query typechecking for generic collection row types …
obeattie Jul 16, 2026
f89fad1
ci: Version Packages (#1670)
github-actions[bot] Jul 16, 2026
7f0fa36
docs: regenerate API documentation (#1680)
github-actions[bot] Jul 16, 2026
a9e13d5
test: cover late query readiness rejection (#1681)
KyleAMathews Jul 20, 2026
932910d
fix(query-db): clean up empty ownership sets (#1672)
KyleAMathews Jul 20, 2026
f42db5c
feat(query-db): support eager initial data (#1683)
KyleAMathews Jul 20, 2026
2250d35
Merge branch 'main' into feat/powersync-attachments
Chriztiaan Jul 21, 2026
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
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/clean-query-ownership.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/query-db-collection': patch
---

Clean up empty query ownership state while preserving authoritative empty results and retained-row lifecycle behavior.
6 changes: 6 additions & 0 deletions .changeset/curly-planets-lead.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/powersync-db-collection': patch
---

Add attachments support via `TanStackDBAttachmentQueue`. This extends the PowerSync SDK's `AttachmentQueue` and backs it with
a TanStack DB collection, so file uploads/deletes are managed atomically alongside the relational data.
7 changes: 7 additions & 0 deletions .changeset/young-cats-initialize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/query-db-collection': minor
---

Add eager collection support for TanStack Query `initialData` and `initialDataUpdatedAt`, including wrapped response projection and collection-local initialization on shared QueryClient instances.

QueryClient-default `placeholderData` no longer materializes as collection rows, and QueryClient-default `initialData` no longer seeds on-demand subset observers.
146 changes: 146 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Contributing

## Questions

If you have questions about implementation details, help, or support, please use our dedicated community forum at [GitHub Discussions](https://github.com/TanStack/db/discussions). **PLEASE NOTE:** If you choose to open an issue for your question instead, your issue may be closed and redirected to the forum.

## Reporting issues

If you have found what you think is a bug, please [file an issue](https://github.com/TanStack/db/issues/new/choose). **PLEASE NOTE:** Issues that are identified as implementation questions or non-issues may be closed and redirected to [GitHub Discussions](https://github.com/TanStack/db/discussions).

## Suggesting new features

If you are here to suggest a feature, first create an issue if it does not already exist. From there, we can discuss use cases for the feature and how it could be implemented.

## Development

If you have been assigned to fix an issue or develop a new feature, please follow these steps to get started:

- Fork this repository.
- Use the Node.js version mentioned in `.nvmrc`.

```bash
nvm use
```

- Enable [Corepack](https://nodejs.org/api/corepack.html) so the [pnpm](https://pnpm.io/) version mentioned in `package.json` is used.

```bash
corepack enable
```

- Install dependencies.

```bash
pnpm install
```

- Build all packages.

```bash
pnpm build
```

- Run tests.

```bash
pnpm test
```

- Run linting.

```bash
pnpm lint
```

- Implement your changes and tests in the relevant package or example.
- Document your changes in the appropriate doc page.
- Git stage your required changes and commit them.
- Submit a PR for review.

### Editing the docs locally and previewing changes

The documentation for all TanStack projects is hosted on [tanstack.com](https://tanstack.com), which is a TanStack Start application (https://github.com/TanStack/tanstack.com). You need to run this app locally to preview your changes in the `TanStack/db` docs.

> [!NOTE]
> The website fetches doc pages from GitHub in production, and searches for them at `../db/docs` in development. Your local clone of `TanStack/db` needs to be in the same directory as the local clone of `TanStack/tanstack.com`.

You can follow these steps to set up the docs for local development:

1. Make a new directory called `tanstack`.

```sh
mkdir tanstack
```

2. Enter that directory and clone the [`TanStack/db`](https://github.com/TanStack/db) and [`TanStack/tanstack.com`](https://github.com/TanStack/tanstack.com) repos.

```sh
cd tanstack
git clone git@github.com:TanStack/db.git
# We probably don't need all the branches and commit history
# from the `tanstack.com` repo, so let's just create a shallow
# clone of the latest version of the `main` branch.
# Read more about shallow clones here:
# https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/#user-content-shallow-clones
git clone git@github.com:TanStack/tanstack.com.git --depth=1 --single-branch --branch=main
```

> [!NOTE]
> Your `tanstack` directory should look like this:
>
> ```text
> tanstack/
> |
> +-- db/ (<-- this directory cannot be called anything else!)
> |
> +-- tanstack.com/
> ```

3. Enter the `tanstack/tanstack.com` directory, install the dependencies, and run the app in dev mode.

```sh
cd tanstack.com
pnpm i
# The app will run on http://localhost:3000 by default
pnpm dev
```

4. Visit http://localhost:3000/db/latest/docs/overview in the browser and see the changes you make in `tanstack/db/docs` there.

> [!WARNING]
> You will need to update `docs/config.json` if you add a new documentation page.

### Running examples

- Make sure you've installed dependencies in the repo's root directory.

```bash
pnpm install
```

- If you want to run an example against your local changes, run the relevant package build/watch command from the repo root if needed. Otherwise, examples may run against the latest published TanStack DB release.

- Run the example from the selected example directory.

```bash
pnpm dev
```

#### Note on standalone execution

If you want to run an example without installing dependencies for the whole repo, follow the instructions from the example's README.md file. It will then run against the latest TanStack DB release.

## Changesets

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.

## Pull requests

Maintainers merge pull requests by squashing all commits and editing the commit message if necessary using the GitHub user interface.

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.
167 changes: 167 additions & 0 deletions docs/collections/powersync-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -1099,4 +1099,171 @@ const liveQuery = createLiveQueryCollection({
completed: todo.completed,
})),
})
```

## Attachments

`@tanstack/powersync-db-collection` ships `TanStackDBAttachmentQueue`, an [`AttachmentQueue`](https://docs.powersync.com/usage/use-case-examples/attachments-files) whose file operations commit inside a TanStack DB collection transaction. This lets you create (or delete) an attachment and mutate a related collection row (for example, setting `lists.photo_id`) atomically in a single transaction, instead of issuing two independent writes.

The queue extends PowerSync's `AttachmentQueue`, so the generic concepts are unchanged and documented once in the SDK.

> This section only covers what is specific to the TanStack DB integration. For storage adapters (local and remote), the `AttachmentTable` schema primitive, error-handling/retry semantics, and the `startSync()` / `stopSync()` lifecycle, see the [PowerSync attachments documentation](https://docs.powersync.com/usage/use-case-examples/attachments-files).

### Prerequisites

These are standard PowerSync attachment requirements. See the SDK attachments docs for details.

- An `AttachmentTable` in your schema:

```ts
import { AttachmentTable, Schema } from "@powersync/web"

const APP_SCHEMA = new Schema({
// ...your tables
attachments: new AttachmentTable(),
})
```

- A local storage adapter (such as `IndexDBFileSystemStorageAdapter` on web) and a remote storage adapter (an implementation of the SDK's `RemoteStorageAdapter`, for example backed by Supabase Storage). Both are generic to all attachment users. See the SDK docs for the available adapters and the remote-adapter contract.

### 1. Create the attachments collection

This is the piece that makes the integration TanStack-aware: a normal PowerSync collection over the attachments table. The queue reads and writes attachment records through it.

```ts
import { createCollection } from "@tanstack/react-db"
import { powerSyncCollectionOptions } from "@tanstack/powersync-db-collection"

const attachmentsCollection = createCollection(
powerSyncCollectionOptions({
database: db,
table: APP_SCHEMA.props.attachments,
})
)
```

### 2. Construct the queue

Pass your collection as `attachmentsCollection` alongside the standard `AttachmentQueue` options. Only `attachmentsCollection` and `watchAttachments` (below) are specific to this package; `db`, `localStorage`, `remoteStorage`, and `errorHandler` are the usual SDK options.

```ts
import { TanStackDBAttachmentQueue } from "@tanstack/powersync-db-collection"

const attachmentQueue = new TanStackDBAttachmentQueue({
db,
attachmentsCollection, // TanStack DB collection over your AttachmentTable
localStorage, // SDK local storage adapter
remoteStorage, // your RemoteStorageAdapter (see SDK docs)
watchAttachments, // see step 3
errorHandler, // standard AttachmentQueue error handler (see SDK docs)
})
```

Start and stop syncing with the standard `attachmentQueue.startSync()` / `attachmentQueue.stopSync()` lifecycle (see SDK docs), typically inside a React effect or provider.

### 3. Tell the queue which attachments exist (`watchAttachments`)

`watchAttachments` reports the set of attachment IDs your data currently references, so the queue knows what to download and what to archive. With TanStack DB you drive it from a live query: emit the initial state, then re-emit the complete set on every change, and clean up on abort.

```ts
import {
createCollection,
isNull,
liveQueryCollectionOptions,
not,
} from "@tanstack/db"
import { WatchedAttachmentItem } from "@powersync/web"

const watchAttachments = async (onUpdate, abortSignal) => {
// Every row in your data model that references an attachment.
const livePhotoIds = createCollection(
liveQueryCollectionOptions({
query: (q) =>
q
.from({ document: listsCollection })
.where(({ document }) => not(isNull(document.photo_id)))
.select(({ document }) => ({ photo_id: document.photo_id })),
})
)

const mapper = (item) =>
({
id: item.photo_id,
fileExtension: "jpg",
}) satisfies WatchedAttachmentItem

// 1. Report the initial set of referenced attachment IDs.
const initialState = await livePhotoIds.stateWhenReady()
onUpdate(Array.from(initialState.values()).map(mapper))

// 2. Re-emit the whole set on every change (the queue expects the holistic state).
livePhotoIds.subscribeChanges(() => {
onUpdate(livePhotoIds.map(mapper))
})

// 3. Clean up when sync stops.
abortSignal.addEventListener("abort", () => livePhotoIds.cleanup(), {
once: true,
})
}
```

> A `watchAttachmentsFromQuery(...)` convenience helper that collapses this boilerplate into a single call is planned. Until then, use the pattern above.

### 4. Save an attachment atomically with related data

`save` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all.

```ts
await attachmentQueue.save({
data, // file bytes (ArrayBuffer / base64, per your local adapter)
fileExtension: "jpg",
updateHook: async (attachmentRecord) => {
// Runs in the same transaction as the attachment insert.
listsCollection.insert({
id: crypto.randomUUID(),
name,
created_at: new Date(),
owner_id: userID,
photo_id: attachmentRecord.id, // associate the row with the attachment
})
},
})
```

### 5. Delete an attachment and detach it from the row

`delete` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent.

```ts
await attachmentQueue.delete({
id: photo_id,
updateHook: async () => {
listsCollection.update(listId, (draft) => {
draft.photo_id = null
})
},
})
```

### 6. Display attachments via a live-query join

Join your attachments collection into a live query to read the local URI (the locally cached file path) alongside your domain rows:

```ts
import { eq } from "@tanstack/db"

const { data } = useLiveQuery((q) =>
q
.from({ lists: listsCollection })
.leftJoin({ attachment: attachmentsCollection }, ({ lists, attachment }) =>
eq(lists.photo_id, attachment.id)
)
.select(({ lists, attachment }) => ({
id: lists.id,
name: lists.name,
photo_id: lists.photo_id,
attachment_local_uri: attachment?.local_uri,
}))
)
```
Loading
Loading