Skip to main content

db-core-collection-setup

Creating typed collections with createCollection. Adapter selection: queryCollectionOptions (REST/TanStack Query), electricCollectionOptions (ElectricSQL real-time sync), powerSyncCollectionOptions (PowerSync SQLite), rxdbCollectionOptions (RxDB), trailBaseCollectionOptions (TrailBase), localOnlyCollectionOptions, localStorageCollectionOptions, indexedDBCollectionOptions. CollectionConfig 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, Query initial data and scoped factories, and PowerSync query-driven sync with onLoad/onLoadSubset hooks.

소스 정보

저장소
TanStack/db
최근 소스 활동
2026년 10월 8일 21:10
감지된 SKILL.md 언어
영어
스타
3,937
포크
272

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
8 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
db-core/collection-setup
description
Creating typed collections with createCollection. Adapter selection: queryCollectionOptions (REST/TanStack Query), electricCollectionOptions (ElectricSQL real-time sync), powerSyncCollectionOptions (PowerSync SQLite), rxdbCollectionOptions (RxDB), trailBaseCollectionOptions (TrailBase), localOnlyCollectionOptions, localStorageCollectionOptions, indexedDBCollectionOptions. CollectionConfig 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, Query initial data and scoped factories, and PowerSync query-driven sync with onLoad/onLoadSubset hooks.
type
sub-skill
library
db
library_version
0.6.17
sources
["TanStack/db:docs/overview.md","TanStack/db:docs/guides/schemas.md","TanStack/db:docs/collections/query-collection.md","TanStack/db:docs/collections/electric-collection.md","TanStack/db:docs/collections/powersync-collection.md","TanStack/db:docs/collections/rxdb-collection.md","TanStack/db:docs/collections/trailbase-collection.md","TanStack/db:docs/collections/indexed-db-collection.md","TanStack/db:packages/db/src/collection/index.ts"]
This skill builds on db-core. Read it first for the overall mental model. # Collection Setup & Schema ## Setup ```ts import { createCollection } from '@tanstack/react-db' import { queryCollectionOptions } from '@tanstack/query-db-collection' import { QueryClient } from '@tanstack/query-core' import { z } from 'zod' const queryClient = new QueryClient() const todoSchema = z.object({ id: z.number(), text: z.string(), completed: z.boolean().default(false), created_at: z .union([z.string(), z.date()]) .transform((val) => (typeof val === 'string' ? new Date(val) : val)), }) const todoCollection = createCollection( queryCollectionOptions({ queryKey: ['todos'], queryFn: async (ctx) => { const res = await fetch('/api/todos', { signal: ctx.signal }) return res.json() }, queryClient, getKey: (item) => item.id, schema: todoSchema, onInsert: async ({ transaction }) => { await api.todos.create(transaction.mutations[0].modified) }, onUpdate: async ({ transaction }) => { const mut = transaction.mutations[0] await api.todos.update(mut.key, mut.changes) }, onDelete: async ({ transaction }) => { await api.todos.delete(transaction.mutations[0].key) }, }), ) ``` ## Choosing an Adapter | Backend | Adapter | Package | | -------------------------------- | ------------------------------- | ----------------------------------- | | REST API / TanStack Query | `queryCollectionOptions` | `@tanstack/query-db-collection` | | ElectricSQL (real-time Postgres) | `electricCollectionOptions` | `@tanstack/electric-db-collection` | | PowerSync (SQLite offline) | `powerSyncCollectionOptions` | `@tanstack/powersync-db-collection` | | RxDB (reactive database) | `rxdbCollectionOptions` | `@tanstack/rxdb-db-collection` | | TrailBase (event streaming) | `trailBaseCollectionOptions` | `@tanstack/trailbase-db-collection` | | No backend (UI state) | `localOnlyCollectionOptions` | `@tanstack/db` | | Browser localStorage | `localStorageCollectionOptions` | `@tanstack/db` | | Browser IndexedDB | `indexedDBCollectionOptions` | `@tanstack/db` | If the user specifies a backend (e.g. Electric, PowerSync), use that adapter directly. Only use `localOnlyCollectionOptions` when there is no backend yet — the collection API is uniform, so swapping to a real adapter later only changes the options creator. ## Sync Modes ```ts queryCollectionOptions({ syncMode: 'eager', // default — loads all data upfront // syncMode: "on-demand", // loads only what live queries request // syncMode: "progressive", // (Electric only) query subset first, full sync in background }) ``` | 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 | Calling `collection.preload()` on an on-demand collection is a no-op. Create the live query for the required subset and call `liveQuery.preload()` instead. For Query Collection request cancellation, cleanup boundaries, and shared `QueryClient` behavior, read [the Query adapter reference](references/query-adapter.md#request-cancellation-and-cleanup). ## 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 ### Local-only collection for prototyping ```ts import { createCollection, localOnlyCollectionOptions, } from '@tanstack/react-db' const todoCollection = createCollection( localOnlyCollectionOptions({ getKey: (item) => item.id, initialData: [{ id: 1, text: 'Learn TanStack DB', completed: false }], }), ) ``` ### Schema with type transformations ```ts const schema = z.object({ id: z.number(), title: z.string(), due_date: z .union([z.string(), z.date()]) .transform((val) => (typeof val === 'string' ? new Date(val) : val)), priority: z.number().default(0), }) ``` Use `z.union([z.string(), z.date()])` for transformed fields — this ensures `TInput` is a superset of `TOutput` so that `update()` works correctly with the draft proxy. ### ElectricSQL with txid tracking Always use a schema with Electric — without one, the collection types as `Record<string, unknown>`. ```ts import { electricCollectionOptions } from '@tanstack/electric-db-collection' import { z } from 'zod' const todoSchema = z.object({ id: z.string(), text: z.string(), completed: z.boolean(), created_at: z.coerce.date(), }) const todoCollection = createCollection( electricCollectionOptions({ schema: todoSchema, shapeOptions: { url: '/api/electric/todos' }, getKey: (item) => item.id, onInsert: async ({ transaction }) => { const res = await api.todos.create(transaction.mutations[0].modified) return { txid: res.txid } }, }), ) ``` The returned `txid` tells the collection to hold optimistic state until Electric streams back that transaction. See the [Electric adapter reference](references/electric-adapter.md) for the full dual-path pattern (schema + parser). ## Common Mistakes ### CRITICAL queryFn returning empty array deletes all data Wrong: ```ts queryCollectionOptions({ queryFn: async () => { const res = await fetch('/api/todos?status=active') return res.json() // returns [] when no active todos — deletes everything }, }) ``` Correct: ```ts queryCollectionOptions({ queryFn: async () => { const res = await fetch('/api/todos') // fetch complete state return res.json() }, // Use on-demand mode + live query where() for filtering syncMode: 'on-demand', }) ``` In eager mode, `queryFn` is complete collection state. Returning `[]` means "the server has no items" and removes all rows. In on-demand mode, a result is complete only for that exact subset/Query key; an empty result releases that subset's ownership, while overlapping subsets can keep shared rows. Source: docs/collections/query-collection.md ### CRITICAL Not using the correct adapter for your backend Wrong: ```ts const todoCollection = createCollection( localOnlyCollectionOptions({ getKey: (item) => item.id, }), ) // Manually fetching and inserting... ``` Correct: ```ts const todoCollection = createCollection( queryCollectionOptions({ queryKey: ['todos'], queryFn: async () => fetch('/api/todos').then((r) => r.json()), queryClient, getKey: (item) => item.id, }), ) ``` Each backend has a dedicated adapter that handles sync, mutation handlers, and utilities. Using `localOnlyCollectionOptions` or bare `createCollection` for a real backend bypasses all of this. Source: docs/overview.md ### CRITICAL Electric txid queried outside mutation transaction Wrong: ```ts // Backend handler app.post('/api/todos', async (req, res) => { const txid = await generateTxId(sql) // WRONG: separate transaction await sql`INSERT INTO todos ${sql(req.body)}` res.json({ txid }) }) ``` Correct: ```ts app.post('/api/todos', async (req, res) => { let txid await sql.begin(async (tx) => { txid = await generateTxId(tx) // CORRECT: same transaction await tx`INSERT INTO todos ${tx(req.body)}` }) res.json({ txid }) }) ``` `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 ### CRITICAL queryFn returning partial data without merging Wrong: ```ts queryCollectionOptions({ queryFn: async () => { const newItems = await fetch('/api/todos?since=' + lastSync) return newItems.json() // only new items — everything else deleted }, }) ``` Correct: ```ts queryCollectionOptions({ queryFn: async (ctx) => { const existing = ctx.queryClient.getQueryData(['todos']) || [] const newItems = await fetch('/api/todos?since=' + lastSync).then((r) => r.json(), ) return [...existing, ...newItems] }, }) ``` An eager `queryFn` result replaces all collection data. For incremental eager fetches, merge with existing data. In on-demand mode, return the complete state for the requested subset instead. Source: docs/collections/query-collection.md ### HIGH Using async schema validation Wrong: ```ts const schema = z.object({ email: z.string().refine(async (val) => { const exists = await checkEmail(val) return !exists }), }) ``` Correct: ```ts const schema = z.object({ email: z.string().email(), }) // Do async validation in the mutation handler instead ``` Schema validation must be synchronous. Async validation throws `SchemaMustBeSynchronousError` at mutation time. Source: packages/db/src/collection/mutations.ts:101 ### HIGH getKey returning undefined for some items Wrong: ```ts createCollection( queryCollectionOptions({ getKey: (item) => item.metadata.id, // undefined if metadata missing }), ) ``` Correct: ```ts createCollection( queryCollectionOptions({ getKey: (item) => item.id, // always present }), ) ``` `getKey` must return a defined value for every item. Throws `UndefinedKeyError` otherwise. Source: packages/db/src/collection/mutations.ts:148 ### HIGH TInput not a superset of TOutput with schema transforms Wrong: ```ts const schema = z.object({ created_at: z.string().transform((val) => new Date(val)), }) // update() fails — draft.created_at is Date but schema only accepts string ``` Correct: ```ts const schema = z.object({ created_at: z .union([z.string(), z.date()]) .transform((val) => (typeof val === 'string' ? new Date(val) : val)), }) ``` When a schema transforms types, `TInput` must accept both the pre-transform and post-transform types for `update()` to work with the draft proxy. Source: docs/guides/schemas.md ### HIGH Runtime has no secure random number generator TanStack DB's `safeRandomUUID()` uses `crypto.randomUUID()` when available and falls back to `crypto.getRandomValues()`, including on non-secure HTTP origins. Add a Web Crypto polyfill only in runtimes, including some React Native versions, that provide neither API. ```ts
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기