pbtsdb 0.0.1 → 0.1.0

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/dist/index.d.ts CHANGED
@@ -1,9 +1,17 @@
1
- import PocketBase, { UnsubscribeFunc } from 'pocketbase';
2
- import { Collection } from '@tanstack/db';
1
+ import { Collection, InsertMutationFn, UpdateMutationFn, DeleteMutationFn } from '@tanstack/db';
2
+ import PocketBase from 'pocketbase';
3
+ import { Collection as Collection$1 } from '@tanstack/react-db';
4
+ import { QueryCollectionUtils } from '@tanstack/query-db-collection';
3
5
  import { QueryClient } from '@tanstack/react-query';
4
- import * as react_jsx_runtime from 'react/jsx-runtime';
5
- import { ReactNode } from 'react';
6
+ import React, { ReactNode } from 'react';
6
7
 
8
+ /**
9
+ * Base record type required by PocketBase collections.
10
+ * All records must have an 'id' field.
11
+ */
12
+ interface BaseRecord {
13
+ id: string;
14
+ }
7
15
  /**
8
16
  * Schema declaration for type-safe collection management.
9
17
  * Define your PocketBase collections with their record types and relations.
@@ -22,98 +30,37 @@ import { ReactNode } from 'react';
22
30
  */
23
31
  interface SchemaDeclaration {
24
32
  [collectionName: string]: {
25
- type: object;
33
+ type: BaseRecord;
26
34
  relations?: {
27
- [fieldName: string]: object | object[];
35
+ [fieldName: string]: BaseRecord | BaseRecord[];
28
36
  };
29
37
  };
30
38
  }
31
39
  /**
32
- * PocketBase real-time event structure.
33
- * Matches RecordSubscription from the PocketBase SDK.
34
- */
35
- interface RealtimeEvent<T extends object = object> {
36
- action: string;
37
- record: T;
38
- }
39
- /**
40
- * Internal state tracking for subscription management.
41
- * Includes reconnection logic and record-specific subscriptions.
42
- */
43
- interface SubscriptionState {
44
- unsubscribe: UnsubscribeFunc;
45
- recordId?: string;
46
- reconnectAttempts: number;
47
- isReconnecting: boolean;
48
- }
49
- /**
50
- * Enhanced collection interface with subscription management capabilities.
51
- * Provides methods to control real-time updates from PocketBase.
40
+ * Extracts the record type from a schema collection.
41
+ * @internal
52
42
  */
53
- interface SubscribableCollection<T extends object = object> {
54
- /**
55
- * Subscribe to real-time updates for this collection.
56
- * @param recordId - Optional: Subscribe to a specific record, or omit for collection-wide updates
57
- */
58
- subscribe: (recordId?: string) => Promise<void>;
59
- /**
60
- * Unsubscribe from real-time updates.
61
- * @param recordId - Optional: Unsubscribe from a specific record, or omit for collection-wide
62
- */
63
- unsubscribe: (recordId?: string) => void;
64
- /**
65
- * Unsubscribe from all subscriptions for this collection.
66
- */
67
- unsubscribeAll: () => void;
68
- /**
69
- * Check if currently subscribed to updates.
70
- * @param recordId - Optional: Check a specific record subscription, or omit for collection-wide
71
- */
72
- isSubscribed: (recordId?: string) => boolean;
73
- /**
74
- * Wait for a subscription to be fully established (useful for testing).
75
- * @param recordId - Optional record ID
76
- * @param timeoutMs - Timeout in milliseconds (default: 5000)
77
- */
78
- waitForSubscription: (recordId?: string, timeoutMs?: number) => Promise<void>;
79
- }
43
+ type ExtractRecordType<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = Schema[CollectionName]['type'];
80
44
  /**
81
- * Join helper for type-safe TanStack DB joins.
82
- * Provides access to pre-configured relation collections.
45
+ * Valid field names that can be omitted during insert operations.
46
+ * Excludes 'id' which is always required for TanStack DB record tracking.
47
+ * @internal
83
48
  */
84
- interface JoinHelper<Schema extends SchemaDeclaration, CollectionName extends keyof Schema, RecordType extends object> {
85
- /**
86
- * Get a pre-configured collection for joining a relation field.
87
- * This returns the related collection that can be used with TanStack DB's .join() method.
88
- *
89
- * @param fieldName - The relation field name to join
90
- * @returns The collection for the related entity, or undefined if not configured
91
- *
92
- * @example
93
- * ```ts
94
- * const jobsCollection = factory.create('jobs', {
95
- * relations: {
96
- * customer: customersCollection,
97
- * address: addressesCollection
98
- * }
99
- * });
100
- *
101
- * // Use with TanStack DB joins
102
- * const query = q.from({ job: jobsCollection })
103
- * .join(
104
- * { customer: jobsCollection.relations.customer },
105
- * ({ job, customer }) => eq(job.customer, customer.id),
106
- * 'left'
107
- * );
108
- * ```
109
- */
110
- relations: RelationsConfig<Schema, CollectionName>;
111
- }
49
+ type OmittableFields<T extends object> = Exclude<keyof T, 'id'>;
112
50
  /**
113
- * Extracts the record type from a schema collection.
51
+ * Computes the insert input type by making specified fields optional.
52
+ * Used to support omitting server-generated fields (created, updated) during insertion.
53
+ *
54
+ * IMPORTANT: The 'id' field can NEVER be omitted as TanStack DB requires it for record tracking.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * type BookInsert = ComputeInsertType<Books, ['created', 'updated']>
59
+ * // Result: Omit<Books, 'created' | 'updated'> & Partial<Pick<Books, 'created' | 'updated'>>
60
+ * ```
114
61
  * @internal
115
62
  */
116
- type ExtractRecordType<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = Schema[CollectionName]['type'];
63
+ type ComputeInsertType<T extends object, OmitFields extends readonly OmittableFields<T>[]> = Omit<T, OmitFields[number]> & Partial<Pick<T, OmitFields[number]>>;
117
64
  /**
118
65
  * Extracts the relations object from a schema collection.
119
66
  * Returns never if the collection has no relations defined.
@@ -122,12 +69,6 @@ type ExtractRecordType<Schema extends SchemaDeclaration, CollectionName extends
122
69
  type ExtractRelations<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = Schema[CollectionName] extends {
123
70
  relations: infer R;
124
71
  } ? R : never;
125
- /**
126
- * Extracts expandable field names from a schema collection.
127
- * Returns the union of all relation field names that can be expanded.
128
- * @internal
129
- */
130
- type ExpandableFields<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = ExtractRelations<Schema, CollectionName> extends never ? never : keyof ExtractRelations<Schema, CollectionName> & string;
131
72
  /**
132
73
  * Parses comma-separated relation field names into a union type.
133
74
  * Recursively processes "field1,field2,field3" into "field1" | "field2" | "field3".
@@ -162,186 +103,366 @@ type WithExpand<Schema extends SchemaDeclaration, CollectionName extends keyof S
162
103
  * Used to unwrap optional relation types.
163
104
  *
164
105
  * @example
165
- * NonNullable<Customer | undefined> => Customer
106
+ * ExcludeUndefined<Customer | undefined> => Customer
166
107
  * @internal
167
108
  */
168
- type NonNullable<T> = T extends (infer U) | undefined ? U : T;
109
+ type ExcludeUndefined<T> = T extends (infer U) | undefined ? U : T;
169
110
  /**
170
- * Converts a schema relation type to its corresponding Collection type.
111
+ * Converts a schema relation type to its corresponding Collection constraint.
171
112
  * Handles both single relations (T) and array relations (T[]).
113
+ * Accepts collections with any insert type to support omitOnInsert configurations.
114
+ *
115
+ * Uses constraint (extends Collection<T, ...>) rather than exact type to allow
116
+ * collections with different TInput types (from omitOnInsert) to be compatible.
172
117
  *
173
118
  * @example
174
- * RelationAsCollection<Customer> => Collection<Customer>
175
- * RelationAsCollection<Customer[]> => Collection<Customer>
119
+ * RelationAsCollection<Customer> => Collection<Customer, string | number, any, any, any>
120
+ * RelationAsCollection<Customer[]> => Collection<Customer, string | number, any, any, any>
176
121
  * @internal
177
122
  */
178
- type RelationAsCollection<T> = T extends Array<infer U> ? U extends object ? Collection<U> : Collection<object> : T extends object ? Collection<T> : Collection<object>;
123
+ type RelationAsCollection<T> = T extends Array<infer U> ? U extends object ? Collection<U, string | number, any, any, any> : Collection<object, string | number, any, any, any> : T extends object ? Collection<T, string | number, any, any, any> : Collection<object, string | number, any, any, any>;
179
124
  /**
180
- * Configuration for relations - maps field names to their TanStack DB collections.
181
- * Used to provide pre-configured collections for manual joins.
125
+ * Configuration for per-collection expand - maps relation field names to their target collections.
126
+ * Relations configured here are automatically expanded on every fetch and auto-upserted into target collections.
182
127
  *
183
128
  * @example
184
129
  * ```ts
185
- * const config: RelationsConfig<Schema, 'jobs'> = {
186
- * customer: customersCollection,
187
- * address: addressesCollection
188
- * };
130
+ * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors');
131
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
132
+ * expand: {
133
+ * author: authorsCollection // Always expand 'author', upsert into authorsCollection
134
+ * }
135
+ * });
189
136
  * ```
190
137
  */
191
- type RelationsConfig<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = ExtractRelations<Schema, CollectionName> extends never ? Record<string, never> : Partial<{
192
- [K in keyof ExtractRelations<Schema, CollectionName>]: RelationAsCollection<NonNullable<ExtractRelations<Schema, CollectionName>[K]>>;
138
+ type ExpandConfig<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = ExtractRelations<Schema, CollectionName> extends never ? Record<string, never> : Partial<{
139
+ [K in keyof ExtractRelations<Schema, CollectionName>]: RelationAsCollection<ExcludeUndefined<ExtractRelations<Schema, CollectionName>[K]>>;
193
140
  }>;
194
141
  /**
195
- * Options for creating a collection with optional expand and relations.
142
+ * Options for creating a collection.
196
143
  */
197
- interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionName extends keyof Schema, Expand extends string | undefined = undefined> {
144
+ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> {
145
+ /**
146
+ * Configure relations to automatically expand on every fetch.
147
+ * Maps relation field names to their target collections for auto-upsert.
148
+ *
149
+ * Expanded records are automatically inserted into their target collections.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors');
154
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
155
+ * expand: {
156
+ * author: authorsCollection // Always expand, auto-upsert into authorsCollection
157
+ * }
158
+ * });
159
+ *
160
+ * // Expand is automatic - no .expand() call needed
161
+ * const { data } = useLiveQuery((q) => q.from({ books: booksCollection }));
162
+ * // data[0].expand.author is typed and populated
163
+ * ```
164
+ */
165
+ expand?: ExpandConfig<Schema, CollectionName>;
166
+ /**
167
+ * Fields that can be omitted during insert operations.
168
+ * Useful for server-generated fields like 'created', 'updated'.
169
+ *
170
+ * When specified, the insert() method will accept records without these fields,
171
+ * and the omitted fields become optional in the insert input type.
172
+ *
173
+ * **Type safety:** Only valid field names from the record type are accepted.
174
+ * **IMPORTANT:** The 'id' field can NEVER be omitted as TanStack DB requires it.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * // Allow inserting without created, updated (server-generated timestamps)
179
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
180
+ * omitOnInsert: ['created', 'updated'] as const
181
+ * });
182
+ *
183
+ * // Now insert() accepts records without those fields
184
+ * booksCollection.insert({
185
+ * id: newRecordId(), // id is always required
186
+ * title: 'New Book',
187
+ * isbn: '1234567890',
188
+ * genre: 'Fiction',
189
+ * author: authorId
190
+ * // created, updated are optional
191
+ * });
192
+ * ```
193
+ */
194
+ omitOnInsert?: readonly OmittableFields<ExtractRecordType<Schema, CollectionName>>[];
198
195
  /**
199
- * Pre-configured relation collections for manual TanStack DB joins.
196
+ * Custom handler for insert mutations.
197
+ *
198
+ * **Default behavior (not provided):** Automatically creates records in PocketBase,
199
+ * excluding auto-generated fields (id, created, updated, collectionId, collectionName).
200
+ *
201
+ * **Custom handler:** Provide your own handler to customize insert behavior.
202
+ *
203
+ * **Disable:** Set to `false` to disable insert mutations entirely (will throw error if insert is called).
200
204
  *
201
205
  * @example
202
206
  * ```ts
203
- * const jobsCollection = factory.create('jobs', {
204
- * relations: {
205
- * customer: customersCollection,
206
- * address: addressesCollection
207
+ * // Use default automatic handler (recommended)
208
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
209
+ *
210
+ * // Custom handler
211
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
212
+ * onInsert: async ({ transaction }) => {
213
+ * for (const mutation of transaction.mutations) {
214
+ * await customInsertLogic(mutation.modified);
215
+ * }
216
+ * await queryClient.invalidateQueries({ queryKey: ['books'] });
207
217
  * }
208
218
  * });
219
+ *
220
+ * // Disable inserts (read-only collection)
221
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
222
+ * onInsert: false
223
+ * });
209
224
  * ```
210
225
  */
211
- relations?: RelationsConfig<Schema, CollectionName>;
226
+ onInsert?: InsertMutationFn<ExtractRecordType<Schema, CollectionName>> | false;
212
227
  /**
213
- * Relation fields to auto-expand from PocketBase.
214
- * Can be a single field or comma-separated list.
215
- * For best type inference, use `as const` when providing comma-separated strings.
228
+ * Custom handler for update mutations.
229
+ *
230
+ * **Default behavior (not provided):** Automatically updates records in PocketBase
231
+ * with the changed fields.
232
+ *
233
+ * **Custom handler:** Provide your own handler to customize update behavior.
234
+ *
235
+ * **Disable:** Set to `false` to disable update mutations entirely (will throw error if update is called).
216
236
  *
217
237
  * @example
218
238
  * ```ts
219
- * // Single relation
220
- * const jobsCollection = factory.create('jobs', {
221
- * expand: 'customer' // Type inference works automatically
239
+ * // Use default automatic handler (recommended)
240
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
241
+ *
242
+ * // Custom handler
243
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
244
+ * onUpdate: async ({ transaction }) => {
245
+ * for (const mutation of transaction.mutations) {
246
+ * await customUpdateLogic(mutation.original.id, mutation.changes);
247
+ * }
248
+ * await queryClient.invalidateQueries({ queryKey: ['books'] });
249
+ * }
250
+ * });
251
+ *
252
+ * // Disable updates (read-only collection)
253
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
254
+ * onUpdate: false
255
+ * });
256
+ * ```
257
+ */
258
+ onUpdate?: UpdateMutationFn<ExtractRecordType<Schema, CollectionName>> | false;
259
+ /**
260
+ * Custom handler for delete mutations.
261
+ *
262
+ * **Default behavior (not provided):** Automatically deletes records from PocketBase.
263
+ *
264
+ * **Custom handler:** Provide your own handler to customize delete behavior.
265
+ *
266
+ * **Disable:** Set to `false` to disable delete mutations entirely (will throw error if delete is called).
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * // Use default automatic handler (recommended)
271
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
272
+ *
273
+ * // Custom handler
274
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
275
+ * onDelete: async ({ transaction }) => {
276
+ * for (const mutation of transaction.mutations) {
277
+ * await customDeleteLogic(mutation.original.id);
278
+ * }
279
+ * await queryClient.invalidateQueries({ queryKey: ['books'] });
280
+ * }
222
281
  * });
223
282
  *
224
- * // Multiple relations with type inference
225
- * const jobsCollection = factory.create('jobs', {
226
- * expand: 'customer,address' as const // `as const` gives proper typing
283
+ * // Disable deletes (read-only collection)
284
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
285
+ * onDelete: false
227
286
  * });
228
287
  * ```
229
288
  */
230
- expand?: Expand;
289
+ onDelete?: DeleteMutationFn<ExtractRecordType<Schema, CollectionName>> | false;
231
290
  /**
232
- * Whether to automatically sync (fetch) data when the collection is created.
233
- * Default: false (lazy - sync starts after first query becomes active).
291
+ * Sync mode for the collection. Controls when and how data is fetched from PocketBase.
292
+ *
293
+ * - `'eager'` (default): Fetches all data immediately when collection is created.
294
+ * Queries are evaluated client-side against the cached data. Fast for small datasets
295
+ * but loads entire collection into memory. Matches TanStack DB default.
296
+ *
297
+ * - `'on-demand'`: Fetches data only when queries execute. Each query with different
298
+ * filters/sorting triggers a new fetch from PocketBase. Enables true server-side
299
+ * filtering and is better for large datasets.
300
+ *
301
+ * @default 'eager'
234
302
  *
235
303
  * @example
236
304
  * ```ts
237
- * // Lazy loading (default) - sync starts after query
238
- * const jobsCollection = factory.create('jobs');
239
- * // or explicitly:
240
- * const jobsCollection = factory.create('jobs', { startSync: false });
241
- *
242
- * // Eager loading - sync starts immediately on creation
243
- * const jobsCollection = factory.create('jobs', {
244
- * startSync: true
305
+ * // Default: eager mode - client-side filtering
306
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
307
+ *
308
+ * // On-demand mode - server-side filtering
309
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
310
+ * syncMode: 'on-demand'
245
311
  * });
246
312
  * ```
247
313
  */
248
- startSync?: boolean;
314
+ syncMode?: 'eager' | 'on-demand';
249
315
  }
250
316
 
251
317
  /**
252
- * Map of collection names to TanStack DB Collection instances.
253
- * Keys are user-defined strings, values are Collection instances.
318
+ * Compute the record type with expand property when expand option is configured.
319
+ * @internal
320
+ */
321
+ type WithExpandFromConfig<Schema extends SchemaDeclaration, C extends keyof Schema, Opts> = Opts extends {
322
+ expand: infer E;
323
+ } ? ExtractRecordType<Schema, C> & {
324
+ expand?: {
325
+ [K in keyof E]: K extends keyof ExtractRelations<Schema, C> ? ExtractRelations<Schema, C>[K] extends Array<infer U> ? U[] : ExtractRelations<Schema, C>[K] : never;
326
+ };
327
+ } : ExtractRecordType<Schema, C>;
328
+ /**
329
+ * Subscription helpers added to collection instances.
330
+ * @internal
331
+ */
332
+ interface CollectionSubscriptionHelpers {
333
+ /** The PocketBase collection name */
334
+ collectionName: string;
335
+ /** Wait for subscription to be established (useful in tests) */
336
+ waitForSubscription: (timeout?: number) => Promise<void>;
337
+ /** Check if collection has an active subscription */
338
+ isSubscribed: () => boolean;
339
+ }
340
+ /**
341
+ * Inferred collection type from config options.
342
+ * @internal
343
+ */
344
+ type InferCollectionType<Schema extends SchemaDeclaration, C extends keyof Schema, Opts extends CreateCollectionOptions<Schema, C>> = Collection$1<WithExpandFromConfig<Schema, C, Opts>, string | number, QueryCollectionUtils<WithExpandFromConfig<Schema, C, Opts>, string | number, WithExpandFromConfig<Schema, C, Opts>>, never, Opts extends {
345
+ omitOnInsert: infer O extends readonly OmittableFields<ExtractRecordType<Schema, C>>[];
346
+ } ? ComputeInsertType<ExtractRecordType<Schema, C>, O> : ExtractRecordType<Schema, C>> & CollectionSubscriptionHelpers;
347
+ /**
348
+ * Creates a type-safe TanStack DB collection backed by PocketBase.
349
+ * Use this when you need fine-grained control or need to create collections with dependencies.
350
+ *
351
+ * @param pb - PocketBase client instance
352
+ * @param queryClient - TanStack Query client
353
+ * @returns A curried function that takes collection name and options
254
354
  *
255
355
  * @example
356
+ * Basic usage:
256
357
  * ```ts
257
- * const stores = {
258
- * jobs: jobsCollection,
259
- * customers: customersCollection,
260
- * addresses: addressesCollection
261
- * };
358
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {});
359
+ *
360
+ * // Use directly
361
+ * const books = await booksCollection.getFullList();
362
+ * ```
363
+ *
364
+ * @example
365
+ * With auto-expand relations:
366
+ * ```ts
367
+ * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors', {});
368
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
369
+ * expand: {
370
+ * author: authorsCollection // Always expand, auto-upsert into authorsCollection
371
+ * }
372
+ * });
373
+ *
374
+ * // Expand is automatic - no .expand() call needed
375
+ * const { data } = useLiveQuery((q) => q.from({ books: booksCollection }));
376
+ * // data[0].expand.author is typed and populated
262
377
  * ```
263
378
  */
264
- type CollectionsMap = Record<string, Collection<any>>;
379
+ declare function createCollection<Schema extends SchemaDeclaration>(pb: PocketBase, queryClient: QueryClient): <C extends keyof Schema & string, Opts extends CreateCollectionOptions<Schema, C> = CreateCollectionOptions<Schema, C>>(collectionName: C, options?: Opts) => InferCollectionType<Schema, C, Opts>;
380
+
381
+ /**
382
+ * UseStore hook type for variadic collection access.
383
+ * @internal
384
+ */
385
+ type UseStoreFn<CollectionsMap> = <K extends (keyof CollectionsMap)[]>(...keys: K) => {
386
+ [I in keyof K]: K[I] extends keyof CollectionsMap ? CollectionsMap[K[I]] : never;
387
+ };
265
388
  /**
266
- * Props for the CollectionsProvider component.
389
+ * Return type for createReactProvider function.
267
390
  */
268
- interface CollectionsProviderProps {
269
- /** Map of collection name to Collection instance */
270
- collections: CollectionsMap;
271
- /** React children to render */
272
- children: ReactNode;
391
+ interface ReactProviderResult<CollectionsMap> {
392
+ /**
393
+ * React Context Provider component.
394
+ * Wrap your app with this provider to make collections available to useStore.
395
+ */
396
+ Provider: React.FC<{
397
+ children: ReactNode;
398
+ }>;
399
+ /**
400
+ * Hook to access collections from the provider.
401
+ * Uses variadic arguments for clean syntax with automatic type inference.
402
+ *
403
+ * @example
404
+ * ```tsx
405
+ * // Single collection
406
+ * const [books] = useStore('books');
407
+ *
408
+ * // Multiple collections (no 'as const' needed!)
409
+ * const [books, authors] = useStore('books', 'authors');
410
+ * ```
411
+ */
412
+ useStore: UseStoreFn<CollectionsMap>;
273
413
  }
274
414
  /**
275
- * Provider component that makes collections available to all child components.
276
- * Wrap your app with this provider to use the useStore and useStores hooks.
415
+ * Creates a React Provider and useStore hook from a collections map.
416
+ *
417
+ * @param collections - Map of collection keys to Collection instances
418
+ * @returns Object containing Provider component and useStore hook
277
419
  *
278
420
  * @example
421
+ * Basic usage:
279
422
  * ```tsx
280
- * const factory = new CollectionFactory<Schema>(pb, queryClient);
423
+ * import { createCollection, createReactProvider } from 'pbtsdb';
424
+ * import { useLiveQuery } from '@tanstack/react-db';
425
+ *
426
+ * // Step 1: Create collections
427
+ * const c = createCollection<Schema>(pb, queryClient);
281
428
  * const collections = {
282
- * jobs: factory.create('jobs'),
283
- * customers: factory.create('customers'),
284
- * addresses: factory.create('addresses')
429
+ * books: c('books', {}),
430
+ * authors: c('authors', {}),
285
431
  * };
286
432
  *
433
+ * // Step 2: Wrap for React
434
+ * const { Provider, useStore } = createReactProvider(collections);
435
+ *
436
+ * // Step 3: Wrap your app
287
437
  * function App() {
288
438
  * return (
289
- * <CollectionsProvider collections={collections}>
290
- * <YourApp />
291
- * </CollectionsProvider>
439
+ * <QueryClientProvider client={queryClient}>
440
+ * <Provider>
441
+ * <BooksList />
442
+ * </Provider>
443
+ * </QueryClientProvider>
292
444
  * );
293
445
  * }
294
- * ```
295
- */
296
- declare function CollectionsProvider({ collections, children }: CollectionsProviderProps): react_jsx_runtime.JSX.Element;
297
- /**
298
- * Hook to access a single collection from the provider.
299
- * Returns the Collection instance for the specified key.
300
446
  *
301
- * @template T - The record type for the collection
302
- * @param key - The collection key as defined in the provider
303
- * @returns The Collection instance
304
- * @throws Error if used outside of CollectionsProvider or if key doesn't exist
305
- *
306
- * @example
307
- * ```tsx
308
- * function JobsList() {
309
- * const jobsCollection = useStore<JobsRecord>('jobs');
310
- *
311
- * const { data } = useLiveQuery((q) =>
312
- * q.from({ jobs: jobsCollection })
313
- * );
314
- *
315
- * return (
316
- * <ul>
317
- * {data?.map(job => <li key={job.id}>{job.name}</li>)}
318
- * </ul>
319
- * );
447
+ * // Step 4: Use in components
448
+ * function BooksList() {
449
+ * const [books] = useStore('books');
450
+ * const { data } = useLiveQuery((q) => q.from({ books }));
451
+ * return <div>{data?.map(book => <p key={book.id}>{book.title}</p>)}</div>;
320
452
  * }
321
453
  * ```
322
- */
323
- declare function useStore<T extends object = object>(key: string): Collection<T>;
324
- /**
325
- * Hook to access multiple collections from the provider.
326
- * Returns an array of Collection instances matching the order of the keys array.
327
- *
328
- * @template T - Tuple type of record types for each collection
329
- * @param keys - Array of collection keys as defined in the provider
330
- * @returns Array of Collection instances in the same order as keys
331
- * @throws Error if used outside of CollectionsProvider or if any key doesn't exist
332
454
  *
333
455
  * @example
456
+ * Variadic useStore pattern:
334
457
  * ```tsx
335
- * function JobsWithCustomers() {
336
- * const [jobsCollection, customersCollection] = useStores<
337
- * [JobsRecord, CustomersRecord]
338
- * >(['jobs', 'customers']);
458
+ * function BooksWithAuthors() {
459
+ * const [books, authors] = useStore('books', 'authors');
339
460
  *
340
461
  * const { data } = useLiveQuery((q) =>
341
- * q.from({ job: jobsCollection })
462
+ * q.from({ book: books })
342
463
  * .join(
343
- * { customer: customersCollection },
344
- * ({ job, customer }) => eq(job.customer, customer.id),
464
+ * { author: authors },
465
+ * ({ book, author }) => eq(book.author, author.id),
345
466
  * 'left'
346
467
  * )
347
468
  * );
@@ -349,201 +470,37 @@ declare function useStore<T extends object = object>(key: string): Collection<T>
349
470
  * return <div>...</div>;
350
471
  * }
351
472
  * ```
352
- */
353
- declare function useStores<T extends readonly object[]>(keys: readonly string[]): {
354
- [K in keyof T]: Collection<T[K]>;
355
- };
356
-
357
- /**
358
- * Factory for creating type-safe TanStack DB collections backed by PocketBase.
359
- * Integrates real-time subscriptions with automatic synchronization.
360
- */
361
- declare class CollectionFactory<Schema extends SchemaDeclaration, TMaxDepth extends 0 | 1 | 2 | 3 | 4 | 5 | 6 = 2> {
362
- pocketbase: PocketBase;
363
- queryClient: QueryClient;
364
- private subscriptionManager;
365
- constructor(pocketbase: PocketBase, queryClient: QueryClient);
366
- /**
367
- * Setup automatic subscription lifecycle management.
368
- * Hooks into TanStack DB's subscriber events to manage real-time subscriptions.
369
- */
370
- private setupSubscriptionLifecycle;
371
- /**
372
- * Create a TanStack DB collection from a PocketBase collection.
373
- *
374
- * Collections are lazy by default - they don't fetch data or subscribe until queried.
375
- * Real-time subscriptions automatically start when the first query becomes active
376
- * and stop when the last query unmounts (with a cleanup delay to prevent thrashing).
377
- *
378
- * @param collection - The name of the collection
379
- * @param options - Optional configuration including relations and expand
380
- *
381
- * @example
382
- * Basic usage with automatic lifecycle management:
383
- * ```ts
384
- * const jobsCollection = factory.create('jobs');
385
- *
386
- * // In your component - subscription starts automatically
387
- * const { data } = useLiveQuery((q) =>
388
- * q.from({ jobs: jobsCollection })
389
- * );
390
- * // Subscription stops automatically when component unmounts
391
- * ```
392
- *
393
- * @example
394
- * With query operators (filters, sorting):
395
- * ```ts
396
- * const jobsCollection = factory.create('jobs');
397
- *
398
- * // In your component:
399
- * const { data } = useLiveQuery((q) =>
400
- * q.from({ jobs: jobsCollection })
401
- * .where(({ jobs }) => and(
402
- * eq(jobs.status, 'ACTIVE'),
403
- * gt(jobs.created, new Date('2025-01-01'))
404
- * ))
405
- * .orderBy(({ jobs }) => jobs.created, 'desc')
406
- * );
407
- * ```
408
- *
409
- * @example
410
- * With relation expansion:
411
- * ```ts
412
- * const jobsCollection = factory.create('jobs', {
413
- * expand: 'customer,location'
414
- * });
415
- *
416
- * // Expanded relations available in record.expand
417
- * ```
418
- *
419
- * @example
420
- * With relations (for manual joins):
421
- * ```ts
422
- * const customersCollection = factory.create('customers');
423
- * const jobsCollection = factory.create('jobs', {
424
- * relations: { customer: customersCollection }
425
- * });
426
- *
427
- * // In your component, manually build joins:
428
- * const { data } = useLiveQuery((q) =>
429
- * q.from({ job: jobsCollection })
430
- * .join(
431
- * { customer: customersCollection },
432
- * ({ job, customer }) => eq(job.customer, customer.id),
433
- * "left"
434
- * )
435
- * .select(({ job, customer }) => ({
436
- * ...job,
437
- * expand: {
438
- * customer: customer ? { ...customer } : undefined
439
- * }
440
- * }))
441
- * );
442
- * ```
443
- *
444
- * @example
445
- * Manual subscription control (advanced):
446
- * ```ts
447
- * const jobsCollection = factory.create('jobs');
448
- *
449
- * // Manually subscribe to specific record (bypasses automatic lifecycle)
450
- * await jobsCollection.subscribe('record_id_123');
451
- *
452
- * // Check subscription status
453
- * const isSubbed = jobsCollection.isSubscribed('record_id_123');
454
- *
455
- * // Manually unsubscribe
456
- * jobsCollection.unsubscribe('record_id_123');
457
- * ```
458
- */
459
- create<C extends keyof Schema & string, E extends string | undefined = undefined>(collection: C, options?: CreateCollectionOptions<Schema, C, E>): Collection<WithExpand<Schema, C, E>> & SubscribableCollection<WithExpand<Schema, C, E>> & JoinHelper<Schema, C, WithExpand<Schema, C, E>>;
460
- }
461
-
462
- declare const SUBSCRIPTION_CONFIG: {
463
- readonly MAX_RECONNECT_ATTEMPTS: 5;
464
- readonly BASE_RECONNECT_DELAY_MS: 1000;
465
- readonly DEFAULT_WAIT_TIMEOUT_MS: 5000;
466
- readonly CLEANUP_DELAY_MS: 5000;
467
- };
468
- /**
469
- * Manages real-time subscriptions to PocketBase collections.
470
- * Handles subscription lifecycle, reconnection with exponential backoff,
471
- * and automatic synchronization with TanStack DB collections.
472
473
  *
473
- * Subscriptions are automatically managed based on TanStack DB's subscriber count:
474
- * - Start subscribing when first query becomes active
475
- * - Stop subscribing when last query becomes inactive (with cleanup delay)
474
+ * @example
475
+ * With auto-expand collections:
476
+ * ```tsx
477
+ * const c = createCollection<Schema>(pb, queryClient);
478
+ * const authors = c('authors', {});
479
+ * const books = c('books', {
480
+ * expand: {
481
+ * author: authors
482
+ * }
483
+ * });
484
+ *
485
+ * const { Provider, useStore } = createReactProvider({ authors, books });
486
+ *
487
+ * function BooksWithExpandedAuthors() {
488
+ * const [books] = useStore('books');
489
+ * const { data } = useLiveQuery((q) => q.from({ books }));
490
+ *
491
+ * return (
492
+ * <ul>
493
+ * {data?.map(book => (
494
+ * <li key={book.id}>
495
+ * {book.title} by {book.expand?.author?.name}
496
+ * </li>
497
+ * ))}
498
+ * </ul>
499
+ * );
500
+ * }
501
+ * ```
476
502
  */
477
- declare class SubscriptionManager {
478
- private pocketbase;
479
- private subscriptions;
480
- private subscriptionPromises;
481
- private cleanupTimers;
482
- private subscriberCounts;
483
- constructor(pocketbase: PocketBase);
484
- private setupSubscription;
485
- private handleReconnection;
486
- /**
487
- * Subscribe to real-time updates for a collection.
488
- * Returns a promise that resolves when the subscription is fully established.
489
- *
490
- * @param collectionName - The PocketBase collection name
491
- * @param collection - The TanStack DB collection to sync with
492
- * @param recordId - Optional: Subscribe to specific record, or omit for collection-wide updates
493
- */
494
- subscribe<T extends object>(collectionName: string, collection: Collection<T>, recordId?: string): Promise<void>;
495
- /**
496
- * Unsubscribe from real-time updates.
497
- *
498
- * @param collectionName - The PocketBase collection name
499
- * @param recordId - Optional: Unsubscribe from specific record, or omit for collection-wide
500
- */
501
- unsubscribe(collectionName: string, recordId?: string): void;
502
- /**
503
- * Unsubscribe from all subscriptions for a collection.
504
- *
505
- * @param collectionName - The PocketBase collection name
506
- */
507
- unsubscribeAll(collectionName: string): void;
508
- /**
509
- * Check if subscribed to a collection.
510
- *
511
- * @param collectionName - The PocketBase collection name
512
- * @param recordId - Optional: Check specific record subscription, or omit for collection-wide
513
- */
514
- isSubscribed(collectionName: string, recordId?: string): boolean;
515
- /**
516
- * Wait for a subscription to be fully established (useful for testing).
517
- *
518
- * @param collectionName - The collection name
519
- * @param recordId - Optional specific record ID
520
- * @param timeoutMs - Timeout in milliseconds
521
- */
522
- waitForSubscription(collectionName: string, recordId?: string, timeoutMs?: number): Promise<void>;
523
- /**
524
- * Track subscriber addition for a collection.
525
- * Automatically subscribes when first subscriber is added.
526
- *
527
- * @param collectionName - The PocketBase collection name
528
- * @param collection - The TanStack DB collection to sync with
529
- */
530
- addSubscriber<T extends object>(collectionName: string, collection: Collection<T>): Promise<void>;
531
- /**
532
- * Track subscriber removal for a collection.
533
- * Automatically unsubscribes (with delay) when last subscriber is removed.
534
- *
535
- * @param collectionName - The PocketBase collection name
536
- */
537
- removeSubscriber(collectionName: string): void;
538
- /**
539
- * Get the current subscriber count for a collection.
540
- * Useful for debugging and testing.
541
- *
542
- * @param collectionName - The PocketBase collection name
543
- * @returns Current subscriber count
544
- */
545
- getSubscriberCount(collectionName: string): number;
546
- }
503
+ declare function createReactProvider<CollectionsMap extends Record<string, any>>(collections: CollectionsMap): ReactProviderResult<CollectionsMap>;
547
504
 
548
505
  /**
549
506
  * Logger interface for subscription events and internal operations.
@@ -556,6 +513,12 @@ interface Logger {
556
513
  * @param context - Optional context object with additional information
557
514
  */
558
515
  debug: (msg: string, context?: object) => void;
516
+ /**
517
+ * Log info-level messages.
518
+ * @param msg - The message to log
519
+ * @param context - Optional context object with additional information
520
+ */
521
+ info: (msg: string, context?: object) => void;
559
522
  /**
560
523
  * Log warning-level messages.
561
524
  * @param msg - The message to log
@@ -577,19 +540,14 @@ interface Logger {
577
540
  *
578
541
  * @example
579
542
  * ```ts
580
- * import { setLogger } from 'pocketbase-tanstack-db';
543
+ * import { setLogger } from 'pbtsdb';
581
544
  *
582
- * // Example: Integration with a custom logging service
545
+ * // Integration with a custom logging service
583
546
  * setLogger({
584
- * debug: (msg, context) => {
585
- * myLogger.debug(msg, context);
586
- * },
587
- * warn: (msg, context) => {
588
- * myLogger.warn(msg, context);
589
- * },
547
+ * debug: (msg, context) => myLogger.debug(msg, context),
548
+ * warn: (msg, context) => myLogger.warn(msg, context),
590
549
  * error: (msg, context) => {
591
550
  * myLogger.error(msg, context);
592
- * // Also send to error tracking service
593
551
  * Sentry.captureMessage(msg, { level: 'error', extra: context });
594
552
  * },
595
553
  * });
@@ -597,7 +555,7 @@ interface Logger {
597
555
  *
598
556
  * @example
599
557
  * ```ts
600
- * // Example: Disable all logging
558
+ * // Disable all logging
601
559
  * setLogger({
602
560
  * debug: () => {},
603
561
  * warn: () => {},
@@ -611,4 +569,19 @@ declare function setLogger(customLogger: Logger): void;
611
569
  */
612
570
  declare function resetLogger(): void;
613
571
 
614
- export { CollectionFactory, type CollectionsMap, CollectionsProvider, type CollectionsProviderProps, type CreateCollectionOptions, type ExpandableFields, type ExtractRecordType, type ExtractRelations, type JoinHelper, type Logger, type NonNullable, type ParseExpandFields, type RealtimeEvent, type RelationAsCollection, type RelationsConfig, SUBSCRIPTION_CONFIG, type SchemaDeclaration, type SubscribableCollection, SubscriptionManager, type SubscriptionState, type WithExpand, resetLogger, setLogger, useStore, useStores };
572
+ /**
573
+ * Generates a new PocketBase-compatible record ID.
574
+ * Returns a 15-character alphanumeric string (lowercase letters and numbers).
575
+ *
576
+ * PocketBase uses 15-character IDs for records, formatted as lowercase alphanumeric.
577
+ *
578
+ * @returns A 15-character alphanumeric string suitable for use as a PocketBase record ID
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * const id = newRecordId(); // "a1b2c3d4e5f6g7h"
583
+ * ```
584
+ */
585
+ declare function newRecordId(): string;
586
+
587
+ export { type CreateCollectionOptions, type ExcludeUndefined, type ExtractRecordType, type ExtractRelations, type Logger, type OmittableFields, type ParseExpandFields, type ReactProviderResult, type RelationAsCollection, type SchemaDeclaration, type WithExpand, createCollection, createReactProvider, newRecordId, resetLogger, setLogger };