diff --git a/docs/community/resources.md b/docs/community/resources.md new file mode 100644 index 000000000..40ce79526 --- /dev/null +++ b/docs/community/resources.md @@ -0,0 +1,42 @@ +# 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` + +### 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. + +## 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. 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/overview.md b/docs/overview.md index 0d2d6145a..ec51a16e4 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -186,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 @@ -301,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. @@ -335,7 +334,7 @@ await db.addCollections({ export const todoCollection = createCollection( rxdbCollectionOptions({ rxCollection: db.todos, - startSync: true + startSync: true, }) ) ``` @@ -614,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 }, }) @@ -671,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 @@ -899,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 @@ -935,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, })) ) // ... - } ``` @@ -960,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 (