Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
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
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.
Indexing
Indexing is opt-in. The autoIndex option defaults to "off". To enable automatic indexing, set autoIndex: "eager" and provide a defaultIndexType:
Without defaultIndexType, setting autoIndex: "eager" throws a CollectionConfigurationError. You can also create indexes manually with collection.createIndex() and remove them with collection.removeIndex().
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>.
The returned txid tells the collection to hold optimistic state until Electric streams back that transaction. See the Electric adapter reference for the full dual-path pattern (schema + parser).
Common Mistakes
CRITICAL queryFn returning empty array deletes all data
Wrong:
queryCollectionOptions({
queryFn: async () => {
const res = awaitfetch('/api/todos?status=active')
return res.json() // returns [] when no active todos — deletes everything
},
})
Correct:
queryCollectionOptions({
queryFn: async () => {
const res = awaitfetch('/api/todos') // fetch complete statereturn res.json()
},
// Use on-demand mode + live query where() for filteringsyncMode: '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
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:
// Backend handler
app.post('/api/todos', async (req, res) => {
const txid = awaitgenerateTxId(sql) // WRONG: separate transactionawait sql`INSERT INTO todos ${sql(req.body)}`
res.json({ txid })
})
Correct:
app.post('/api/todos', async (req, res) => {
let txid
await sql.begin(async (tx) => {
txid = awaitgenerateTxId(tx) // CORRECT: same transactionawait 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
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.
HIGH TInput not a superset of TOutput with schema transforms
Wrong:
const schema = z.object({
created_at: z.string().transform((val) =>newDate(val)),
})
// update() fails — draft.created_at is Date but schema only accepts string
Correct:
const schema = z.object({
created_at: z
.union([z.string(), z.date()])
.transform((val) => (typeof val === 'string' ? newDate(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.