From c680d98c1f74c66ce95023d4e9c68c9b11ee75d0 Mon Sep 17 00:00:00 2001 From: HimanshuKumarDutt094 Date: Sun, 21 Sep 2025 19:56:15 +0530 Subject: [PATCH 1/6] docs: add unofficial dexiejs collection option --- docs/installation.md | 10 +++++++ docs/overview.md | 65 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index 177504869..95374de54 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -104,3 +104,13 @@ npm install @tanstack/rxdb-db-collection Use `rxdbCollectionOptions` to bridge an [RxDB collection](https://rxdb.info/rx-collection.html) into TanStack DB. This gives you reactive TanStack DB collections backed by RxDB's powerful local-first database, replication, and conflict handling features. + +### Dexie (Unofficial) + +For local persistence using [Dexie.js](https://dexie.org) (IndexedDB). A small community-maintained adapter keeps a TanStack DB collection in sync with a Dexie table. + +Install: `npm install tanstack-dexie-db-collection` + +Repository: `https://github.com/HimanshuKumarDutt094/tanstack-dexie-db-collection.git` + +Note: This package is unofficial and maintained by the community. Compatibility with core packages or future releases is not guaranteed. diff --git a/docs/overview.md b/docs/overview.md index 0d2d6145a..276116518 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -69,6 +69,8 @@ Collections can be populated in many ways, including: Once you have your data in collections, you can query across them using live queries in your components. + + ### Using live queries Live queries are used to query data out of collections. Live queries are reactive: when the underlying data changes in a way that would affect the query result, the result is incrementally updated and returned from the query, triggering a re-render. @@ -157,6 +159,7 @@ There are a number of built-in collection types: 4. [`RxDBCollection`](#rxdbcollection) to integrate with [RxDB](https://rxdb.info) for local persistence and sync 5. [`LocalStorageCollection`](#localstoragecollection) for small amounts of local-only state that syncs across browser tabs 6. [`LocalOnlyCollection`](#localonlycollection) for in-memory client data or UI state +7. [`DexieCollection`](#dexiecollection) — _Unofficial_ community integration for Dexie.js (IndexedDB) You can also use: @@ -445,6 +448,68 @@ export const tempDataCollection = createCollection( > [!TIP] > LocalOnly collections are perfect for temporary UI state, form data, or any client-side data that doesn't need persistence. For data that should persist across sessions, use [`LocalStorageCollection`](#localstoragecollection) instead. +### DexieCollection (Unofficial) + +_This entry is unofficial and maintained by the community._ + +For local-first persistence using IndexedDB, there's a community package that integrates [Dexie.js](https://dexie.org) with TanStack DB. It keeps a TanStack DB collection and a Dexie table in sync, supports optimistic mutations, and can use a `codec` or a schema for parsing/serializing stored rows. + +Repository: `https://github.com/HimanshuKumarDutt094/tanstack-dexie-db-collection.git` + +Install: `npm install tanstack-dexie-db-collection` + +Two concise examples showing common workflows: + +1) Zod (TypeScript) — type-safe schema + runtime validation + +```ts +import { createCollection } from "@tanstack/react-db" +import { dexieCollectionOptions } from "tanstack-dexie-db-collection" +import { z } from "zod" + +const todoSchema = z.object({ + id: z.string(), + text: z.string(), + completed: z.boolean(), + createdAt: z.date().optional(), +}) + +const todos = createCollection( + dexieCollectionOptions({ + id: "todos", + schema: todoSchema, + getKey: (t) => t.id, + }) +) +``` + +2) Plain JS + `codec` — when stored shape differs (e.g. dates as ISO strings) + +```js +import { createCollection } from "@tanstack/react-db" +import { dexieCollectionOptions } from "tanstack-dexie-db-collection" + +const todos = createCollection( + dexieCollectionOptions({ + id: "todos", + getKey: (t) => t.id, + codec: { + parse: (stored) => ({ + ...stored, + createdAt: stored.createdAt ? new Date(stored.createdAt) : undefined, + }), + serialize: (item) => ({ + ...item, + createdAt: item.createdAt ? item.createdAt.toISOString() : undefined, + }), + }, + }) +) +``` + +Quick note: Use Zod (or another Standard Schema) for compile-time types and runtime validation. Use a `codec` when you need to transform stored shapes (ISO dates, legacy fields) without a typed schema. + + #### Derived collections Live queries return collections. This allows you to derive collections from other collections. From ee87e1d714a057da97876203cc83925bcbf84968 Mon Sep 17 00:00:00 2001 From: HimanshuKumarDutt094 Date: Sun, 21 Sep 2025 22:50:55 +0530 Subject: [PATCH 2/6] refine the dexie docs --- docs/overview.md | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/docs/overview.md b/docs/overview.md index 276116518..1123bb643 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -448,6 +448,7 @@ export const tempDataCollection = createCollection( > [!TIP] > LocalOnly collections are perfect for temporary UI state, form data, or any client-side data that doesn't need persistence. For data that should persist across sessions, use [`LocalStorageCollection`](#localstoragecollection) instead. + ### DexieCollection (Unofficial) _This entry is unofficial and maintained by the community._ @@ -458,6 +459,8 @@ Repository: `https://github.com/HimanshuKumarDutt094/tanstack-dexie-db-collectio Install: `npm install tanstack-dexie-db-collection` +Read more examples: [Dexie Collection Examples](https://github.com/HimanshuKumarDutt094/tanstack-dexie-db-collection/blob/master/EXAMPLES.md) + Two concise examples showing common workflows: 1) Zod (TypeScript) — type-safe schema + runtime validation @@ -465,14 +468,6 @@ Two concise examples showing common workflows: ```ts import { createCollection } from "@tanstack/react-db" import { dexieCollectionOptions } from "tanstack-dexie-db-collection" -import { z } from "zod" - -const todoSchema = z.object({ - id: z.string(), - text: z.string(), - completed: z.boolean(), - createdAt: z.date().optional(), -}) const todos = createCollection( dexieCollectionOptions({ @@ -483,25 +478,39 @@ const todos = createCollection( ) ``` -2) Plain JS + `codec` — when stored shape differs (e.g. dates as ISO strings) +2) Sync handlers — common persistence patterns -```js +```ts import { createCollection } from "@tanstack/react-db" import { dexieCollectionOptions } from "tanstack-dexie-db-collection" + const todos = createCollection( dexieCollectionOptions({ id: "todos", getKey: (t) => t.id, - codec: { - parse: (stored) => ({ - ...stored, - createdAt: stored.createdAt ? new Date(stored.createdAt) : undefined, - }), - serialize: (item) => ({ - ...item, - createdAt: item.createdAt ? item.createdAt.toISOString() : undefined, - }), + schema: todoSchema, + // Example A — bulk background sync using an existing mutation + onUpdate: async ({ transaction }) => { + const updates = transaction.mutations.map((m) => ({ + id: m.key, + changes: m.changes, + })) + + await bulkUpdateMutation.mutateAsync({ updates }) + }, + + // Example B — per-item sync using individual mutations + onInsert: async ({ transaction }) => { + for (const m of transaction.mutations) { + await itemCreateMutation.mutateAsync(m.modified) + } + }, + + onDelete: async ({ transaction }) => { + for (const m of transaction.mutations) { + await itemDeleteMutation.mutateAsync({ id: m.key }) + } }, }) ) From 8ab724a9a6d2e05b015f3c89189a15d8058d067d Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 22 Sep 2025 09:16:36 -0600 Subject: [PATCH 3/6] Add community page --- docs/community/resources.md | 47 +++++++++++++++++++++++++ docs/config.json | 9 +++++ docs/installation.md | 10 ------ docs/overview.md | 70 ------------------------------------- 4 files changed, 56 insertions(+), 80 deletions(-) create mode 100644 docs/community/resources.md diff --git a/docs/community/resources.md b/docs/community/resources.md new file mode 100644 index 000000000..7d5e7f00f --- /dev/null +++ b/docs/community/resources.md @@ -0,0 +1,47 @@ +# Community Resources + +This page contains a curated list of community-created packages, tools, and resources that extend or complement TanStack DB. + +## Community Packages + +### Dexie.js Integration (Unofficial) +- **[tanstack-dexie-db-collection](https://github.com/yourusername/tanstack-dexie-db-collection)** - Community-maintained Dexie.js adapter for TanStack DB + - Local persistence using [Dexie.js](https://dexie.org) (IndexedDB wrapper) + - Lightweight integration for browser-based storage + - Install: `npm install tanstack-dexie-db-collection` + - **Note:** This is an unofficial package maintained by the community. Compatibility with future TanStack DB releases is not guaranteed. + +### Contributing Your Package + +Have you created a collection adapter or integration? We'd love to feature it here! [Submit a PR](https://github.com/TanStack/db/pulls) to add your package. + +## Tools & Utilities + +*Coming soon! Share your TanStack DB development tools with the community.* + +## Examples & Templates + +### Starter Templates +*Share your starter templates and boilerplates here* + +### Example Applications +*Community-built example apps showcasing TanStack DB features* + +## Learning Resources + +### Tutorials & Guides +*Community tutorials and blog posts about TanStack DB* + +### Videos & Courses +*Video tutorials and online courses* + +## Contributing + +We welcome contributions to this community resources page! If you've created something useful for the TanStack DB ecosystem: + +1. Fork the repository +2. Add your resource to the appropriate section +3. Include a brief description and link +4. Submit a pull request + +Please ensure all submissions are relevant to TanStack DB and provide value to the community. \ No newline at end of file diff --git a/docs/config.json b/docs/config.json index d6f2da8d7..d3f4bd595 100644 --- a/docs/config.json +++ b/docs/config.json @@ -104,6 +104,15 @@ } ] }, + { + "label": "Community", + "children": [ + { + "label": "Resources & Packages", + "to": "community/resources" + } + ] + }, { "label": "API Reference", "children": [ diff --git a/docs/installation.md b/docs/installation.md index 95374de54..177504869 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -104,13 +104,3 @@ npm install @tanstack/rxdb-db-collection Use `rxdbCollectionOptions` to bridge an [RxDB collection](https://rxdb.info/rx-collection.html) into TanStack DB. This gives you reactive TanStack DB collections backed by RxDB's powerful local-first database, replication, and conflict handling features. - -### Dexie (Unofficial) - -For local persistence using [Dexie.js](https://dexie.org) (IndexedDB). A small community-maintained adapter keeps a TanStack DB collection in sync with a Dexie table. - -Install: `npm install tanstack-dexie-db-collection` - -Repository: `https://github.com/HimanshuKumarDutt094/tanstack-dexie-db-collection.git` - -Note: This package is unofficial and maintained by the community. Compatibility with core packages or future releases is not guaranteed. diff --git a/docs/overview.md b/docs/overview.md index 1123bb643..3256863de 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -448,76 +448,6 @@ export const tempDataCollection = createCollection( > [!TIP] > LocalOnly collections are perfect for temporary UI state, form data, or any client-side data that doesn't need persistence. For data that should persist across sessions, use [`LocalStorageCollection`](#localstoragecollection) instead. - -### DexieCollection (Unofficial) - -_This entry is unofficial and maintained by the community._ - -For local-first persistence using IndexedDB, there's a community package that integrates [Dexie.js](https://dexie.org) with TanStack DB. It keeps a TanStack DB collection and a Dexie table in sync, supports optimistic mutations, and can use a `codec` or a schema for parsing/serializing stored rows. - -Repository: `https://github.com/HimanshuKumarDutt094/tanstack-dexie-db-collection.git` - -Install: `npm install tanstack-dexie-db-collection` - -Read more examples: [Dexie Collection Examples](https://github.com/HimanshuKumarDutt094/tanstack-dexie-db-collection/blob/master/EXAMPLES.md) - -Two concise examples showing common workflows: - -1) Zod (TypeScript) — type-safe schema + runtime validation - -```ts -import { createCollection } from "@tanstack/react-db" -import { dexieCollectionOptions } from "tanstack-dexie-db-collection" - -const todos = createCollection( - dexieCollectionOptions({ - id: "todos", - schema: todoSchema, - getKey: (t) => t.id, - }) -) -``` - -2) Sync handlers — common persistence patterns - -```ts -import { createCollection } from "@tanstack/react-db" -import { dexieCollectionOptions } from "tanstack-dexie-db-collection" - - -const todos = createCollection( - dexieCollectionOptions({ - id: "todos", - getKey: (t) => t.id, - schema: todoSchema, - // Example A — bulk background sync using an existing mutation - onUpdate: async ({ transaction }) => { - const updates = transaction.mutations.map((m) => ({ - id: m.key, - changes: m.changes, - })) - - await bulkUpdateMutation.mutateAsync({ updates }) - }, - - // Example B — per-item sync using individual mutations - onInsert: async ({ transaction }) => { - for (const m of transaction.mutations) { - await itemCreateMutation.mutateAsync(m.modified) - } - }, - - onDelete: async ({ transaction }) => { - for (const m of transaction.mutations) { - await itemDeleteMutation.mutateAsync({ id: m.key }) - } - }, - }) -) -``` - -Quick note: Use Zod (or another Standard Schema) for compile-time types and runtime validation. Use a `codec` when you need to transform stored shapes (ISO dates, legacy fields) without a typed schema. - #### Derived collections From d1299ff15be7263487996307f120ad0fc38946f1 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 22 Sep 2025 09:19:42 -0600 Subject: [PATCH 4/6] format --- docs/overview.md | 177 +++++++++++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 84 deletions(-) diff --git a/docs/overview.md b/docs/overview.md index 3256863de..ec51a16e4 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -69,8 +69,6 @@ Collections can be populated in many ways, including: Once you have your data in collections, you can query across them using live queries in your components. - - ### Using live queries Live queries are used to query data out of collections. Live queries are reactive: when the underlying data changes in a way that would affect the query result, the result is incrementally updated and returned from the query, triggering a re-render. @@ -159,7 +157,6 @@ There are a number of built-in collection types: 4. [`RxDBCollection`](#rxdbcollection) to integrate with [RxDB](https://rxdb.info) for local persistence and sync 5. [`LocalStorageCollection`](#localstoragecollection) for small amounts of local-only state that syncs across browser tabs 6. [`LocalOnlyCollection`](#localonlycollection) for in-memory client data or UI state -7. [`DexieCollection`](#dexiecollection) — _Unofficial_ community integration for Dexie.js (IndexedDB) You can also use: @@ -189,8 +186,8 @@ const todoCollection = createCollection( queryCollectionOptions({ queryKey: ["todoItems"], queryFn: async () => { - const response = await fetch("/api/todos"); - return response.json(); + const response = await fetch("/api/todos") + return response.json() }, getKey: (item) => item.id, schema: todoSchema, // any standard schema @@ -304,7 +301,6 @@ This collection requires the following TrailBase-specific options: A new collections doesn't start syncing until you call `collection.preload()` or you query it. - #### `RxDBCollection` [RxDB](https://rxdb.info) is a client-side database for JavaScript apps with replication, conflict resolution, and offline-first features. @@ -338,7 +334,7 @@ await db.addCollections({ export const todoCollection = createCollection( rxdbCollectionOptions({ rxCollection: db.todos, - startSync: true + startSync: true, }) ) ``` @@ -448,7 +444,6 @@ export const tempDataCollection = createCollection( > [!TIP] > LocalOnly collections are perfect for temporary UI state, form data, or any client-side data that doesn't need persistence. For data that should persist across sessions, use [`LocalStorageCollection`](#localstoragecollection) instead. - #### Derived collections Live queries return collections. This allows you to derive collections from other collections. @@ -618,11 +613,11 @@ const addTodo = createOptimisticAction({ body: JSON.stringify({ text, completed: false }), }) const result = await response.json() - + // IMPORTANT: Ensure server writes have synced back before returning // This ensures the optimistic state can be safely discarded await todoCollection.utils.refetch() - + return result }, }) @@ -675,47 +670,54 @@ When multiple mutations operate on the same item within a transaction, TanStack The merging behavior follows a truth table based on the mutation types: -| Existing → New | Result | Description | -|---|---|---| -| **insert + update** | `insert` | Keeps insert type, merges changes, empty original | -| **insert + delete** | *removed* | Mutations cancel each other out | -| **update + delete** | `delete` | Delete dominates | -| **update + update** | `update` | Union changes, keep first original | -| **same type** | *latest* | Replace with most recent mutation | +| Existing → New | Result | Description | +| ------------------- | --------- | ------------------------------------------------- | +| **insert + update** | `insert` | Keeps insert type, merges changes, empty original | +| **insert + delete** | _removed_ | Mutations cancel each other out | +| **update + delete** | `delete` | Delete dominates | +| **update + update** | `update` | Union changes, keep first original | +| **same type** | _latest_ | Replace with most recent mutation | #### Examples **Insert followed by update:** + ```ts const tx = createTransaction({ autoCommit: false, mutationFn }) // Insert a new todo -tx.mutate(() => todoCollection.insert({ - id: '1', - text: 'Buy groceries', - completed: false -})) +tx.mutate(() => + todoCollection.insert({ + id: "1", + text: "Buy groceries", + completed: false, + }) +) // Update the same todo -tx.mutate(() => todoCollection.update('1', (draft) => { - draft.text = 'Buy organic groceries' - draft.priority = 'high' -})) +tx.mutate(() => + todoCollection.update("1", (draft) => { + draft.text = "Buy organic groceries" + draft.priority = "high" + }) +) // Result: Single insert mutation with merged data // { id: '1', text: 'Buy organic groceries', completed: false, priority: 'high' } ``` **Insert followed by delete:** + ```ts // Insert then delete cancels out - no mutations sent to server -tx.mutate(() => todoCollection.insert({ id: '1', text: 'Temp todo' })) -tx.mutate(() => todoCollection.delete('1')) +tx.mutate(() => todoCollection.insert({ id: "1", text: "Temp todo" })) +tx.mutate(() => todoCollection.delete("1")) // Result: No mutations (they cancel each other out) ``` This intelligent merging ensures that: + - **Network efficiency**: Fewer mutations sent to the server - **User intent preservation**: Final state matches what user expects - **Optimistic UI consistency**: Local state always reflects user actions @@ -903,32 +905,36 @@ import { queryCollectionOptions } from "@tanstack/query-db-collection" // Load data into collections using TanStack Query. // It's common to define these in a `collections` module. -const todoCollection = createCollection(queryCollectionOptions({ - queryKey: ["todos"], - queryFn: async () => fetch("/api/todos"), - getKey: (item) => item.id, - schema: todoSchema, // any standard schema - onInsert: async ({ transaction }) => { - const { changes: newTodo } = transaction.mutations[0] - - // Handle the local write by sending it to your API. - await api.todos.create(newTodo) - } - // also add onUpdate, onDelete as needed. -})) -const listCollection = createCollection(queryCollectionOptions({ - queryKey: ["todo-lists"], - queryFn: async () => fetch("/api/todo-lists"), - getKey: (item) => item.id, - schema: todoListSchema, - onInsert: async ({ transaction }) => { - const { changes: newTodo } = transaction.mutations[0] - - // Handle the local write by sending it to your API. - await api.todoLists.create(newTodo) - } - // also add onUpdate, onDelete as needed. -})) +const todoCollection = createCollection( + queryCollectionOptions({ + queryKey: ["todos"], + queryFn: async () => fetch("/api/todos"), + getKey: (item) => item.id, + schema: todoSchema, // any standard schema + onInsert: async ({ transaction }) => { + const { changes: newTodo } = transaction.mutations[0] + + // Handle the local write by sending it to your API. + await api.todos.create(newTodo) + }, + // also add onUpdate, onDelete as needed. + }) +) +const listCollection = createCollection( + queryCollectionOptions({ + queryKey: ["todo-lists"], + queryFn: async () => fetch("/api/todo-lists"), + getKey: (item) => item.id, + schema: todoListSchema, + onInsert: async ({ transaction }) => { + const { changes: newTodo } = transaction.mutations[0] + + // Handle the local write by sending it to your API. + await api.todoLists.create(newTodo) + }, + // also add onUpdate, onDelete as needed. + }) +) const Todos = () => { // Read the data using live queries. Here we show a live @@ -939,19 +945,18 @@ const Todos = () => { .join( { list: listCollection }, ({ todo, list }) => eq(list.id, todo.list_id), - 'inner' + "inner" ) .where(({ list }) => eq(list.active, true)) .select(({ todo, list }) => ({ id: todo.id, text: todo.text, status: todo.status, - listName: list.name + listName: list.name, })) ) // ... - } ``` @@ -964,37 +969,41 @@ One of the most powerful ways of using TanStack DB is with a sync engine, for a Here, we illustrate this pattern using [ElectricSQL](https://electric-sql.com) as the sync engine. ```tsx -import type { Collection } from '@tanstack/db' -import type { MutationFn, PendingMutation, createCollection } from '@tanstack/react-db' -import { electricCollectionOptions } from '@tanstack/electric-db-collection' - -export const todoCollection = createCollection(electricCollectionOptions({ - id: 'todos', - schema: todoSchema, - // Electric syncs data using "shapes". These are filtered views - // on database tables that Electric keeps in sync for you. - shapeOptions: { - url: 'https://api.electric-sql.cloud/v1/shape', - params: { - table: 'todos' - } - }, - getKey: (item) => item.id, - schema: todoSchema, - onInsert: async ({ transaction }) => { - const response = await api.todos.create(transaction.mutations[0].modified) +import type { Collection } from "@tanstack/db" +import type { + MutationFn, + PendingMutation, + createCollection, +} from "@tanstack/react-db" +import { electricCollectionOptions } from "@tanstack/electric-db-collection" - return { txid: response.txid} - } - // You can also implement onUpdate, onDelete as needed. -})) +export const todoCollection = createCollection( + electricCollectionOptions({ + id: "todos", + schema: todoSchema, + // Electric syncs data using "shapes". These are filtered views + // on database tables that Electric keeps in sync for you. + shapeOptions: { + url: "https://api.electric-sql.cloud/v1/shape", + params: { + table: "todos", + }, + }, + getKey: (item) => item.id, + schema: todoSchema, + onInsert: async ({ transaction }) => { + const response = await api.todos.create(transaction.mutations[0].modified) + + return { txid: response.txid } + }, + // You can also implement onUpdate, onDelete as needed. + }) +) const AddTodo = () => { return (