-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathdomain_map.yaml
More file actions
707 lines (643 loc) · 38.3 KB
/
domain_map.yaml
File metadata and controls
707 lines (643 loc) · 38.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
# domain_map.yaml
# Generated by skill-domain-discovery
# Library: @tanstack/db
# Version: 0.5.30
# Date: 2026-03-04
# Status: reviewed
library:
name: '@tanstack/db'
version: '0.5.30'
repository: 'https://github.com/TanStack/db'
description: 'Reactive client store with normalized collections, sub-millisecond live queries, and instant optimistic mutations'
primary_framework: 'framework-agnostic (React, Vue, Svelte, Solid, Angular adapters)'
domains:
- name: 'Collection Setup & Schema'
slug: 'collection-setup'
description: 'Creating and configuring typed collections from any data source, with optional schema validation, type transformations, and adapter-specific sync patterns'
- name: 'Live Query Construction'
slug: 'live-queries'
description: 'Building SQL-like reactive queries across collections using the fluent query builder API with expressions, joins, aggregations, and derived collections'
- name: 'Framework Integration'
slug: 'framework-integration'
description: 'Binding live queries to UI framework components using framework-specific hooks, dependency tracking, Suspense, and pagination'
- name: 'Mutations & Optimistic State'
slug: 'mutations-optimistic'
description: 'Writing data to collections with instant optimistic feedback, managing transaction lifecycles, and handling rollback on failure'
- name: 'Meta-Framework Integration'
slug: 'meta-framework'
description: 'Client-side preloading of collections in route loaders for TanStack Start/Router, Next.js, Remix, Nuxt, and SvelteKit — ensuring data is ready before component render'
- name: 'Custom Adapter Authoring'
slug: 'custom-adapter'
description: 'Building custom collection adapters that implement the SyncConfig contract for new backends'
- name: 'Offline Transactions'
slug: 'offline'
description: 'Offline-first transaction queueing with persistence, retry, multi-tab coordination, and connectivity detection'
skills:
- name: 'Collection Setup'
slug: 'collection-setup'
domain: 'collection-setup'
description: 'Creating and configuring typed collections backed by any data source'
type: 'core'
covers:
- 'createCollection'
- 'queryCollectionOptions (@tanstack/query-db-collection)'
- 'electricCollectionOptions (@tanstack/electric-db-collection)'
- 'trailbaseCollectionOptions (@tanstack/trailbase-db-collection)'
- 'powerSyncCollectionOptions (@tanstack/powersync-db-collection)'
- 'rxdbCollectionOptions (@tanstack/rxdb-db-collection)'
- 'localOnlyCollectionOptions'
- 'localStorageCollectionOptions'
- 'CollectionConfig interface (id, getKey, schema, sync, compare, autoIndex, startSync, gcTime, utils)'
- 'StandardSchema integration (Zod, Valibot, ArkType, Effect)'
- 'Schema validation (TInput vs TOutput, transformations, defaults)'
- 'Collection lifecycle (idle -> loading -> initialCommit -> ready -> error -> cleaned-up)'
- 'Collection status tracking (isReady, isLoading, isError, isCleanedUp)'
- 'localOnly -> real backend upgrade path'
- 'Adapter-specific sync patterns (Electric txid, Query direct writes, PowerSync persistence)'
- 'Sync modes (eager, on-demand, progressive)'
tasks:
- 'Create a collection backed by TanStack Query for REST API data'
- 'Create a collection synced with ElectricSQL for real-time Postgres data'
- 'Create a local-only collection for temporary UI state'
- 'Add schema validation with type transformations (e.g. string -> Date)'
- 'Configure collection with custom getKey, gcTime, and autoIndex'
- 'Create a localStorage collection for cross-tab persistent state'
- 'Configure PowerSync collection with custom serializer'
- 'Prototype with localOnlyCollectionOptions then swap to a real backend'
- 'Configure on-demand sync mode for large datasets'
- 'Track Electric txid to prevent optimistic state flash'
- 'Use direct writes to update query collection without refetch'
subsystems:
- name: 'TanStack Query adapter'
package: '@tanstack/query-db-collection'
config_surface: 'queryKey, queryFn, queryClient, select, enabled, refetchInterval, staleTime, syncMode'
- name: 'ElectricSQL adapter'
package: '@tanstack/electric-db-collection'
config_surface: 'shapeOptions, syncMode (eager/on-demand/progressive), txid tracking via awaitTxId/awaitMatch'
- name: 'PowerSync adapter'
package: '@tanstack/powersync-db-collection'
config_surface: 'database, table, conversions, batchSize, syncMode'
- name: 'RxDB adapter'
package: '@tanstack/rxdb-db-collection'
config_surface: 'rxCollection, syncBatchSize; keys always strings'
- name: 'TrailBase adapter'
package: '@tanstack/trailbase-db-collection'
config_surface: 'recordApi, conversions'
- name: 'Local-only'
package: '@tanstack/db'
config_surface: 'getKey, schema, initialData'
- name: 'localStorage'
package: '@tanstack/db'
config_surface: 'storageKey, getKey, schema'
failure_modes:
- mistake: 'queryFn returning empty array deletes all collection data'
mechanism: "queryCollectionOptions treats queryFn result as complete server state; returning [] means 'server has no items', causing all existing items to be deleted from the collection"
wrong_pattern: |
queryFn: async () => {
const res = await fetch('/api/todos?status=active')
return res.json() // returns [] when no active todos
}
correct_pattern: |
queryFn: async () => {
const res = await fetch('/api/todos') // fetch ALL todos
return res.json()
}
// Or use on-demand sync mode for filtered queries
source: 'docs/collections/query-collection.md - Full State Sync section'
priority: 'CRITICAL'
skills: ['collection-setup']
- mistake: 'Not knowing which collection type to use for a given backend'
mechanism: 'AI agents default to bare createCollection or localOnlyCollectionOptions when they should use queryCollectionOptions, electricCollectionOptions, etc.; each adapter handles sync, handlers, and utilities differently'
source: 'maintainer interview'
priority: 'CRITICAL'
skills: ['collection-setup']
- mistake: 'Using async schema validation'
mechanism: 'Schema validation must be synchronous; returning a Promise from a schema throws SchemaMustBeSynchronousError, but the error only surfaces at mutation time, not at collection creation'
source: 'packages/db/src/collection/mutations.ts:101'
priority: 'HIGH'
skills: ['collection-setup']
- mistake: 'getKey returning undefined for some items'
mechanism: "If getKey returns undefined for any item, throws UndefinedKeyError; common when accessing a nested property that doesn't exist on all items"
source: 'packages/db/src/collection/mutations.ts:148'
priority: 'HIGH'
skills: ['collection-setup']
- mistake: 'TInput not a superset of TOutput with schema transforms'
mechanism: 'When schema transforms types (e.g. string -> Date), the input type for mutations must accept the pre-transform type; mismatches cause type errors that are confusing because they reference internal schema types'
source: 'docs/guides/schemas.md - TInput must be superset of TOutput'
priority: 'HIGH'
skills: ['collection-setup']
- mistake: 'Providing both explicit type parameter and schema'
mechanism: 'When a schema is provided, the collection infers types from it; also passing an explicit generic type parameter creates conflicting type constraints'
source: 'docs/overview.md - schema type inference note'
priority: 'MEDIUM'
skills: ['collection-setup']
- mistake: 'React Native missing crypto.randomUUID polyfill'
mechanism: "TanStack DB uses crypto.randomUUID() internally for IDs; React Native doesn't provide this, causing runtime crash; must install react-native-random-uuid"
source: 'docs/overview.md - React Native section'
priority: 'HIGH'
skills: ['collection-setup']
- mistake: 'Electric txid queried outside mutation transaction'
mechanism: "If pg_current_xact_id() is queried in a separate transaction from the actual mutation, the txid won't match the mutation's transaction, causing awaitTxId to stall forever; must query txid INSIDE the same SQL transaction as the mutation"
source: 'docs/collections/electric-collection.md - Debugging txid section'
priority: 'CRITICAL'
skills: ['collection-setup']
- mistake: 'queryFn returning partial data without merging'
mechanism: 'queryFn result is treated as complete state; returning only new/changed items without merging with existing data causes all non-returned items to be deleted from the collection'
source: 'docs/collections/query-collection.md - Handling Partial/Incremental Fetches'
priority: 'CRITICAL'
skills: ['collection-setup']
- mistake: 'Direct writes overridden by next query sync'
mechanism: 'Direct writes (writeInsert, etc.) update the collection immediately, but the next queryFn execution returns the complete server state which overwrites the direct writes; must coordinate staleTime and refetch behavior'
source: 'docs/collections/query-collection.md - Direct Writes and Query Sync'
priority: 'MEDIUM'
skills: ['collection-setup']
- name: 'Live Queries'
slug: 'live-queries'
domain: 'live-queries'
description: 'Building SQL-like reactive queries across collections'
type: 'core'
covers:
- 'Query builder fluent API (.from, .where, .join, .select, .groupBy, .having, .orderBy, .limit, .offset, .distinct, .findOne)'
- 'Comparison operators (eq, ne, gt, gte, lt, lte, like, ilike, inArray, isNull, isUndefined)'
- 'Logical operators (and, or, not)'
- 'Aggregate functions (count, sum, avg, min, max)'
- 'String functions (upper, lower, length, concat, coalesce)'
- 'Math functions (add, subtract, multiply, divide)'
- 'Join types (inner, left, right, full)'
- 'Derived collections (query results are themselves collections)'
- 'createLiveQueryCollection (standalone queries outside components)'
- 'QueryIR (intermediate representation)'
- 'compileQuery (query compilation)'
- '$selected namespace for accessing SELECT fields in ORDER BY / HAVING'
- 'Predicate push-down (loadSubsetOptions for on-demand sync)'
- 'Incremental view maintenance via d2ts (differential dataflow)'
tasks:
- 'Filter collection items with complex WHERE conditions'
- 'Join two collections on a foreign key'
- 'Aggregate data with GROUP BY and HAVING'
- 'Sort and paginate query results'
- 'Create a derived collection from a query for reuse across components'
- 'Build a query with computed/projected fields'
- 'Use subqueries for complex data access patterns'
- 'Move JS array filtering/transformation logic into live queries for better performance'
reference_candidates:
- topic: 'Query operators'
reason: '>20 distinct operators (comparison, logical, aggregate, string, math) with signatures'
failure_modes:
- mistake: 'Using === instead of eq() in where clauses'
mechanism: 'JavaScript === in a where callback returns a boolean, not an expression object; the query silently evaluates to always-false or always-true instead of building the correct filter predicate. Throws InvalidWhereExpressionError.'
wrong_pattern: |
q.from({ users }).where(({ users }) => users.active === true)
correct_pattern: |
q.from({ users }).where(({ users }) => eq(users.active, true))
source: 'packages/db/src/query/builder/index.ts:375'
priority: 'CRITICAL'
skills: ['live-queries']
- mistake: 'Filtering/transforming data in JS instead of using live query operators'
mechanism: "AI agents write .filter()/.map()/.reduce() on the data array instead of using the query builder's where/select/groupBy; this throws away incremental maintenance -- the JS code re-runs from scratch on every change, while the query only recomputes the delta"
wrong_pattern: |
const { data } = useLiveQuery(q => q.from({ todos }))
const active = data.filter(t => t.completed === false)
correct_pattern: |
const { data } = useLiveQuery(q =>
q.from({ todos }).where(({ todos }) => eq(todos.completed, false))
)
source: 'maintainer interview'
priority: 'CRITICAL'
skills: ['live-queries']
- mistake: 'Not using the full set of available query operators'
mechanism: 'The library has a comprehensive operator set (string functions, math, aggregates, coalesce, etc.) but agents default to basic eq/gt/lt and do the rest in JS; every operator is incrementally maintained and should be preferred over JS equivalents'
source: 'maintainer interview'
priority: 'HIGH'
skills: ['live-queries']
- mistake: 'Using .distinct() without .select()'
mechanism: 'distinct() deduplicates by the entire selected object shape; without select(), the shape is undefined, throwing DistinctRequiresSelectError'
source: 'packages/db/src/query/compiler/index.ts:218'
priority: 'HIGH'
skills: ['live-queries']
- mistake: 'Using .having() without .groupBy()'
mechanism: 'HAVING filters aggregated groups; without GROUP BY there are no groups to filter, throwing HavingRequiresGroupByError'
source: 'packages/db/src/query/compiler/index.ts:293'
priority: 'HIGH'
skills: ['live-queries']
- mistake: 'Using .limit() or .offset() without .orderBy()'
mechanism: 'Without deterministic ordering, limit/offset results are non-deterministic and cannot be incrementally maintained; throws LimitOffsetRequireOrderByError'
source: 'packages/db/src/query/compiler/index.ts:356'
priority: 'HIGH'
skills: ['live-queries']
- mistake: 'Join condition using operator other than eq()'
mechanism: 'The D2 differential dataflow join operator only supports equality joins; using gt(), like(), etc. throws JoinConditionMustBeEqualityError'
source: 'packages/db/src/query/builder/index.ts:216'
priority: 'HIGH'
skills: ['live-queries']
- mistake: 'Passing source directly instead of as {alias: collection}'
mechanism: 'from() and join() require sources wrapped as {alias: collection}; passing the collection directly throws InvalidSourceTypeError'
wrong_pattern: |
q.from(usersCollection)
correct_pattern: |
q.from({ users: usersCollection })
source: 'packages/db/src/query/builder/index.ts:79-96'
priority: 'MEDIUM'
skills: ['live-queries']
- name: 'Framework Integration'
slug: 'framework-integration'
domain: 'framework-integration'
description: 'Binding live queries to UI framework components'
type: 'framework'
covers:
- 'React: useLiveQuery, useLiveSuspenseQuery, useLiveInfiniteQuery, usePacedMutations'
- 'Vue: useLiveQuery composable with computed refs'
- 'Svelte: useLiveQuery with Svelte 5 runes'
- 'Solid: useLiveQuery with fine-grained reactivity'
- 'Angular: injectLiveQuery with signals'
- 'Dependency arrays for reactive query parameters'
- 'React Suspense integration with Error Boundaries'
- 'Infinite query pagination (cursor-based)'
- 'Return shape: { data, state, collection, status, isLoading, isReady, isError }'
tasks:
- 'Bind a live query to a React component with useLiveQuery'
- 'Use React Suspense for loading states with useLiveSuspenseQuery'
- 'Implement infinite scroll with useLiveInfiniteQuery'
- 'Pass reactive parameters to queries in Vue (refs) / Angular (signals) / Solid (signals)'
- 'Set up dependency arrays for dynamic query parameters'
subsystems:
- name: 'React'
package: '@tanstack/react-db'
config_surface: 'useLiveQuery, useLiveSuspenseQuery, useLiveInfiniteQuery, usePacedMutations'
- name: 'Vue'
package: '@tanstack/vue-db'
config_surface: 'useLiveQuery composable with MaybeRefOrGetter'
- name: 'Svelte'
package: '@tanstack/svelte-db'
config_surface: 'useLiveQuery with Svelte 5 runes ($state)'
- name: 'Solid'
package: '@tanstack/solid-db'
config_surface: 'useLiveQuery with Accessor/createSignal'
- name: 'Angular'
package: '@tanstack/angular-db'
config_surface: 'injectLiveQuery with Signal, inject(DestroyRef)'
failure_modes:
- mistake: 'Missing external values in useLiveQuery dependency array'
mechanism: "When query uses external state (props, local state) not included in deps array, the query won't re-run when those values change, showing stale results"
wrong_pattern: |
const { data } = useLiveQuery(q =>
q.from({ todos }).where(({ todos }) => eq(todos.userId, userId))
) // userId not in deps
correct_pattern: |
const { data } = useLiveQuery(q =>
q.from({ todos }).where(({ todos }) => eq(todos.userId, userId)),
[userId]
)
source: 'docs/framework/react/overview.md - dependency array section'
priority: 'CRITICAL'
skills: ['framework-integration']
- mistake: 'Reading Solid signals outside the query function'
mechanism: "Solid's reactivity tracks signal reads inside the query function; reading signals before passing to useLiveQuery means changes aren't tracked and query won't re-run"
source: 'docs/framework/solid/overview.md - fine-grained reactivity section'
priority: 'HIGH'
skills: ['framework-integration']
- mistake: 'Using useLiveSuspenseQuery without Error Boundary'
mechanism: 'Suspense query throws errors during rendering; without an Error Boundary wrapping the component, the entire app crashes instead of showing a fallback'
source: 'docs/guides/live-queries.md - React Suspense section'
priority: 'HIGH'
skills: ['framework-integration']
- mistake: 'Passing non-function deps in Svelte instead of getter functions'
mechanism: 'In Svelte 5, props and derived values should be wrapped in getter functions in the dependency array to maintain reactivity; passing values directly captures them at creation time'
source: 'docs/framework/svelte/overview.md - Props in dependencies'
priority: 'MEDIUM'
skills: ['framework-integration']
compositions:
- library: 'meta-framework'
skill: 'meta-framework'
- name: 'Mutations & Optimistic State'
slug: 'mutations-optimistic'
domain: 'mutations-optimistic'
description: 'Writing data to collections with instant optimistic feedback'
type: 'core'
covers:
- 'collection.insert(), collection.update(), collection.delete()'
- 'createOptimisticAction (custom mutation actions)'
- 'createPacedMutations (debounced/throttled mutations)'
- 'createTransaction (manual transaction control)'
- 'getActiveTransaction (ambient transaction context)'
- 'Transaction lifecycle (pending -> persisting -> completed | failed)'
- 'Transaction stacking (concurrent transactions build on each other)'
- 'Mutation merging (insert+update -> insert, insert+delete -> null, etc.)'
- 'onInsert, onUpdate, onDelete handlers'
- 'Optimistic vs non-optimistic updates (optimistic: false)'
- 'Automatic rollback on handler error'
- 'Change tracking proxy (draft updates via Immer-like API)'
- 'PendingMutation type (original, modified, changes, globalKey)'
- 'Transaction.isPersisted promise'
- 'Temporary ID handling'
- 'TanStack Pacer integration for sequential execution'
tasks:
- 'Insert a new item with optimistic UI update'
- 'Update an item using the draft proxy pattern'
- 'Delete items with automatic rollback on server error'
- 'Create a custom optimistic action for complex mutations'
- 'Use paced mutations for real-time text editing'
- 'Batch multiple mutations into a single transaction'
- 'Handle temporary IDs that get replaced by server-generated IDs'
- 'Use pacer for sequential transaction execution to avoid conflicts'
failure_modes:
- mistake: 'Passing a new object to update() instead of mutating the draft'
mechanism: "collection.update(id, {...item, title: 'new'}) is wrong; the API uses an Immer-style draft proxy: collection.update(id, (draft) => { draft.title = 'new' }). Passing an object instead of a callback silently fails or throws a confusing error."
wrong_pattern: |
collection.update(id, { ...item, title: 'new' })
correct_pattern: |
collection.update(id, (draft) => { draft.title = 'new' })
source: 'maintainer interview'
priority: 'CRITICAL'
skills: ['mutations-optimistic']
- mistake: 'Hallucinating mutation API signatures'
mechanism: 'AI agents generate plausible but wrong mutation code -- inventing handler signatures, confusing createOptimisticAction with createTransaction, missing the ambient transaction pattern, or wrong PendingMutation property names (e.g. transaction.mutations[0].changes vs .data)'
source: 'maintainer interview'
priority: 'CRITICAL'
skills: ['mutations-optimistic']
- mistake: 'onMutate callback returning a Promise'
mechanism: 'onMutate in createOptimisticAction must be synchronous because optimistic state needs to be applied immediately in the current tick; returning a Promise throws OnMutateMustBeSynchronousError'
wrong_pattern: |
createOptimisticAction({
onMutate: async (text) => { collection.insert({ id: await generateId(), text }) },
mutationFn: async (text, { transaction }) => { ... }
})
correct_pattern: |
createOptimisticAction({
onMutate: (text) => { collection.insert({ id: crypto.randomUUID(), text }) },
mutationFn: async (text, { transaction }) => { ... }
})
source: 'packages/db/src/optimistic-action.ts:75'
priority: 'CRITICAL'
skills: ['mutations-optimistic']
- mistake: 'Calling insert/update/delete without handler or ambient transaction'
mechanism: 'Collection mutations require either an onInsert/onUpdate/onDelete handler or an ambient transaction from createTransaction; without either, throws MissingInsertHandlerError (or Update/Delete variant)'
source: 'packages/db/src/collection/mutations.ts:166'
priority: 'CRITICAL'
skills: ['mutations-optimistic']
- mistake: 'Calling .mutate() after transaction is no longer pending'
mechanism: "Transactions can only accept new mutations while in 'pending' state; calling mutate() after commit() or rollback() throws TransactionNotPendingMutateError"
source: 'packages/db/src/transactions.ts:289'
priority: 'HIGH'
skills: ['mutations-optimistic']
- mistake: "Attempting to change an item's primary key via update"
mechanism: 'The update proxy detects key changes and throws KeyUpdateNotAllowedError; primary keys are immutable once set'
source: 'packages/db/src/collection/mutations.ts:352'
priority: 'HIGH'
skills: ['mutations-optimistic']
- mistake: 'Inserting item with duplicate key'
mechanism: 'If an item with the same key already exists in the collection (synced or optimistic), throws DuplicateKeyError; common when using client-generated IDs without checking'
source: 'packages/db/src/collection/mutations.ts:181'
priority: 'HIGH'
skills: ['mutations-optimistic']
- mistake: 'Not awaiting refetch after mutation in query collection handler'
mechanism: "In query collection onInsert/onUpdate/onDelete handlers, the optimistic state is only held until the handler resolves; if you don't await the refetch or sync back, the optimistic state is dropped before new server state arrives, causing a flash of missing data"
wrong_pattern: |
onInsert: async ({ transaction }) => {
await api.createTodo(transaction.mutations[0].modified)
// missing: await collection.utils.refetch()
}
correct_pattern: |
onInsert: async ({ transaction }) => {
await api.createTodo(transaction.mutations[0].modified)
await collection.utils.refetch()
}
source: 'docs/overview.md - optimistic state lifecycle'
priority: 'HIGH'
skills: ['mutations-optimistic']
- name: 'Meta-Framework Integration'
slug: 'meta-framework'
domain: 'meta-framework'
description: 'Client-side preloading of collections in route loaders for meta-frameworks'
type: 'composition'
covers:
- 'collection.preload() in route loaders'
- 'collection.stateWhenReady() and toArrayWhenReady()'
- 'collection.onFirstReady(callback)'
- 'Pre-creating createLiveQueryCollection in loaders'
- 'Setting ssr: false on routes using collections'
- 'TanStack Start / TanStack Router loader patterns'
- 'Coordinating collection lifecycle with route transitions'
- 'Passing pre-loaded collections to components via loader data'
tasks:
- 'Preload a collection in a TanStack Router route loader'
- 'Pre-create a live query collection in a loader and pass to component'
- 'Configure ssr: false on routes that use TanStack DB collections'
- 'Coordinate multiple collection preloads in a single route loader'
- 'Handle route transitions when collections are still loading'
failure_modes:
- mistake: 'Not preloading collections in route loaders'
mechanism: 'Without preload() in the loader, the collection starts syncing only when the component mounts; this causes a loading flash even though the router could have started the sync during navigation'
wrong_pattern: |
export const Route = createFileRoute('/todos')({
component: TodoList,
// no loader -- collection loads on mount
})
correct_pattern: |
export const Route = createFileRoute('/todos')({
component: TodoList,
ssr: false,
loader: async () => {
await todosCollection.preload()
},
})
source: 'examples/react/projects/src/routes'
priority: 'HIGH'
skills: ['meta-framework']
- mistake: 'Not setting ssr: false on routes using collections'
mechanism: 'Collections are client-side only (no SSR support yet); rendering a route with collections on the server attempts to access browser-only APIs, causing crashes or hydration mismatches'
wrong_pattern: |
export const Route = createFileRoute('/todos')({
component: TodoList,
loader: async () => {
await todosCollection.preload()
},
})
correct_pattern: |
export const Route = createFileRoute('/todos')({
component: TodoList,
ssr: false,
loader: async () => {
await todosCollection.preload()
},
})
source: 'examples/react/projects/src/start.tsx - defaultSsr: false'
priority: 'CRITICAL'
skills: ['meta-framework']
- mistake: 'Creating new collection instances inside loaders on every navigation'
mechanism: 'createLiveQueryCollection should be called once and reused; creating new instances on each navigation leaks D2 graph nodes and subscriptions'
source: 'docs/guides/live-queries.md - standalone queries'
priority: 'HIGH'
skills: ['meta-framework']
compositions:
- library: '@tanstack/react-router'
skill: 'framework-integration'
- name: 'Custom Adapter Authoring'
slug: 'custom-adapter'
domain: 'custom-adapter'
description: 'Building custom collection adapters for new backends'
type: 'core'
covers:
- 'SyncConfig interface (sync, getSyncMetadata, rowUpdateMode)'
- 'Sync primitives (begin, write, commit, markReady, truncate)'
- 'ChangeMessage format (insert, update, delete)'
- 'loadSubset for on-demand sync mode'
- 'LoadSubsetOptions (where, orderBy, limit, cursor)'
- 'Expression parsing helpers (parseWhereExpression, parseOrderByExpression, extractSimpleComparisons)'
- 'Collection options creator pattern'
- 'Subscription lifecycle and cleanup'
tasks:
- 'Build a custom collection adapter for a new backend'
- 'Implement loadSubset for on-demand predicate push-down'
- 'Use expression parsing helpers to translate query predicates to API params'
- 'Handle the sync lifecycle correctly (begin/write/commit/markReady)'
failure_modes:
- mistake: 'Not calling markReady() in custom sync implementation'
mechanism: "markReady() transitions the collection from 'loading' to 'ready' status; forgetting to call it means live queries never resolve and useLiveSuspenseQuery hangs forever in Suspense"
wrong_pattern: |
sync: ({ begin, write, commit }) => {
fetchData().then(items => {
begin()
items.forEach(item => write({ type: 'insert', value: item }))
commit()
// forgot markReady()!
})
}
correct_pattern: |
sync: ({ begin, write, commit, markReady }) => {
fetchData().then(items => {
begin()
items.forEach(item => write({ type: 'insert', value: item }))
commit()
markReady()
})
}
source: 'docs/guides/collection-options-creator.md - markReady section'
priority: 'CRITICAL'
skills: ['custom-adapter']
- mistake: 'Race condition between initial sync and event subscription'
mechanism: "If live change events aren't subscribed BEFORE the initial data fetch, changes that occur during the fetch are lost; the sync implementation must start listening before fetching"
wrong_pattern: |
sync: ({ begin, write, commit, markReady }) => {
// BAD: fetch first, then subscribe
const data = await fetchAll()
writeAll(data)
subscribe(onChange) // missed changes during fetch!
}
correct_pattern: |
sync: ({ begin, write, commit, markReady }) => {
// GOOD: subscribe first, then fetch
subscribe(onChange)
const data = await fetchAll()
writeAll(data)
}
source: 'docs/guides/collection-options-creator.md - Race condition prevention'
priority: 'HIGH'
skills: ['custom-adapter']
- mistake: 'write() called without begin() in sync implementation'
mechanism: 'Sync data must be written within a transaction (begin -> write -> commit); calling write() without begin() throws NoPendingSyncTransactionWriteError'
source: 'packages/db/src/collection/sync.ts:110'
priority: 'HIGH'
skills: ['custom-adapter']
- name: 'Offline Transactions'
slug: 'offline'
domain: 'offline'
description: 'Offline-first transaction queueing with persistence and retry'
type: 'composition'
covers:
- 'OfflineExecutor / startOfflineExecutor'
- 'OfflineConfig (collections, mutationFns, storage, maxConcurrency)'
- 'Storage adapters (IndexedDBAdapter, LocalStorageAdapter)'
- 'Retry policies (DefaultRetryPolicy, BackoffCalculator, NonRetriableError)'
- 'Leader election (WebLocksLeader, BroadcastChannelLeader)'
- 'Online detection (WebOnlineDetector)'
- 'OutboxManager (transaction queue)'
- 'KeyScheduler (prevents concurrent mutations on same key)'
- 'TransactionSerializer (persistence)'
- 'React Native support (@react-native-community/netinfo)'
tasks:
- 'Set up offline-first transactions with @tanstack/offline-transactions'
- 'Configure IndexedDB storage for transaction persistence'
- 'Handle multi-tab coordination with leader election'
- 'Implement custom retry logic with NonRetriableError'
- 'Set up offline support for React Native with NetInfo'
failure_modes:
- mistake: 'Using offline transactions when not needed'
mechanism: 'Offline is inherently complex; @tanstack/offline-transactions adds storage, leader election, and retry overhead. Only adopt when true offline support is required. PowerSync/RxDB handle their own local persistence, which is a different concern.'
source: 'maintainer interview'
priority: 'HIGH'
skills: ['offline']
- mistake: 'Not handling NonRetriableError for permanent failures'
mechanism: 'By default, failed transactions retry with exponential backoff; for permanent failures (e.g. 400 Bad Request), throw NonRetriableError to skip retry and move the transaction to a dead-letter state'
source: 'packages/offline-transactions/src/retry.ts'
priority: 'HIGH'
skills: ['offline']
- mistake: 'Multiple tabs executing the same queued transaction'
mechanism: 'Without leader election, each tab runs its own OfflineExecutor and processes the outbox independently, causing duplicate mutations; must configure WebLocksLeader or BroadcastChannelLeader'
source: 'packages/offline-transactions/src/leader/'
priority: 'CRITICAL'
skills: ['offline']
tensions:
- name: 'Simplicity vs. correctness in sync'
skills: ['collection-setup', 'custom-adapter']
description: 'Getting-started simplicity (localOnlyCollectionOptions, eager sync) conflicts with production correctness (on-demand sync, proper adapter selection, race condition prevention)'
implication: 'Agents use localOnly or eager mode for everything; production apps need adapter-specific patterns and on-demand sync for large datasets'
- name: 'Optimistic speed vs. data consistency'
skills: ['mutations-optimistic', 'collection-setup']
description: 'Instant optimistic updates create a window where client state diverges from server state; resolving conflicts on rollback can lose user work'
implication: 'Agents apply optimistic updates without considering rollback UX or awaiting refetch in mutation handlers'
- name: 'Query expressiveness vs. IVM constraints'
skills: ['live-queries', 'framework-integration']
description: "The query builder looks like SQL but has constraints (equality joins only, orderBy required for limit, no distinct without select) that SQL doesn't have"
implication: 'Agents write SQL-style queries that violate IVM constraints, producing confusing errors'
- name: 'Offline complexity vs. app simplicity'
skills: ['offline', 'mutations-optimistic']
description: "Offline transaction support adds storage, leader election, and retry complexity; most apps don't need it but agents may recommend it prematurely"
implication: 'Agents add @tanstack/offline-transactions to apps that only need basic optimistic mutations'
cross_references:
- from: 'framework-integration'
to: 'meta-framework'
reason: 'Framework hooks render data; meta-framework loaders preload it. Developers need both for production apps with routing.'
- from: 'meta-framework'
to: 'framework-integration'
reason: 'Preloaded collections are consumed by framework hooks; understanding the hook API informs what to preload.'
- from: 'collection-setup'
to: 'mutations-optimistic'
reason: 'Collection mutation handlers (onInsert/onUpdate/onDelete) are configured at setup time but execute during mutations; understanding both is required for working writes.'
- from: 'mutations-optimistic'
to: 'collection-setup'
reason: 'Mutation handler signatures and behavior depend on which adapter is used (e.g. Electric txid return, Query refetch).'
- from: 'live-queries'
to: 'collection-setup'
reason: 'Live queries reference collections by alias; understanding collection types and sync modes affects query behavior (e.g. on-demand predicate push-down).'
- from: 'custom-adapter'
to: 'collection-setup'
reason: 'Custom adapters produce the same CollectionConfig shape that built-in adapters use; understanding the config contract is essential.'
- from: 'offline'
to: 'mutations-optimistic'
reason: 'Offline transactions wrap the same transaction/mutation model; understanding createTransaction and PendingMutation is prerequisite.'
gaps:
- skill: 'meta-framework'
question: 'What are the specific patterns for non-TanStack-Start frameworks (Next.js App Router, Remix loaders, Nuxt middleware, SvelteKit load functions)?'
context: 'Only TanStack Start/Router patterns are documented in examples; other frameworks need guidance'
status: 'open'
- skill: 'collection-setup'
question: 'What is the recommended pattern for collection cleanup/disposal in single-page apps with route-based code splitting?'
context: "gcTime defaults to 5 minutes, but docs don't clearly explain when/how collections are garbage collected or what triggers cleanup"
status: 'open'
- skill: 'live-queries'
question: 'Are there performance cliffs with live queries? At what complexity/data size do queries degrade?'
context: "Docs claim sub-millisecond for 100k items, but don't discuss limits (e.g., 5-way joins, deeply nested aggregations)"
status: 'open'
- skill: 'mutations-optimistic'
question: 'What is the recommended pattern for handling temporary IDs that get replaced by server-generated IDs?'
context: "The mutations guide mentions temporary IDs but the pattern for mapping client IDs to server IDs during sync isn't well documented"
status: 'open'
- skill: 'meta-framework'
question: 'What are the specific patterns for TanStack Router integration with collection loading/prefetching?'
context: 'Maintainer reports this is a major composition pain point; agents struggle with the loading/prefetching pattern'
status: 'open'
- skill: 'offline'
question: 'What happens to in-flight transactions when the browser goes offline mid-persist?'
context: "The offline executor package handles queuing, but the interaction with the main transaction lifecycle isn't documented"
status: 'open'