pbtsdb 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@ A TypeScript library that seamlessly integrates [PocketBase](https://pocketbase.
8
8
  - 🎯 **Full TypeScript type safety** for queries and relations
9
9
  - ⚡ **Reactive collections** with TanStack DB
10
10
  - 🔄 **Automatic caching** via TanStack Query
11
+ - ✨ **Optimistic mutations** with insert/update/delete support
11
12
  - 🎨 **React hooks** for easy component integration
12
13
  - 🔗 **Type-safe joins** and relation expansion
13
14
 
@@ -17,21 +18,22 @@ A TypeScript library that seamlessly integrates [PocketBase](https://pocketbase.
17
18
  - [Quick Start](#quick-start)
18
19
  - [Core Concepts](#core-concepts)
19
20
  - [API Reference](#api-reference)
20
- - [CollectionFactory](#collectionfactory)
21
- - [React Provider](#react-provider)
21
+ - [createCollection()](#createcollection)
22
+ - [React Integration](#react-integration)
22
23
  - [Subscriptions](#subscriptions)
23
24
  - [Usage Examples](#usage-examples)
24
25
  - [Basic Queries](#basic-queries)
25
26
  - [Filtering and Sorting](#filtering-and-sorting)
26
27
  - [Relations and Joins](#relations-and-joins)
27
28
  - [Real-time Updates](#real-time-updates)
29
+ - [Mutations](#mutations)
28
30
  - [TypeScript](#typescript)
29
31
  - [Best Practices](#best-practices)
30
32
 
31
33
  ## Installation
32
34
 
33
35
  ```bash
34
- npm install pocketbase-tanstack-db pocketbase @tanstack/react-query @tanstack/react-db @tanstack/query-db-collection
36
+ npm install pbtsdb pocketbase @tanstack/react-query @tanstack/react-db @tanstack/query-db-collection
35
37
  ```
36
38
 
37
39
  ### Peer Dependencies
@@ -43,107 +45,187 @@ npm install pocketbase-tanstack-db pocketbase @tanstack/react-query @tanstack/re
43
45
  - `react` >= 18.0.0
44
46
  - `react-dom` >= 18.0.0
45
47
 
48
+ All peer dependencies use minimum version constraints - newer versions should work.
49
+
46
50
  ## Quick Start
47
51
 
52
+ Let's build a **real-world blog** with posts, authors, and comments using pbtsdb.
53
+
48
54
  ### 1. Define Your Schema
49
55
 
50
- Create type-safe schema definitions for your PocketBase collections. The schema should be formed like the below, it's recommended
51
- that you install https://github.com/satohshi/pocketbase-schema-generator as a pocketbase hook. When configured a schema file will be auto-generated on every schema change.
56
+ First, generate your types from PocketBase. Install [pocketbase-schema-generator](https://github.com/satohshi/pocketbase-schema-generator) as a PocketBase hook to auto-generate types on schema changes.
52
57
 
53
58
  ```typescript
54
- import type { SchemaDeclaration } from 'pocketbase-tanstack-db';
59
+ // schema.ts - Auto-generated from PocketBase
60
+ interface Post {
61
+ id: string;
62
+ title: string;
63
+ content: string;
64
+ author: string; // FK to users
65
+ published: boolean;
66
+ created: string;
67
+ updated: string;
68
+ }
55
69
 
56
- // Define your record types
57
- interface Author {
70
+ interface User {
58
71
  id: string;
59
- name: string;
72
+ username: string;
60
73
  email: string;
74
+ avatar?: string;
61
75
  created: string;
62
76
  updated: string;
63
77
  }
64
78
 
65
- interface Book {
79
+ interface Comment {
66
80
  id: string;
67
- title: string;
68
- author: string; // FK to authors
69
- genre: 'Fiction' | 'Non-Fiction' | 'Science Fiction';
70
- published_date: string;
81
+ post: string; // FK to posts
82
+ author: string; // FK to users
83
+ text: string;
71
84
  created: string;
72
85
  updated: string;
73
86
  }
74
87
 
75
- // Create schema declaration
76
- interface MySchema extends SchemaDeclaration {
77
- authors: {
78
- Row: Author;
79
- Relations: {
80
- forward: {};
81
- back: {
82
- books: ['books', true]; // One-to-many relation
83
- };
84
- };
88
+ // Schema declaration for pbtsdb
89
+ type BlogSchema = {
90
+ posts: {
91
+ type: Post;
92
+ relations: { author?: User };
93
+ };
94
+ users: {
95
+ type: User;
96
+ relations: {};
85
97
  };
86
- books: {
87
- Row: Book;
88
- Relations: {
89
- forward: {
90
- author: ['authors', false]; // Many-to-one relation
91
- };
92
- back: {};
98
+ comments: {
99
+ type: Comment;
100
+ relations: {
101
+ post?: Post;
102
+ author?: User;
93
103
  };
94
104
  };
95
105
  }
96
106
  ```
97
107
 
98
- ### 2. Initialize PocketBase and Collections
108
+ ### 2. Set Up Your App
99
109
 
100
110
  ```typescript
111
+ // app.tsx
101
112
  import PocketBase from 'pocketbase';
102
- import { QueryClient } from '@tanstack/react-query';
103
- import { CollectionFactory } from 'pocketbase-tanstack-db';
113
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
114
+ import { createCollection, createReactProvider } from 'pbtsdb';
104
115
 
105
- // Initialize PocketBase
106
116
  const pb = new PocketBase('http://localhost:8090');
107
-
108
- // Initialize QueryClient
109
117
  const queryClient = new QueryClient({
110
118
  defaultOptions: {
111
- queries: {
112
- staleTime: 1000 * 60, // 1 minute
113
- },
114
- },
119
+ queries: { staleTime: 60_000 } // Cache for 1 minute
120
+ }
115
121
  });
116
122
 
117
- // Create collection factory
118
- const factory = new CollectionFactory<MySchema>(pb, queryClient);
123
+ // Create collections with automatic type inference
124
+ const c = createCollection<BlogSchema>(pb, queryClient);
125
+ export const { Provider, useStore } = createReactProvider({
126
+ posts: c('posts', {
127
+ omitOnInsert: ['created', 'updated'] as const
128
+ }),
129
+ users: c('users', {}),
130
+ comments: c('comments', {
131
+ omitOnInsert: ['created', 'updated'] as const
132
+ })
133
+ });
119
134
 
120
- // Create collections
121
- const authorsCollection = factory.create('authors');
122
- const booksCollection = factory.create('books');
135
+ export function App() {
136
+ return (
137
+ <QueryClientProvider client={queryClient}>
138
+ <Provider>
139
+ <BlogDashboard />
140
+ </Provider>
141
+ </QueryClientProvider>
142
+ );
143
+ }
123
144
  ```
124
145
 
125
- ### 3. Use in React Components
146
+ ### 3. Build Your Components
126
147
 
127
148
  ```typescript
149
+ // BlogDashboard.tsx
128
150
  import { useLiveQuery } from '@tanstack/react-db';
151
+ import { useStore } from './app';
129
152
 
130
- function BooksList() {
131
- const { data: books, isLoading } = useLiveQuery((q) =>
132
- q.from({ books: booksCollection })
153
+ export function BlogDashboard() {
154
+ const [posts] = useStore('posts');
155
+
156
+ const { data: allPosts, isLoading } = useLiveQuery((q) =>
157
+ q.from({ posts })
158
+ .orderBy(({ posts }) => posts.created, 'desc')
133
159
  );
134
160
 
135
- if (isLoading) return <div>Loading...</div>;
161
+ if (isLoading) return <div>Loading posts...</div>;
136
162
 
137
163
  return (
138
- <ul>
139
- {books?.map(book => (
140
- <li key={book.id}>{book.title}</li>
164
+ <div>
165
+ <h1>Blog Posts</h1>
166
+ {allPosts?.map(post => (
167
+ <article key={post.id}>
168
+ <h2>{post.title}</h2>
169
+ <p>{post.content}</p>
170
+ {/* Expanded author is fully typed! */}
171
+ <small>By {post.expand?.author?.username}</small>
172
+ </article>
141
173
  ))}
142
- </ul>
174
+ </div>
143
175
  );
144
176
  }
145
177
  ```
146
178
 
179
+ ### 4. Add Real-time Comments
180
+
181
+ ```typescript
182
+ // PostWithComments.tsx
183
+ import { useLiveQuery } from '@tanstack/react-db';
184
+ import { eq } from '@tanstack/db';
185
+ import { useStore } from './app';
186
+ import { newRecordId } from 'pbtsdb';
187
+
188
+ export function PostWithComments({ postId }: { postId: string }) {
189
+ const [comments, posts] = useStore('comments', 'posts');
190
+
191
+ // Real-time comments for this post
192
+ const { data: postComments } = useLiveQuery((q) =>
193
+ q.from({ comments })
194
+ .where(({ comments }) => eq(comments.post, postId))
195
+ .orderBy(({ comments }) => comments.created, 'desc')
196
+ );
197
+
198
+ const handleAddComment = (text: string, authorId: string) => {
199
+ comments.insert({
200
+ id: newRecordId(),
201
+ post: postId,
202
+ author: authorId,
203
+ text
204
+ });
205
+ // Comment appears instantly (optimistic), syncs to PocketBase in background
206
+ };
207
+
208
+ return (
209
+ <div>
210
+ <h3>Comments ({postComments?.length || 0})</h3>
211
+ {postComments?.map(comment => (
212
+ <div key={comment.id}>
213
+ <strong>{comment.expand?.author?.username}:</strong>
214
+ <p>{comment.text}</p>
215
+ </div>
216
+ ))}
217
+ <CommentForm onSubmit={handleAddComment} />
218
+ </div>
219
+ );
220
+ }
221
+ ```
222
+
223
+ **That's it!** You now have a real-time blog with:
224
+ - ✅ Type-safe queries
225
+ - ✅ Automatic real-time updates
226
+ - ✅ Optimistic mutations
227
+ - ✅ Expanded relations
228
+
147
229
  ## Core Concepts
148
230
 
149
231
  ### Collections
@@ -151,8 +233,9 @@ function BooksList() {
151
233
  Collections are reactive data stores that automatically sync with PocketBase:
152
234
 
153
235
  ```typescript
154
- // Create a collection
155
- const booksCollection = factory.create('books');
236
+ // Create a collection using the curried API
237
+ const c = createCollection<MySchema>(pb, queryClient);
238
+ const booksCollection = c('books', {});
156
239
 
157
240
  // Collections automatically:
158
241
  // - Fetch data from PocketBase
@@ -167,22 +250,23 @@ Collections manage subscriptions **automatically** based on query lifecycle:
167
250
 
168
251
  ```typescript
169
252
  // Collections are lazy - no subscription until queried
170
- const booksCollection = factory.create('books');
253
+ const c = createCollection<MySchema>(pb, queryClient);
254
+ const booksCollection = c('books', {});
171
255
 
172
256
  // Subscription starts automatically when query becomes active
173
257
  const { data } = useLiveQuery((q) =>
174
258
  q.from({ books: booksCollection })
175
259
  );
176
260
  // ✅ Subscribed to changes while component is mounted
177
- // ✅ Unsubscribes automatically when component unmounts (with 5s cleanup delay)
178
-
179
- // Advanced: Manual subscription control
180
- await booksCollection.subscribe(); // Subscribe to all
181
- await booksCollection.subscribe('record_id'); // Subscribe to specific record
182
- booksCollection.unsubscribe('record_id'); // Unsubscribe from specific record
183
- booksCollection.unsubscribeAll(); // Clear all subscriptions
261
+ // ✅ Unsubscribes automatically when component unmounts
184
262
  ```
185
263
 
264
+ **Subscription Lifecycle:**
265
+ - **Lazy:** No subscription starts until the first `useLiveQuery` using the collection renders
266
+ - **Automatic:** Subscription starts when first subscriber mounts, stops when last subscriber unmounts
267
+ - **Shared:** Multiple components using the same collection share one subscription
268
+ - **No manual control needed:** The collection handles all subscription management internally
269
+
186
270
  ### Type Safety
187
271
 
188
272
  Full TypeScript support with compile-time type checking:
@@ -200,138 +284,137 @@ const { data } = useLiveQuery((q) =>
200
284
 
201
285
  ## API Reference
202
286
 
203
- ### CollectionFactory
287
+ ### createCollection()
204
288
 
205
- The main class for creating type-safe collections.
206
-
207
- #### Constructor
289
+ The main function for creating type-safe collections. Uses a curried API for better type inference.
208
290
 
209
291
  ```typescript
210
- new CollectionFactory<Schema>(pocketbase: PocketBase, queryClient: QueryClient)
292
+ const c = createCollection<Schema>(pb: PocketBase, queryClient: QueryClient);
293
+ const collection = c(collectionName: string, options?: CreateCollectionOptions);
211
294
  ```
212
295
 
213
296
  **Parameters:**
214
- - `pocketbase`: PocketBase instance
215
- - `queryClient`: TanStack Query QueryClient instance
216
-
217
- **Example:**
218
- ```typescript
219
- const factory = new CollectionFactory<MySchema>(pb, queryClient);
220
- ```
221
-
222
- #### create()
223
-
224
- Creates a reactive collection from a PocketBase collection.
225
-
226
- ```typescript
227
- create<CollectionName>(
228
- collection: CollectionName,
229
- options?: CreateCollectionOptions
230
- ): Collection & SubscribableCollection
231
- ```
297
+ - `pb` - PocketBase instance
298
+ - `queryClient` - TanStack Query QueryClient instance
299
+ - `collectionName` - Name of the PocketBase collection
300
+ - `options` - Optional configuration
232
301
 
233
302
  **Options:**
234
- - `expand?: string` - Relations to expand (e.g., `'author,metadata'`)
235
- - `relations?: Record<string, Collection>` - Collections for manual joins
303
+ - `expand?: Record<string, Collection>` - Relations to auto-expand and auto-upsert on every fetch
304
+ - `omitOnInsert?: readonly string[]` - Fields to make optional during insert (e.g., `['created', 'updated'] as const`)
305
+ - `syncMode?: 'eager' | 'on-demand'` - Data fetching strategy (default: `'eager'`)
306
+ - `onInsert?: InsertMutationFn | false` - Custom insert handler or `false` to disable
307
+ - `onUpdate?: UpdateMutationFn | false` - Custom update handler or `false` to disable
308
+ - `onDelete?: DeleteMutationFn | false` - Custom delete handler or `false` to disable
309
+
310
+ **Returns:** Fully-typed Collection instance with subscription capabilities
236
311
 
237
312
  **Examples:**
238
313
 
239
314
  Basic collection (lazy, subscribes automatically on first query):
240
315
  ```typescript
241
- const booksCollection = factory.create('books');
316
+ const c = createCollection<MySchema>(pb, queryClient);
317
+ const booksCollection = c('books', {});
242
318
  ```
243
319
 
244
- With expand:
320
+ With auto-expand relations:
245
321
  ```typescript
246
- const booksCollection = factory.create('books', {
247
- expand: 'author' as const // Type-safe expand
322
+ const c = createCollection<MySchema>(pb, queryClient);
323
+ const authorsCollection = c('authors', {});
324
+ const booksCollection = c('books', {
325
+ expand: {
326
+ author: authorsCollection // Auto-expand and auto-upsert
327
+ }
248
328
  });
249
329
 
250
- // Expanded relations available
330
+ // Expand is automatic on every fetch
251
331
  const { data } = useLiveQuery((q) => q.from({ books: booksCollection }));
252
- data[0].expand?.author // ✅ Typed!
253
- ```
254
332
 
255
- With relations for joins:
256
- ```typescript
257
- const authorsCollection = factory.create('authors');
258
- const booksCollection = factory.create('books', {
259
- relations: {
260
- author: authorsCollection
261
- }
262
- });
333
+ // Expanded records auto-inserted into authorsCollection
263
334
  ```
264
335
 
265
- ### React Provider
336
+ ### React Integration
266
337
 
267
- Provide collections to your React component tree.
338
+ #### createReactProvider()
268
339
 
269
- #### CollectionsProvider
340
+ Creates a React Provider and useStore hook from a collections map.
270
341
 
271
342
  ```typescript
272
- <CollectionsProvider collections={collectionsMap}>
273
- {children}
274
- </CollectionsProvider>
343
+ const { Provider, useStore } = createReactProvider(collections: CollectionsMap);
275
344
  ```
276
345
 
346
+ **Parameters:**
347
+ - `collections` - Object mapping keys to Collection instances
348
+
349
+ **Returns:**
350
+ - `Provider` - React Context Provider component
351
+ - `useStore` - Hook to access collections (variadic args, returns typed tuple)
352
+
277
353
  **Example:**
278
354
  ```typescript
279
- import { CollectionsProvider } from 'pocketbase-tanstack-db';
355
+ import { createCollection, createReactProvider } from 'pbtsdb';
280
356
 
357
+ const c = createCollection<MySchema>(pb, queryClient);
281
358
  const collections = {
282
- authors: factory.create('authors'),
283
- books: factory.create('books'),
359
+ authors: c('authors', {}),
360
+ books: c('books', {
361
+ omitOnInsert: ['created', 'updated'] as const
362
+ }),
284
363
  };
285
364
 
286
- function App() {
287
- return (
288
- <CollectionsProvider collections={collections}>
289
- <BooksList />
290
- </CollectionsProvider>
291
- );
292
- }
365
+ const { Provider, useStore } = createReactProvider(collections);
366
+
367
+ // Wrap your app
368
+ <Provider>
369
+ <App />
370
+ </Provider>
371
+ ```
372
+
373
+ **With custom collection key:**
374
+ ```typescript
375
+ const collections = {
376
+ myBooks: c('books', {}) // Key 'myBooks', PocketBase collection 'books'
377
+ };
378
+
379
+ const { Provider, useStore } = createReactProvider(collections);
380
+
381
+ // Access via custom key
382
+ const [myBooks] = useStore('myBooks');
293
383
  ```
294
384
 
295
385
  #### useStore()
296
386
 
297
- Access a single collection from the provider.
387
+ Access collections from the provider. Uses variadic arguments and returns a typed tuple.
298
388
 
389
+ **Single collection:**
299
390
  ```typescript
300
- const collection = useStore<RecordType>(key: string)
391
+ const [collection] = useStore('key')
301
392
  ```
302
393
 
303
- **Example:**
394
+ **Multiple collections:**
395
+ ```typescript
396
+ const [col1, col2, col3] = useStore('key1', 'key2', 'key3')
397
+ ```
398
+
399
+ **Examples:**
304
400
  ```typescript
305
401
  function BooksList() {
306
- const booksCollection = useStore<Book>('books');
402
+ const [books] = useStore('books'); // ✅ Typed automatically!
307
403
 
308
404
  const { data } = useLiveQuery((q) =>
309
- q.from({ books: booksCollection })
405
+ q.from({ books })
310
406
  );
311
407
 
312
408
  return <div>{/* ... */}</div>;
313
409
  }
314
- ```
315
-
316
- #### useStores()
317
410
 
318
- Access multiple collections at once.
319
-
320
- ```typescript
321
- const [col1, col2] = useStores<[Type1, Type2]>(keys: string[])
322
- ```
323
-
324
- **Example:**
325
- ```typescript
326
411
  function BooksWithAuthors() {
327
- const [booksCollection, authorsCollection] = useStores<[Book, Author]>(
328
- ['books', 'authors']
329
- );
412
+ const [books, authors] = useStore('books', 'authors'); // ✅ Variadic!
330
413
 
331
414
  const { data } = useLiveQuery((q) =>
332
- q.from({ book: booksCollection })
415
+ q.from({ book: books })
333
416
  .join(
334
- { author: authorsCollection },
417
+ { author: authors },
335
418
  ({ book, author }) => eq(book.author, author.id),
336
419
  'left'
337
420
  )
@@ -343,50 +426,26 @@ function BooksWithAuthors() {
343
426
 
344
427
  ### Subscriptions
345
428
 
346
- Collections support real-time subscriptions to PocketBase.
347
-
348
- #### subscribe()
349
-
350
- Subscribe to collection changes.
351
-
352
- ```typescript
353
- // Subscribe to all records
354
- await collection.subscribe();
355
-
356
- // Subscribe to specific record
357
- await collection.subscribe('record_id');
358
- ```
359
-
360
- #### unsubscribe()
361
-
362
- Unsubscribe from changes.
363
-
364
- ```typescript
365
- // Unsubscribe from all
366
- collection.unsubscribe();
367
-
368
- // Unsubscribe from specific record
369
- collection.unsubscribe('record_id');
370
- ```
429
+ Collections manage real-time subscriptions to PocketBase **automatically**. No manual subscription management is needed for normal usage.
371
430
 
372
- #### unsubscribeAll()
373
-
374
- Clear all subscriptions for a collection.
431
+ #### Automatic Subscription Lifecycle
375
432
 
376
433
  ```typescript
377
- collection.unsubscribeAll();
434
+ // Subscriptions start automatically when useLiveQuery renders
435
+ function MyComponent() {
436
+ const [books] = useStore('books');
437
+ const { data } = useLiveQuery((q) => q.from({ books }));
438
+ // ✅ Subscription active while this component is mounted
439
+ // ✅ Automatically stops when component unmounts
440
+ }
378
441
  ```
379
442
 
380
443
  #### isSubscribed()
381
444
 
382
- Check subscription status.
445
+ Check if a collection has an active subscription.
383
446
 
384
447
  ```typescript
385
- // Check collection-wide subscription
386
448
  const isSubbed = collection.isSubscribed(); // boolean
387
-
388
- // Check specific record subscription
389
- const isRecordSubbed = collection.isSubscribed('record_id'); // boolean
390
449
  ```
391
450
 
392
451
  #### waitForSubscription()
@@ -394,519 +453,432 @@ const isRecordSubbed = collection.isSubscribed('record_id'); // boolean
394
453
  Wait for subscription to be established (useful in tests).
395
454
 
396
455
  ```typescript
397
- await collection.waitForSubscription(); // Wait for collection-wide
398
- await collection.waitForSubscription('record_id'); // Wait for specific record
399
- await collection.waitForSubscription(undefined, 5000); // With timeout
456
+ await collection.waitForSubscription(); // Wait with default 5s timeout
457
+ await collection.waitForSubscription(10000); // Wait with custom timeout (ms)
400
458
  ```
401
459
 
402
- ## Usage Examples
403
-
404
- ### Basic Queries
405
-
406
- #### Fetch All Records
407
-
408
- ```typescript
409
- const { data: books, isLoading, error } = useLiveQuery((q) =>
410
- q.from({ books: booksCollection })
411
- );
412
-
413
- if (isLoading) return <div>Loading...</div>;
414
- if (error) return <div>Error: {error.message}</div>;
460
+ ### Utility Functions
415
461
 
416
- return (
417
- <ul>
418
- {books?.map(book => (
419
- <li key={book.id}>{book.title}</li>
420
- ))}
421
- </ul>
422
- );
423
- ```
462
+ #### newRecordId()
424
463
 
425
- #### Fetch Single Record
464
+ Generate a PocketBase-compatible record ID (15-character alphanumeric string).
426
465
 
427
466
  ```typescript
428
- import { eq } from '@tanstack/db';
467
+ import { newRecordId } from 'pbtsdb';
429
468
 
430
- const bookId = 'abc123';
469
+ const id = newRecordId(); // "a1b2c3d4e5f6g7h"
431
470
 
432
- const { data: books } = useLiveQuery((q) =>
433
- q.from({ books: booksCollection })
434
- .where(({ books }) => eq(books.id, bookId))
435
- );
471
+ // Use when creating records
472
+ const newBook = {
473
+ id: newRecordId(),
474
+ title: 'New Book',
475
+ // ... other fields
476
+ };
436
477
 
437
- const book = books?.[0];
478
+ booksCollection.insert(newBook);
438
479
  ```
439
480
 
440
- ### Filtering and Sorting
481
+ **Returns:** `string` - 15-character lowercase alphanumeric ID
482
+
483
+ ## Usage Examples
441
484
 
442
- #### Basic Filtering
485
+ ### Example 1: Task Manager with Filtering
443
486
 
444
487
  ```typescript
445
- import { eq, gt, and, or } from '@tanstack/db';
488
+ // TaskBoard.tsx
489
+ import { useLiveQuery } from '@tanstack/react-db';
490
+ import { eq, and } from '@tanstack/db';
491
+ import { useStore } from './app';
446
492
 
447
- // Filter by genre
448
- const { data } = useLiveQuery((q) =>
449
- q.from({ books: booksCollection })
450
- .where(({ books }) => eq(books.genre, 'Fiction'))
451
- );
493
+ export function TaskBoard({ userId }: { userId: string }) {
494
+ const [tasks] = useStore('tasks');
452
495
 
453
- // Filter by date
454
- const { data } = useLiveQuery((q) =>
455
- q.from({ books: booksCollection })
456
- .where(({ books }) =>
457
- gt(books.published_date, '2020-01-01')
458
- )
459
- );
496
+ // Filter tasks by assignee and status - updates in real-time
497
+ const { data: myTasks } = useLiveQuery((q) =>
498
+ q.from({ tasks })
499
+ .where(({ tasks }) => and(eq(tasks.assignee, userId), eq(tasks.status, 'in_progress')))
500
+ .orderBy(({ tasks }) => tasks.due_date, 'asc')
501
+ );
460
502
 
461
- // Multiple conditions with AND
462
- const { data } = useLiveQuery((q) =>
463
- q.from({ books: booksCollection })
464
- .where(({ books }) => and(
465
- eq(books.genre, 'Fiction'),
466
- gt(books.published_date, '2020-01-01')
467
- ))
468
- );
503
+ const handleComplete = (taskId: string) => {
504
+ tasks.update(taskId, (draft) => { draft.status = 'done'; });
505
+ };
469
506
 
470
- // Multiple conditions with OR
471
- const { data } = useLiveQuery((q) =>
472
- q.from({ books: booksCollection })
473
- .where(({ books }) => or(
474
- eq(books.genre, 'Fiction'),
475
- eq(books.genre, 'Science Fiction')
476
- ))
477
- );
507
+ return (
508
+ <div>
509
+ <h2>My Tasks ({myTasks?.length || 0})</h2>
510
+ {myTasks?.map(task => (
511
+ <div key={task.id}>
512
+ {task.title}
513
+ <button onClick={() => handleComplete(task.id)}>Complete</button>
514
+ </div>
515
+ ))}
516
+ </div>
517
+ );
518
+ }
478
519
  ```
479
520
 
480
- #### Advanced Filtering
521
+ ### Example 2: E-commerce Product Catalog with Filtering
481
522
 
482
523
  ```typescript
483
- import { gte, lte, and } from '@tanstack/db';
524
+ // ProductCatalog.tsx
525
+ import { useLiveQuery } from '@tanstack/react-db';
526
+ import { and, gte, lte } from '@tanstack/db';
527
+ import { useStore } from './app';
528
+
529
+ export function ProductCatalog() {
530
+ const [products] = useStore('products');
531
+ const [category, setCategory] = useState<string | null>(null);
532
+ const [maxPrice, setMaxPrice] = useState(1000);
533
+
534
+ // Dynamic filtering - updates reactively
535
+ const { data: filteredProducts } = useLiveQuery((q) => {
536
+ let query = q.from({ products })
537
+ .where(({ products }) => and(
538
+ products.in_stock === true,
539
+ lte(products.price, maxPrice)
540
+ ));
541
+
542
+ if (category) {
543
+ query = query.where(({ products }) => products.category === category);
544
+ }
484
545
 
485
- // Complex nested queries
486
- const { data } = useLiveQuery((q) =>
487
- q.from({ books: booksCollection })
488
- .where(({ books }) => and(
489
- eq(books.genre, 'Fiction'),
490
- or(
491
- gte(books.published_date, '2020-01-01'),
492
- lte(books.published_date, '2010-12-31')
493
- )
494
- ))
495
- );
546
+ return query.orderBy(({ products }) => products.rating, 'desc');
547
+ });
548
+
549
+ return (
550
+ <div>
551
+ <select onChange={(e) => setCategory(e.target.value || null)}>
552
+ <option value="">All Categories</option>
553
+ <option value="electronics">Electronics</option>
554
+ </select>
555
+ <input type="range" max="1000" value={maxPrice}
556
+ onChange={(e) => setMaxPrice(+e.target.value)} />
557
+
558
+ {filteredProducts?.map(product => (
559
+ <ProductCard key={product.id} product={product} />
560
+ ))}
561
+ </div>
562
+ );
563
+ }
496
564
  ```
497
565
 
498
- #### Sorting
566
+ ### Example 3: Social Media Feed with Likes
499
567
 
500
568
  ```typescript
501
- // Sort descending
502
- const { data } = useLiveQuery((q) =>
503
- q.from({ books: booksCollection })
504
- .orderBy(({ books }) => books.published_date, 'desc')
505
- );
506
-
507
- // Sort ascending
508
- const { data } = useLiveQuery((q) =>
509
- q.from({ books: booksCollection })
510
- .orderBy(({ books }) => books.title, 'asc')
511
- );
512
-
513
- // Multiple sorts
514
- const { data } = useLiveQuery((q) =>
515
- q.from({ books: booksCollection })
516
- .orderBy(({ books }) => books.genre, 'asc')
517
- .orderBy(({ books }) => books.published_date, 'desc')
518
- );
519
- ```
569
+ // SocialFeed.tsx
570
+ import { useLiveQuery } from '@tanstack/react-db';
571
+ import { eq } from '@tanstack/db';
572
+ import { useStore } from './app';
573
+ import { newRecordId } from 'pbtsdb';
520
574
 
521
- ### Relations and Joins
575
+ export function SocialFeed({ currentUserId }: { currentUserId: string }) {
576
+ const [posts, likes] = useStore('posts', 'likes');
522
577
 
523
- #### Type-Safe Expand (Recommended)
578
+ const { data: feedPosts } = useLiveQuery((q) =>
579
+ q.from({ posts }).orderBy(({ posts }) => posts.created, 'desc')
580
+ );
524
581
 
525
- Use PocketBase's built-in expand for fast, server-side joins:
582
+ const { data: userLikes } = useLiveQuery((q) =>
583
+ q.from({ likes }).where(({ likes }) => eq(likes.user, currentUserId))
584
+ );
526
585
 
527
- ```typescript
528
- // Create collection with expand
529
- const booksCollection = factory.create('books', {
530
- expand: 'author' as const // ← Type-safe!
531
- });
586
+ const likedPostIds = new Set(userLikes?.map(l => l.post) || []);
532
587
 
533
- // Use in query
534
- const { data } = useLiveQuery((q) =>
535
- q.from({ books: booksCollection })
536
- );
588
+ const handleLike = (postId: string) => {
589
+ if (likedPostIds.has(postId)) {
590
+ const like = userLikes?.find(l => l.post === postId);
591
+ if (like) likes.delete(like.id);
592
+ } else {
593
+ likes.insert({ id: newRecordId(), post: postId, user: currentUserId });
594
+ }
595
+ };
537
596
 
538
- // Access expanded relations (fully typed!)
539
- data?.forEach(book => {
540
- if (book.expand?.author) {
541
- console.log(book.expand.author.name); // ✅ Type-safe
542
- }
543
- });
597
+ return (
598
+ <div>
599
+ {feedPosts?.map(post => (
600
+ <div key={post.id}>
601
+ <strong>{post.expand?.author?.username}</strong>
602
+ <p>{post.content}</p>
603
+ <button onClick={() => handleLike(post.id)}>
604
+ {likedPostIds.has(post.id) ? '❤️' : '🤍'} {post.likes_count}
605
+ </button>
606
+ </div>
607
+ ))}
608
+ </div>
609
+ );
610
+ }
544
611
  ```
545
612
 
546
- #### Multiple Expands
613
+ ### Example 4: Real-time Collaborative Todo List
547
614
 
548
615
  ```typescript
549
- const booksCollection = factory.create('books', {
550
- expand: 'author,metadata' as const
551
- });
552
-
553
- // Both relations expanded
554
- data[0].expand?.author // ✅ Author type
555
- data[0].expand?.metadata // Metadata type
556
- ```
557
-
558
- #### TanStack DB Joins
559
-
560
- For complex client-side joins:
616
+ // CollaborativeTodoList.tsx
617
+ import { useLiveQuery } from '@tanstack/react-db';
618
+ import { eq } from '@tanstack/db';
619
+ import { useStore } from './app';
620
+ import { newRecordId } from 'pbtsdb';
621
+
622
+ export function CollaborativeTodoList({ listId, userId }: { listId: string; userId: string }) {
623
+ const [todos] = useStore('todos');
624
+ const [newText, setNewText] = useState('');
625
+
626
+ // Real-time todos - updates when any user adds/edits
627
+ const { data: allTodos } = useLiveQuery((q) =>
628
+ q.from({ todos })
629
+ .where(({ todos }) => eq(todos.list_id, listId))
630
+ .orderBy(({ todos }) => todos.created, 'asc')
631
+ );
561
632
 
562
- ```typescript
563
- const authorsCollection = factory.create('authors');
564
- const booksCollection = factory.create('books', {
565
- relations: {
566
- author: authorsCollection
567
- }
568
- });
633
+ const handleAdd = () => {
634
+ if (!newText.trim()) return;
635
+ todos.insert({ id: newRecordId(), text: newText, completed: false, list_id: listId, created_by: userId });
636
+ setNewText('');
637
+ };
569
638
 
570
- // Manual join with full type safety
571
- const { data } = useLiveQuery((q) =>
572
- q.from({ book: booksCollection })
573
- .join(
574
- { author: authorsCollection },
575
- ({ book, author }) => eq(book.author, author.id),
576
- 'left' // Join type: 'left' | 'right' | 'inner' | 'full'
577
- )
578
- .select(({ book, author }) => ({
579
- ...book,
580
- expand: {
581
- author: author ? { ...author } : undefined
582
- }
583
- }))
584
- );
639
+ return (
640
+ <div>
641
+ <input value={newText} onChange={(e) => setNewText(e.target.value)}
642
+ onKeyPress={(e) => e.key === 'Enter' && handleAdd()} />
643
+ <ul>
644
+ {allTodos?.map(todo => (
645
+ <li key={todo.id}>
646
+ <input type="checkbox" checked={todo.completed}
647
+ onChange={() => todos.update(todo.id, d => { d.completed = !d.completed; })} />
648
+ {todo.text}
649
+ <button onClick={() => todos.delete(todo.id)}>×</button>
650
+ </li>
651
+ ))}
652
+ </ul>
653
+ </div>
654
+ );
655
+ }
585
656
  ```
586
657
 
587
- #### Inner Join (Filter Out Missing Relations)
658
+ Real-time collaboration works automatically - when User A adds/edits a todo, User B sees it instantly.
588
659
 
589
- ```typescript
590
- const { data } = useLiveQuery((q) =>
591
- q.from({ book: booksCollection })
592
- .join(
593
- { author: authorsCollection },
594
- ({ book, author }) => eq(book.author, author.id),
595
- 'inner' // Only books WITH authors
596
- )
597
- );
598
- ```
599
-
600
- #### Complex Multi-Collection Joins
660
+ ### Example 5: Form with Optimistic Updates and Error Handling
601
661
 
602
662
  ```typescript
603
- const authorsCollection = factory.create('authors');
604
- const booksCollection = factory.create('books');
605
- const metadataCollection = factory.create('book_metadata');
663
+ // CreateBookForm.tsx
664
+ import { useStore } from './app';
665
+ import { newRecordId } from 'pbtsdb';
606
666
 
607
- const { data } = useLiveQuery((q) =>
608
- q.from({ book: booksCollection })
609
- .join(
610
- { author: authorsCollection },
611
- ({ book, author }) => eq(book.author, author.id),
612
- 'left'
613
- )
614
- .join(
615
- { metadata: metadataCollection },
616
- ({ book, metadata }) => eq(book.id, metadata.book),
617
- 'left'
618
- )
619
- .select(({ book, author, metadata }) => ({
620
- ...book,
621
- expand: {
622
- author: author ? { ...author } : undefined,
623
- metadata: metadata ? { ...metadata } : undefined
624
- }
625
- }))
626
- );
627
- ```
667
+ export function CreateBookForm() {
668
+ const [books] = useStore('books');
669
+ const [title, setTitle] = useState('');
670
+ const [error, setError] = useState<string | null>(null);
628
671
 
629
- ### Real-time Updates
672
+ const handleSubmit = async (e: React.FormEvent) => {
673
+ e.preventDefault();
674
+ setError(null);
630
675
 
631
- #### Automatic Updates
676
+ try {
677
+ // Optimistic insert - appears instantly
678
+ const tx = books.insert({ id: newRecordId(), title, author: 'author_id' });
679
+ await tx.isPersisted.promise;
632
680
 
633
- Collections automatically receive real-time updates based on query lifecycle:
681
+ if (tx.state === 'completed') setTitle('');
682
+ else setError('Failed to create book');
683
+ } catch (err: any) {
684
+ setError(err.data ? Object.values(err.data).join(', ') : err.message);
685
+ }
686
+ };
634
687
 
635
- ```typescript
636
- function BooksList() {
637
- const { data } = useLiveQuery((q) =>
638
- q.from({ books: booksCollection })
688
+ return (
689
+ <form onSubmit={handleSubmit}>
690
+ {error && <div className="error">{error}</div>}
691
+ <input value={title} onChange={(e) => setTitle(e.target.value)} required />
692
+ <button type="submit">Add Book</button>
693
+ </form>
639
694
  );
640
-
641
- // Subscription automatically starts when component mounts
642
- // Component re-renders automatically when:
643
- // - A book is created
644
- // - A book is updated
645
- // - A book is deleted
646
- // Subscription automatically stops when component unmounts (with 5s delay)
647
-
648
- return <ul>{/* ... */}</ul>;
649
695
  }
650
696
  ```
651
697
 
652
- **Subscription Lifecycle:**
653
- - ✅ Collections are lazy - no network activity on creation
654
- - ✅ Subscription starts when first `useLiveQuery` becomes active
655
- - ✅ Multiple queries share a single subscription per collection
656
- - ✅ Subscription stops 5 seconds after last query unmounts
657
- - ✅ Prevents thrashing during rapid mount/unmount cycles
658
-
659
- #### Manual Subscription Control (Advanced)
698
+ Optimistic updates show changes instantly; automatic rollback on server errors.
660
699
 
661
- For advanced use cases, you can manually control subscriptions:
700
+ ### Example 6: Dashboard with Multiple Collections and Joins
662
701
 
663
702
  ```typescript
664
- const booksCollection = factory.create('books');
703
+ // ProjectDashboard.tsx
704
+ import { useLiveQuery } from '@tanstack/react-db';
705
+ import { eq } from '@tanstack/db';
706
+ import { useStore } from './app';
665
707
 
666
- // Manually subscribe (bypasses automatic lifecycle)
667
- await booksCollection.subscribe();
708
+ export function ProjectDashboard({ projectId }: { projectId: string }) {
709
+ const [projects, tasks, teamMembers, users] = useStore('projects', 'tasks', 'team_members', 'users');
668
710
 
669
- // Subscribe to specific record
670
- await booksCollection.subscribe('record_id');
711
+ const { data: projectList } = useLiveQuery((q) =>
712
+ q.from({ projects }).where(({ projects }) => eq(projects.id, projectId))
713
+ );
671
714
 
672
- // Unsubscribe when done
673
- booksCollection.unsubscribe('record_id');
674
- booksCollection.unsubscribeAll();
675
- ```
715
+ const { data: projectTasks } = useLiveQuery((q) =>
716
+ q.from({ tasks }).where(({ tasks }) => eq(tasks.project, projectId))
717
+ );
676
718
 
677
- #### Subscribing to Specific Records
719
+ // Join team members with users
720
+ const { data: team } = useLiveQuery((q) =>
721
+ q.from({ member: teamMembers })
722
+ .where(({ member }) => eq(member.project, projectId))
723
+ .join({ user: users }, ({ member, user }) => eq(member.user, user.id), 'left')
724
+ .select(({ member, user }) => ({ id: member.id, role: member.role, name: user?.name }))
725
+ );
678
726
 
679
- ```typescript
680
- // Subscribe to a specific book
681
- await booksCollection.subscribe('book_id_123');
727
+ const completed = projectTasks?.filter(t => t.completed).length || 0;
728
+ const total = projectTasks?.length || 0;
682
729
 
683
- // Check if subscribed
684
- if (booksCollection.isSubscribed('book_id_123')) {
685
- console.log('Subscribed to book_id_123');
730
+ return (
731
+ <div>
732
+ <h1>{projectList?.[0]?.name}</h1>
733
+ <p>Progress: {completed}/{total} tasks</p>
734
+ <p>Team: {team?.map(m => m.name).join(', ')}</p>
735
+ </div>
736
+ );
686
737
  }
687
-
688
- // Unsubscribe from specific record
689
- booksCollection.unsubscribe('book_id_123');
690
738
  ```
691
739
 
692
- #### Waiting for Subscription (Testing)
693
-
694
- ```typescript
695
- // Useful in tests
696
- await booksCollection.waitForSubscription();
697
-
698
- // Now safe to create/update records and expect real-time updates
699
- const newBook = await pb.collection('books').create({ /* ... */ });
700
-
701
- // Wait for update to propagate
702
- await waitFor(() => {
703
- expect(data?.some(b => b.id === newBook.id)).toBe(true);
704
- });
705
- ```
740
+ Demonstrates variadic `useStore()`, client-side aggregations, and TanStack DB joins.
706
741
 
707
742
  ## TypeScript
708
743
 
709
- ### Schema Declaration
744
+ pbtsdb is fully type-safe. Here's what you need to know:
710
745
 
711
- Define your schema with full type safety:
746
+ ### Define Your Schema
712
747
 
713
- ```typescript
714
- import type { SchemaDeclaration } from 'pocketbase-tanstack-db';
748
+ Use the simple schema format shown in the Quick Start:
715
749
 
716
- interface MySchema extends SchemaDeclaration {
750
+ ```typescript
751
+ type MySchema = {
717
752
  collection_name: {
718
- Row: RecordType; // Your record type
719
- Relations: {
720
- forward: {
721
- // Forward relations (FK fields)
722
- field_name: [
723
- 'target_collection',
724
- is_array // false for single, true for array
725
- ];
726
- };
727
- back: {
728
- // Back relations (reverse lookups)
729
- relation_name: ['source_collection', is_array];
730
- };
753
+ type: RecordInterface; // Your record type
754
+ relations: {
755
+ field_name: RelatedType; // Related record types
731
756
  };
732
757
  };
733
758
  }
734
759
  ```
735
760
 
736
- ### Example: Blog Schema
737
-
738
- ```typescript
739
- interface Post {
740
- id: string;
741
- title: string;
742
- content: string;
743
- author: string; // FK to users
744
- tags: string[]; // FK array to tags
745
- created: string;
746
- updated: string;
747
- }
748
-
749
- interface User {
750
- id: string;
751
- username: string;
752
- email: string;
753
- created: string;
754
- updated: string;
755
- }
756
-
757
- interface Tag {
758
- id: string;
759
- name: string;
760
- created: string;
761
- updated: string;
762
- }
763
-
764
- interface BlogSchema extends SchemaDeclaration {
765
- posts: {
766
- Row: Post;
767
- Relations: {
768
- forward: {
769
- author: ['users', false]; // Single relation
770
- tags: ['tags', true]; // Array relation
771
- };
772
- back: {};
773
- };
774
- };
775
- users: {
776
- Row: User;
777
- Relations: {
778
- forward: {};
779
- back: {
780
- posts: ['posts', true]; // One user, many posts
781
- };
782
- };
783
- };
784
- tags: {
785
- Row: Tag;
786
- Relations: {
787
- forward: {};
788
- back: {
789
- posts: ['posts', true]; // One tag, many posts
790
- };
791
- };
792
- };
793
- }
794
- ```
761
+ **Pro tip:** Use [pocketbase-schema-generator](https://github.com/satohshi/pocketbase-schema-generator) to auto-generate types from your PocketBase database.
795
762
 
796
- ### Type-Safe Expand
763
+ ### Type-Safe Collections
797
764
 
798
- Use `as const` for type-safe expand strings:
765
+ Always create collections with proper type parameters:
799
766
 
800
767
  ```typescript
801
- const postsCollection = factory.create('posts', {
802
- expand: 'author,tags' as const
803
- // TypeScript validates these are real relations
768
+ // Good - full type safety
769
+ const c = createCollection<MySchema>(pb, queryClient);
770
+ const books = c('books', {
771
+ omitOnInsert: ['created', 'updated'] as const
804
772
  });
805
773
 
806
- // Expanded fields are typed
807
- data[0].expand?.author.username // string
808
- data[0].expand?.tags[0].name // string
774
+ // Good - with auto-expand relations
775
+ const authors = c('authors', {});
776
+ const books = c('books', {
777
+ expand: {
778
+ author: authors
779
+ }
780
+ });
809
781
  ```
810
782
 
811
783
  ## Best Practices
812
784
 
813
- ### 1. Choose the Right Approach for Relations
814
-
815
- **Use Type-Safe Expand when:**
816
- - You need fast, single-query performance
817
- - Relations are straightforward
785
+ ### 1. Define Collections Centrally
818
786
 
787
+ Define all collections once at app initialization:
819
788
 
820
789
  ```typescript
821
- // ✅ Fast, simple, type-safe
822
- const books = factory.create('books', {
823
- expand: 'author' as const
790
+ // ✅ Do this - centralized, type-safe
791
+ const c = createCollection<MySchema>(pb, queryClient);
792
+
793
+ export const { Provider, useStore } = createReactProvider({
794
+ posts: c('posts', { omitOnInsert: ['created', 'updated'] as const }),
795
+ users: c('users', {}),
796
+ comments: c('comments', { omitOnInsert: ['created', 'updated'] as const })
824
797
  });
825
798
  ```
826
799
 
827
- **Use TanStack Joins when:**
828
- - You need inner/right/full joins
829
- - You want the related records to update in response to sync
830
- - Complex client-side filtering after joins
831
- - Building computed fields from multiple collections
800
+ ### 2. Create Dependencies Before Dependents
832
801
 
802
+ When using expand collections, create the target collection first:
833
803
 
834
804
  ```typescript
835
- // ✅ Flexible, powerful, type-safe
836
- const { data } = useLiveQuery((q) =>
837
- q.from({ book: booksCollection })
838
- .join({ author: authorsCollection }, ..., 'inner')
839
- );
840
- ```
841
-
842
- ### 2. Use Provider for App-Wide Collections
843
-
844
- ```typescript
845
- // ✅ Define collections once
846
- const collections = {
847
- books: factory.create('books'),
848
- authors: factory.create('authors'),
849
- };
805
+ // ✅ Good - authors exists before books references it
806
+ const c = createCollection<MySchema>(pb, queryClient);
807
+ const authors = c('authors', {});
808
+ const books = c('books', {
809
+ expand: {
810
+ author: authors // authors is already created
811
+ }
812
+ });
850
813
 
851
- // Use throughout app
852
- <CollectionsProvider collections={collections}>
853
- <App />
854
- </CollectionsProvider>
814
+ // Bad - can't reference what doesn't exist yet
815
+ const books = c('books', {
816
+ expand: {
817
+ author: ??? // Where is authors?
818
+ }
819
+ });
855
820
  ```
856
821
 
857
- ### 3. Type Your Hooks
822
+ ### 3. Subscriptions are Automatic
823
+
824
+ Don't manually subscribe - just use `useLiveQuery`:
858
825
 
859
826
  ```typescript
860
- // ✅ Explicit typing
861
- const booksCollection = useStore<Book>('books');
827
+ // ✅ Do this
828
+ const { data } = useLiveQuery((q) => q.from({ posts }));
862
829
 
863
- // Tuple typing for multiple collections
864
- const [books, authors] = useStores<[Book, Author]>(['books', 'authors']);
830
+ // Don't do this
831
+ useEffect(() => {
832
+ posts.subscribe();
833
+ return () => posts.unsubscribe();
834
+ }, []);
865
835
  ```
866
836
 
867
- ### 4. Handle Loading and Error States
837
+ ### 4. Handle Loading States
838
+
839
+ Always check loading and error states:
868
840
 
869
841
  ```typescript
870
- const { data, isLoading, error } = useLiveQuery((q) =>
871
- q.from({ books: booksCollection })
872
- );
842
+ const { data, isLoading, error } = useLiveQuery((q) => q.from({ posts }));
873
843
 
874
- if (isLoading) return <Spinner />;
875
- if (error) return <ErrorMessage error={error} />;
876
- if (!data?.length) return <EmptyState />;
844
+ if (isLoading) return <div>Loading...</div>;
845
+ if (error) return <div>Error: {error.message}</div>;
846
+ if (!data?.length) return <div>No posts found</div>;
877
847
 
878
- return <BooksList books={data} />;
848
+ return <PostsList posts={data} />;
879
849
  ```
880
850
 
881
- ### 5. Subscriptions are Automatic
851
+ ### 5. Use Expand for Performance
882
852
 
883
- No need to manually subscribe/unsubscribe - the library handles it:
853
+ Use PocketBase's expand feature for better performance:
884
854
 
885
855
  ```typescript
886
- // Don't do this (unless you have advanced use case)
887
- useEffect(() => {
888
- booksCollection.subscribe();
889
- return () => booksCollection.unsubscribe();
890
- }, []);
856
+ // Fast - single query with server-side expand
857
+ const c = createCollection<MySchema>(pb, queryClient);
858
+ const authors = c('authors', {});
859
+ const posts = c('posts', {
860
+ expand: {
861
+ author: authors // Auto-expand on every fetch
862
+ }
863
+ });
891
864
 
892
- // Do this instead - automatic lifecycle management
893
- const { data } = useLiveQuery((q) =>
894
- q.from({ books: booksCollection })
895
- );
865
+ const { data } = useLiveQuery((q) => q.from({ posts }));
866
+
867
+ // ⚠️ Slower - multiple queries + client-side join
868
+ // Only use TanStack DB joins for inner/right/full join behavior
896
869
  ```
897
870
 
898
- ### 6. Use QueryClient Configuration
871
+ ### 6. Configure QueryClient Defaults
899
872
 
900
873
  ```typescript
901
874
  const queryClient = new QueryClient({
902
875
  defaultOptions: {
903
876
  queries: {
904
- staleTime: 1000 * 60 * 5, // 5 minutes
905
- gcTime: 1000 * 60 * 10, // 10 minutes
906
- retry: 3,
907
- refetchOnWindowFocus: false,
908
- },
909
- },
877
+ staleTime: 60_000, // 1 minute
878
+ gcTime: 300_000, // 5 minutes
879
+ refetchOnWindowFocus: false
880
+ }
881
+ }
910
882
  });
911
883
  ```
912
884
 
@@ -917,7 +889,7 @@ const queryClient = new QueryClient({
917
889
  By default, pbtsdb logs debug messages to the console in development mode. You can integrate with your own logging service (Sentry, LogRocket, etc.) using `setLogger`:
918
890
 
919
891
  ```typescript
920
- import { setLogger } from 'pocketbase-tanstack-db';
892
+ import { setLogger } from 'pbtsdb';
921
893
 
922
894
  // Example: Send errors to Sentry
923
895
  setLogger({
@@ -946,7 +918,7 @@ setLogger({
946
918
  **Disable logging completely:**
947
919
 
948
920
  ```typescript
949
- import { setLogger } from 'pocketbase-tanstack-db';
921
+ import { setLogger } from 'pbtsdb';
950
922
 
951
923
  setLogger({
952
924
  debug: () => {},
@@ -958,14 +930,14 @@ setLogger({
958
930
  **Reset to default logger:**
959
931
 
960
932
  ```typescript
961
- import { resetLogger } from 'pocketbase-tanstack-db';
933
+ import { resetLogger } from 'pbtsdb';
962
934
 
963
935
  resetLogger();
964
936
  ```
965
937
 
966
938
  ## License
967
939
 
968
- ISC
940
+ MIT
969
941
 
970
942
  ## Contributing
971
943
 
@@ -979,8 +951,8 @@ Contributions welcome! Please open an issue or PR.
979
951
 
980
952
  **Clone and Install:**
981
953
  ```bash
982
- git clone https://github.com/yourusername/pocketbase-tanstack-db
983
- cd pocketbase-tanstack-db
954
+ git clone https://github.com/yourusername/pbtsdb
955
+ cd pbtsdb
984
956
  npm install
985
957
  ```
986
958