pbtsdb 0.0.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/dist/index.js ADDED
@@ -0,0 +1,545 @@
1
+ import { createCollection } from '@tanstack/db';
2
+ import { queryCollectionOptions } from '@tanstack/query-db-collection';
3
+ import { createContext, useContext } from 'react';
4
+ import { jsx } from 'react/jsx-runtime';
5
+
6
+ // src/collection.ts
7
+
8
+ // src/logger.ts
9
+ var defaultLogger = {
10
+ debug: (msg, context) => {
11
+ if (process.env.NODE_ENV === "development") {
12
+ console.log(`[pbtsdb] ${msg}`, context || "");
13
+ }
14
+ },
15
+ warn: (msg, context) => {
16
+ console.warn(`[pbtsdb] ${msg}`, context || "");
17
+ },
18
+ error: (msg, context) => {
19
+ console.error(`[pbtsdb] ${msg}`, context || "");
20
+ }
21
+ };
22
+ var currentLogger = defaultLogger;
23
+ var logger = {
24
+ debug: (msg, context) => currentLogger.debug(msg, context),
25
+ warn: (msg, context) => currentLogger.warn(msg, context),
26
+ error: (msg, context) => currentLogger.error(msg, context)
27
+ };
28
+ function setLogger(customLogger) {
29
+ currentLogger = customLogger;
30
+ }
31
+ function resetLogger() {
32
+ currentLogger = defaultLogger;
33
+ }
34
+
35
+ // src/subscription-manager.ts
36
+ var SUBSCRIPTION_CONFIG = {
37
+ MAX_RECONNECT_ATTEMPTS: 5,
38
+ BASE_RECONNECT_DELAY_MS: 1e3,
39
+ DEFAULT_WAIT_TIMEOUT_MS: 5e3,
40
+ CLEANUP_DELAY_MS: 5e3
41
+ };
42
+ function hasId(record) {
43
+ return typeof record === "object" && record !== null && "id" in record && typeof record.id === "string";
44
+ }
45
+ function createPendingSubscriptionState(recordId) {
46
+ return {
47
+ unsubscribe: async () => {
48
+ },
49
+ recordId,
50
+ reconnectAttempts: 0,
51
+ isReconnecting: false
52
+ };
53
+ }
54
+ function getSubscriptionKey(recordId) {
55
+ return recordId || "*";
56
+ }
57
+ var SubscriptionManager = class {
58
+ constructor(pocketbase) {
59
+ this.pocketbase = pocketbase;
60
+ this.subscriptions = /* @__PURE__ */ new Map();
61
+ this.subscriptionPromises = /* @__PURE__ */ new Map();
62
+ this.cleanupTimers = /* @__PURE__ */ new Map();
63
+ this.subscriberCounts = /* @__PURE__ */ new Map();
64
+ }
65
+ // ============================================================================
66
+ // Internal Helpers
67
+ // ============================================================================
68
+ async setupSubscription(collectionName, collection, recordId) {
69
+ const subscriptionKey = getSubscriptionKey(recordId);
70
+ const eventHandler = (event) => {
71
+ collection.utils.writeBatch(() => {
72
+ switch (event.action) {
73
+ case "create":
74
+ collection.utils.writeInsert(event.record);
75
+ break;
76
+ case "update":
77
+ collection.utils.writeUpdate(event.record);
78
+ break;
79
+ case "delete":
80
+ if (!hasId(event.record)) {
81
+ logger.error("Delete event record missing id field", {
82
+ collectionName,
83
+ record: event.record
84
+ });
85
+ return;
86
+ }
87
+ collection.utils.writeDelete(event.record.id);
88
+ break;
89
+ }
90
+ });
91
+ };
92
+ const unsubscribe = await this.pocketbase.collection(collectionName).subscribe(subscriptionKey, eventHandler);
93
+ logger.debug("Subscription established", { collectionName, subscriptionKey });
94
+ return unsubscribe;
95
+ }
96
+ async handleReconnection(collectionName, collection, recordId) {
97
+ const collectionSubs = this.subscriptions.get(collectionName);
98
+ if (!collectionSubs) return;
99
+ const subscriptionKey = getSubscriptionKey(recordId);
100
+ const state = collectionSubs.get(subscriptionKey);
101
+ if (!state || state.isReconnecting) return;
102
+ state.isReconnecting = true;
103
+ logger.warn("Starting reconnection attempts", { collectionName, subscriptionKey });
104
+ while (state.reconnectAttempts < SUBSCRIPTION_CONFIG.MAX_RECONNECT_ATTEMPTS) {
105
+ const delay = SUBSCRIPTION_CONFIG.BASE_RECONNECT_DELAY_MS * Math.pow(2, state.reconnectAttempts);
106
+ logger.debug(`Reconnection attempt ${state.reconnectAttempts + 1}, waiting ${delay}ms`, {
107
+ collectionName,
108
+ subscriptionKey
109
+ });
110
+ await new Promise((resolve) => setTimeout(resolve, delay));
111
+ try {
112
+ const newUnsubscribe = await this.setupSubscription(
113
+ collectionName,
114
+ collection,
115
+ recordId
116
+ );
117
+ state.unsubscribe = newUnsubscribe;
118
+ state.reconnectAttempts = 0;
119
+ state.isReconnecting = false;
120
+ logger.debug("Reconnection successful", { collectionName, subscriptionKey });
121
+ return;
122
+ } catch (error) {
123
+ state.reconnectAttempts++;
124
+ logger.warn(`Reconnection attempt ${state.reconnectAttempts} failed`, {
125
+ collectionName,
126
+ subscriptionKey,
127
+ error
128
+ });
129
+ }
130
+ }
131
+ logger.error("Max reconnection attempts reached", {
132
+ collectionName,
133
+ subscriptionKey,
134
+ attempts: state.reconnectAttempts
135
+ });
136
+ state.isReconnecting = false;
137
+ collectionSubs.delete(subscriptionKey);
138
+ }
139
+ // ============================================================================
140
+ // Core Subscription Methods
141
+ // ============================================================================
142
+ /**
143
+ * Subscribe to real-time updates for a collection.
144
+ * Returns a promise that resolves when the subscription is fully established.
145
+ *
146
+ * @param collectionName - The PocketBase collection name
147
+ * @param collection - The TanStack DB collection to sync with
148
+ * @param recordId - Optional: Subscribe to specific record, or omit for collection-wide updates
149
+ */
150
+ async subscribe(collectionName, collection, recordId) {
151
+ if (!this.subscriptions.has(collectionName)) {
152
+ this.subscriptions.set(collectionName, /* @__PURE__ */ new Map());
153
+ }
154
+ if (!this.subscriptionPromises.has(collectionName)) {
155
+ this.subscriptionPromises.set(collectionName, /* @__PURE__ */ new Map());
156
+ }
157
+ const collectionSubs = this.subscriptions.get(collectionName);
158
+ const collectionPromises = this.subscriptionPromises.get(collectionName);
159
+ const subscriptionKey = getSubscriptionKey(recordId);
160
+ if (collectionSubs.has(subscriptionKey)) {
161
+ logger.debug("Already subscribed, skipping", { collectionName, subscriptionKey });
162
+ return;
163
+ }
164
+ const existingPromise = collectionPromises.get(subscriptionKey);
165
+ if (existingPromise) {
166
+ logger.debug("Pending subscription found, waiting", { collectionName, subscriptionKey });
167
+ return existingPromise;
168
+ }
169
+ collectionSubs.set(subscriptionKey, createPendingSubscriptionState(recordId));
170
+ const subscriptionPromise = (async () => {
171
+ try {
172
+ const unsubscribe = await this.setupSubscription(collectionName, collection, recordId);
173
+ const state = collectionSubs.get(subscriptionKey);
174
+ if (state) {
175
+ state.unsubscribe = unsubscribe;
176
+ }
177
+ } catch (error) {
178
+ collectionSubs.delete(subscriptionKey);
179
+ collectionPromises.delete(subscriptionKey);
180
+ logger.error("Subscription failed", { collectionName, subscriptionKey, error });
181
+ await this.handleReconnection(collectionName, collection, recordId);
182
+ throw error;
183
+ }
184
+ })();
185
+ collectionPromises.set(subscriptionKey, subscriptionPromise);
186
+ try {
187
+ await subscriptionPromise;
188
+ } catch (_error) {
189
+ }
190
+ }
191
+ /**
192
+ * Unsubscribe from real-time updates.
193
+ *
194
+ * @param collectionName - The PocketBase collection name
195
+ * @param recordId - Optional: Unsubscribe from specific record, or omit for collection-wide
196
+ */
197
+ unsubscribe(collectionName, recordId) {
198
+ const collectionSubs = this.subscriptions.get(collectionName);
199
+ if (!collectionSubs) return;
200
+ const subscriptionKey = getSubscriptionKey(recordId);
201
+ const state = collectionSubs.get(subscriptionKey);
202
+ if (state) {
203
+ const unsubPromise = state.unsubscribe();
204
+ if (unsubPromise && typeof unsubPromise.catch === "function") {
205
+ unsubPromise.catch((error) => {
206
+ logger.debug("Unsubscribe failed (expected if connection closed)", {
207
+ collectionName,
208
+ subscriptionKey,
209
+ error
210
+ });
211
+ });
212
+ }
213
+ collectionSubs.delete(subscriptionKey);
214
+ logger.debug("Unsubscribed", { collectionName, subscriptionKey });
215
+ }
216
+ const collectionPromises = this.subscriptionPromises.get(collectionName);
217
+ if (collectionPromises) {
218
+ collectionPromises.delete(subscriptionKey);
219
+ if (collectionPromises.size === 0) {
220
+ this.subscriptionPromises.delete(collectionName);
221
+ }
222
+ }
223
+ if (collectionSubs.size === 0) {
224
+ this.subscriptions.delete(collectionName);
225
+ }
226
+ }
227
+ /**
228
+ * Unsubscribe from all subscriptions for a collection.
229
+ *
230
+ * @param collectionName - The PocketBase collection name
231
+ */
232
+ unsubscribeAll(collectionName) {
233
+ const collectionSubs = this.subscriptions.get(collectionName);
234
+ if (!collectionSubs) return;
235
+ logger.debug("Unsubscribing from all subscriptions", { collectionName, count: collectionSubs.size });
236
+ for (const state of collectionSubs.values()) {
237
+ const unsubPromise = state.unsubscribe();
238
+ if (unsubPromise && typeof unsubPromise.catch === "function") {
239
+ unsubPromise.catch((error) => {
240
+ logger.debug("Unsubscribe failed (expected if connection closed)", {
241
+ collectionName,
242
+ error
243
+ });
244
+ });
245
+ }
246
+ }
247
+ this.subscriptions.delete(collectionName);
248
+ this.subscriptionPromises.delete(collectionName);
249
+ }
250
+ // ============================================================================
251
+ // State Queries
252
+ // ============================================================================
253
+ /**
254
+ * Check if subscribed to a collection.
255
+ *
256
+ * @param collectionName - The PocketBase collection name
257
+ * @param recordId - Optional: Check specific record subscription, or omit for collection-wide
258
+ */
259
+ isSubscribed(collectionName, recordId) {
260
+ const collectionSubs = this.subscriptions.get(collectionName);
261
+ if (!collectionSubs) return false;
262
+ const subscriptionKey = getSubscriptionKey(recordId);
263
+ return collectionSubs.has(subscriptionKey);
264
+ }
265
+ /**
266
+ * Wait for a subscription to be fully established (useful for testing).
267
+ *
268
+ * @param collectionName - The collection name
269
+ * @param recordId - Optional specific record ID
270
+ * @param timeoutMs - Timeout in milliseconds
271
+ */
272
+ async waitForSubscription(collectionName, recordId, timeoutMs = SUBSCRIPTION_CONFIG.DEFAULT_WAIT_TIMEOUT_MS) {
273
+ const subscriptionKey = getSubscriptionKey(recordId);
274
+ const collectionSubs = this.subscriptions.get(collectionName);
275
+ if (collectionSubs?.has(subscriptionKey)) {
276
+ const collectionPromises2 = this.subscriptionPromises.get(collectionName);
277
+ if (!collectionPromises2?.has(subscriptionKey)) {
278
+ return;
279
+ }
280
+ }
281
+ const collectionPromises = this.subscriptionPromises.get(collectionName);
282
+ const promise = collectionPromises?.get(subscriptionKey);
283
+ if (!promise) {
284
+ const isSubscribed = collectionSubs?.has(subscriptionKey);
285
+ if (!isSubscribed) {
286
+ throw new Error(`No subscription found for ${collectionName}:${subscriptionKey}`);
287
+ }
288
+ return;
289
+ }
290
+ const timeoutPromise = new Promise((_, reject) => {
291
+ setTimeout(() => reject(new Error(`Subscription timeout after ${timeoutMs}ms`)), timeoutMs);
292
+ });
293
+ await Promise.race([promise, timeoutPromise]);
294
+ }
295
+ // ============================================================================
296
+ // Lifecycle Management
297
+ // ============================================================================
298
+ /**
299
+ * Track subscriber addition for a collection.
300
+ * Automatically subscribes when first subscriber is added.
301
+ *
302
+ * @param collectionName - The PocketBase collection name
303
+ * @param collection - The TanStack DB collection to sync with
304
+ */
305
+ async addSubscriber(collectionName, collection) {
306
+ const currentCount = this.subscriberCounts.get(collectionName) || 0;
307
+ const newCount = currentCount + 1;
308
+ this.subscriberCounts.set(collectionName, newCount);
309
+ logger.debug("Subscriber added", { collectionName, count: newCount });
310
+ const cleanupTimer = this.cleanupTimers.get(collectionName);
311
+ if (cleanupTimer) {
312
+ clearTimeout(cleanupTimer);
313
+ this.cleanupTimers.delete(collectionName);
314
+ logger.debug("Cleanup timer cancelled", { collectionName });
315
+ }
316
+ if (newCount === 1 && !this.isSubscribed(collectionName)) {
317
+ logger.debug("First subscriber - starting subscription", { collectionName });
318
+ await this.subscribe(collectionName, collection);
319
+ }
320
+ }
321
+ /**
322
+ * Track subscriber removal for a collection.
323
+ * Automatically unsubscribes (with delay) when last subscriber is removed.
324
+ *
325
+ * @param collectionName - The PocketBase collection name
326
+ */
327
+ removeSubscriber(collectionName) {
328
+ const currentCount = this.subscriberCounts.get(collectionName) || 0;
329
+ const newCount = Math.max(0, currentCount - 1);
330
+ this.subscriberCounts.set(collectionName, newCount);
331
+ logger.debug("Subscriber removed", { collectionName, count: newCount });
332
+ if (newCount === 0) {
333
+ const existingTimer = this.cleanupTimers.get(collectionName);
334
+ if (existingTimer) {
335
+ clearTimeout(existingTimer);
336
+ }
337
+ const cleanupTimer = setTimeout(() => {
338
+ const finalCount = this.subscriberCounts.get(collectionName) || 0;
339
+ if (finalCount === 0) {
340
+ logger.debug("Cleanup timer fired - unsubscribing", { collectionName });
341
+ this.unsubscribeAll(collectionName);
342
+ this.subscriberCounts.delete(collectionName);
343
+ }
344
+ this.cleanupTimers.delete(collectionName);
345
+ }, SUBSCRIPTION_CONFIG.CLEANUP_DELAY_MS);
346
+ this.cleanupTimers.set(collectionName, cleanupTimer);
347
+ logger.debug("Cleanup timer scheduled", {
348
+ collectionName,
349
+ delayMs: SUBSCRIPTION_CONFIG.CLEANUP_DELAY_MS
350
+ });
351
+ }
352
+ }
353
+ /**
354
+ * Get the current subscriber count for a collection.
355
+ * Useful for debugging and testing.
356
+ *
357
+ * @param collectionName - The PocketBase collection name
358
+ * @returns Current subscriber count
359
+ */
360
+ getSubscriberCount(collectionName) {
361
+ return this.subscriberCounts.get(collectionName) || 0;
362
+ }
363
+ };
364
+ var CollectionsContext = createContext(null);
365
+ function CollectionsProvider({ collections, children }) {
366
+ return /* @__PURE__ */ jsx(CollectionsContext.Provider, { value: collections, children });
367
+ }
368
+ function useStore(key) {
369
+ const context = useContext(CollectionsContext);
370
+ if (!context) {
371
+ throw new Error("useStore must be used within a CollectionsProvider");
372
+ }
373
+ if (!(key in context)) {
374
+ throw new Error(`Collection "${key}" not found in CollectionsProvider`);
375
+ }
376
+ return context[key];
377
+ }
378
+ function useStores(keys) {
379
+ const context = useContext(CollectionsContext);
380
+ if (!context) {
381
+ throw new Error("useStores must be used within a CollectionsProvider");
382
+ }
383
+ const collections = keys.map((key) => {
384
+ if (!(key in context)) {
385
+ throw new Error(`Collection "${key}" not found in CollectionsProvider`);
386
+ }
387
+ return context[key];
388
+ });
389
+ return collections;
390
+ }
391
+
392
+ // src/collection.ts
393
+ var CollectionFactory = class {
394
+ constructor(pocketbase, queryClient) {
395
+ this.pocketbase = pocketbase;
396
+ this.queryClient = queryClient;
397
+ this.subscriptionManager = new SubscriptionManager(pocketbase);
398
+ }
399
+ /**
400
+ * Setup automatic subscription lifecycle management.
401
+ * Hooks into TanStack DB's subscriber events to manage real-time subscriptions.
402
+ */
403
+ setupSubscriptionLifecycle(collectionName, baseCollection) {
404
+ baseCollection.on("subscribers:change", (event) => {
405
+ const newCount = event.subscriberCount;
406
+ const previousCount = event.previousSubscriberCount;
407
+ if (newCount > previousCount) {
408
+ this.subscriptionManager.addSubscriber(collectionName, baseCollection).catch(() => {
409
+ });
410
+ } else if (newCount < previousCount) {
411
+ this.subscriptionManager.removeSubscriber(collectionName);
412
+ }
413
+ });
414
+ }
415
+ /**
416
+ * Create a TanStack DB collection from a PocketBase collection.
417
+ *
418
+ * Collections are lazy by default - they don't fetch data or subscribe until queried.
419
+ * Real-time subscriptions automatically start when the first query becomes active
420
+ * and stop when the last query unmounts (with a cleanup delay to prevent thrashing).
421
+ *
422
+ * @param collection - The name of the collection
423
+ * @param options - Optional configuration including relations and expand
424
+ *
425
+ * @example
426
+ * Basic usage with automatic lifecycle management:
427
+ * ```ts
428
+ * const jobsCollection = factory.create('jobs');
429
+ *
430
+ * // In your component - subscription starts automatically
431
+ * const { data } = useLiveQuery((q) =>
432
+ * q.from({ jobs: jobsCollection })
433
+ * );
434
+ * // Subscription stops automatically when component unmounts
435
+ * ```
436
+ *
437
+ * @example
438
+ * With query operators (filters, sorting):
439
+ * ```ts
440
+ * const jobsCollection = factory.create('jobs');
441
+ *
442
+ * // In your component:
443
+ * const { data } = useLiveQuery((q) =>
444
+ * q.from({ jobs: jobsCollection })
445
+ * .where(({ jobs }) => and(
446
+ * eq(jobs.status, 'ACTIVE'),
447
+ * gt(jobs.created, new Date('2025-01-01'))
448
+ * ))
449
+ * .orderBy(({ jobs }) => jobs.created, 'desc')
450
+ * );
451
+ * ```
452
+ *
453
+ * @example
454
+ * With relation expansion:
455
+ * ```ts
456
+ * const jobsCollection = factory.create('jobs', {
457
+ * expand: 'customer,location'
458
+ * });
459
+ *
460
+ * // Expanded relations available in record.expand
461
+ * ```
462
+ *
463
+ * @example
464
+ * With relations (for manual joins):
465
+ * ```ts
466
+ * const customersCollection = factory.create('customers');
467
+ * const jobsCollection = factory.create('jobs', {
468
+ * relations: { customer: customersCollection }
469
+ * });
470
+ *
471
+ * // In your component, manually build joins:
472
+ * const { data } = useLiveQuery((q) =>
473
+ * q.from({ job: jobsCollection })
474
+ * .join(
475
+ * { customer: customersCollection },
476
+ * ({ job, customer }) => eq(job.customer, customer.id),
477
+ * "left"
478
+ * )
479
+ * .select(({ job, customer }) => ({
480
+ * ...job,
481
+ * expand: {
482
+ * customer: customer ? { ...customer } : undefined
483
+ * }
484
+ * }))
485
+ * );
486
+ * ```
487
+ *
488
+ * @example
489
+ * Manual subscription control (advanced):
490
+ * ```ts
491
+ * const jobsCollection = factory.create('jobs');
492
+ *
493
+ * // Manually subscribe to specific record (bypasses automatic lifecycle)
494
+ * await jobsCollection.subscribe('record_id_123');
495
+ *
496
+ * // Check subscription status
497
+ * const isSubbed = jobsCollection.isSubscribed('record_id_123');
498
+ *
499
+ * // Manually unsubscribe
500
+ * jobsCollection.unsubscribe('record_id_123');
501
+ * ```
502
+ */
503
+ create(collection, options) {
504
+ const baseCollection = createCollection(
505
+ queryCollectionOptions({
506
+ queryKey: [collection],
507
+ queryFn: async () => {
508
+ const queryOptions = {};
509
+ if (options?.expand) {
510
+ queryOptions.expand = options.expand;
511
+ }
512
+ const result = await this.pocketbase.collection(collection).getFullList(queryOptions);
513
+ return result;
514
+ },
515
+ queryClient: this.queryClient,
516
+ getKey: (item) => item.id,
517
+ startSync: options?.startSync ?? false
518
+ })
519
+ );
520
+ const subscribableCollection = Object.assign(baseCollection, {
521
+ subscribe: async (recordId) => {
522
+ await this.subscriptionManager.subscribe(collection, baseCollection, recordId);
523
+ },
524
+ unsubscribe: (recordId) => {
525
+ this.subscriptionManager.unsubscribe(collection, recordId);
526
+ },
527
+ unsubscribeAll: () => {
528
+ this.subscriptionManager.unsubscribeAll(collection);
529
+ },
530
+ isSubscribed: (recordId) => {
531
+ return this.subscriptionManager.isSubscribed(collection, recordId);
532
+ },
533
+ waitForSubscription: async (recordId, timeoutMs) => {
534
+ await this.subscriptionManager.waitForSubscription(collection, recordId, timeoutMs);
535
+ },
536
+ relations: options?.relations || {}
537
+ });
538
+ this.setupSubscriptionLifecycle(collection, baseCollection);
539
+ return subscribableCollection;
540
+ }
541
+ };
542
+
543
+ export { CollectionFactory, CollectionsProvider, SUBSCRIPTION_CONFIG, SubscriptionManager, resetLogger, setLogger, useStore, useStores };
544
+ //# sourceMappingURL=index.js.map
545
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/logger.ts","../src/subscription-manager.ts","../src/provider.tsx","../src/collection.ts"],"names":["collectionPromises"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Logger interface for subscription events and internal operations.\n * Users can provide their own implementation to integrate with external logging services.\n */\nexport interface Logger {\n /**\n * Log debug-level messages (typically only shown in development).\n * @param msg - The message to log\n * @param context - Optional context object with additional information\n */\n debug: (msg: string, context?: object) => void;\n\n /**\n * Log warning-level messages.\n * @param msg - The message to log\n * @param context - Optional context object with additional information\n */\n warn: (msg: string, context?: object) => void;\n\n /**\n * Log error-level messages.\n * @param msg - The message to log\n * @param context - Optional context object with additional information\n */\n error: (msg: string, context?: object) => void;\n}\n\n/**\n * Default console-based logger implementation.\n * Only logs debug messages in development mode.\n */\nconst defaultLogger: Logger = {\n debug: (msg: string, context?: object) => {\n // Only log debug in development\n if (process.env.NODE_ENV === 'development') {\n // biome-ignore lint/suspicious/noConsoleLog: Debug logging is acceptable in development\n console.log(`[pbtsdb] ${msg}`, context || '');\n }\n },\n warn: (msg: string, context?: object) => {\n // biome-ignore lint/suspicious/noConsoleLog: Warning logging is acceptable\n console.warn(`[pbtsdb] ${msg}`, context || '');\n },\n error: (msg: string, context?: object) => {\n // biome-ignore lint/suspicious/noConsoleLog: Error logging is acceptable\n console.error(`[pbtsdb] ${msg}`, context || '');\n },\n};\n\n/**\n * Current logger instance (can be replaced by users).\n */\nlet currentLogger: Logger = defaultLogger;\n\n/**\n * Internal logger instance used by the library.\n */\nexport const logger: Logger = {\n debug: (msg: string, context?: object) => currentLogger.debug(msg, context),\n warn: (msg: string, context?: object) => currentLogger.warn(msg, context),\n error: (msg: string, context?: object) => currentLogger.error(msg, context),\n};\n\n/**\n * Set a custom logger implementation.\n * This allows users to integrate with their own logging services (e.g., Sentry, LogRocket, etc.).\n *\n * @param customLogger - The custom logger implementation\n *\n * @example\n * ```ts\n * import { setLogger } from 'pocketbase-tanstack-db';\n *\n * // Example: Integration with a custom logging service\n * setLogger({\n * debug: (msg, context) => {\n * myLogger.debug(msg, context);\n * },\n * warn: (msg, context) => {\n * myLogger.warn(msg, context);\n * },\n * error: (msg, context) => {\n * myLogger.error(msg, context);\n * // Also send to error tracking service\n * Sentry.captureMessage(msg, { level: 'error', extra: context });\n * },\n * });\n * ```\n *\n * @example\n * ```ts\n * // Example: Disable all logging\n * setLogger({\n * debug: () => {},\n * warn: () => {},\n * error: () => {},\n * });\n * ```\n */\nexport function setLogger(customLogger: Logger): void {\n currentLogger = customLogger;\n}\n\n/**\n * Reset the logger to the default implementation.\n */\nexport function resetLogger(): void {\n currentLogger = defaultLogger;\n}\n","import PocketBase from 'pocketbase';\nimport type { UnsubscribeFunc } from 'pocketbase';\nimport type { Collection } from \"@tanstack/db\"\nimport type { RealtimeEvent, SubscriptionState } from './types';\nimport { logger } from './logger';\n\nexport const SUBSCRIPTION_CONFIG = {\n MAX_RECONNECT_ATTEMPTS: 5,\n BASE_RECONNECT_DELAY_MS: 1000,\n DEFAULT_WAIT_TIMEOUT_MS: 5000,\n CLEANUP_DELAY_MS: 5000,\n} as const;\n\nfunction hasId(record: unknown): record is { id: string } {\n return typeof record === 'object'\n && record !== null\n && 'id' in record\n && typeof (record as { id: unknown }).id === 'string';\n}\n\nfunction createPendingSubscriptionState(recordId?: string): SubscriptionState {\n return {\n unsubscribe: async () => {},\n recordId,\n reconnectAttempts: 0,\n isReconnecting: false,\n };\n}\n\nfunction getSubscriptionKey(recordId?: string): string {\n return recordId || '*';\n}\n\n\n/**\n * Manages real-time subscriptions to PocketBase collections.\n * Handles subscription lifecycle, reconnection with exponential backoff,\n * and automatic synchronization with TanStack DB collections.\n *\n * Subscriptions are automatically managed based on TanStack DB's subscriber count:\n * - Start subscribing when first query becomes active\n * - Stop subscribing when last query becomes inactive (with cleanup delay)\n */\nexport class SubscriptionManager {\n private subscriptions: Map<string, Map<string, SubscriptionState>> = new Map();\n private subscriptionPromises: Map<string, Map<string, Promise<void>>> = new Map();\n private cleanupTimers: Map<string, NodeJS.Timeout> = new Map();\n private subscriberCounts: Map<string, number> = new Map();\n\n constructor(private pocketbase: PocketBase) {}\n\n // ============================================================================\n // Internal Helpers\n // ============================================================================\n\n private async setupSubscription<T extends object>(\n collectionName: string,\n collection: Collection<T>,\n recordId?: string\n ): Promise<UnsubscribeFunc> {\n const subscriptionKey = getSubscriptionKey(recordId);\n\n const eventHandler = (event: RealtimeEvent<T>) => {\n collection.utils.writeBatch(() => {\n switch (event.action) {\n case 'create':\n collection.utils.writeInsert(event.record);\n break;\n case 'update':\n collection.utils.writeUpdate(event.record);\n break;\n case 'delete':\n if (!hasId(event.record)) {\n logger.error('Delete event record missing id field', {\n collectionName,\n record: event.record\n });\n return;\n }\n collection.utils.writeDelete(event.record.id);\n break;\n }\n });\n };\n\n const unsubscribe = await this.pocketbase\n .collection(collectionName)\n .subscribe(subscriptionKey, eventHandler);\n\n logger.debug('Subscription established', { collectionName, subscriptionKey });\n\n return unsubscribe;\n }\n\n private async handleReconnection<T extends object>(\n collectionName: string,\n collection: Collection<T>,\n recordId?: string\n ): Promise<void> {\n const collectionSubs = this.subscriptions.get(collectionName);\n if (!collectionSubs) return;\n\n const subscriptionKey = getSubscriptionKey(recordId);\n const state = collectionSubs.get(subscriptionKey);\n if (!state || state.isReconnecting) return;\n\n state.isReconnecting = true;\n logger.warn('Starting reconnection attempts', { collectionName, subscriptionKey });\n\n while (state.reconnectAttempts < SUBSCRIPTION_CONFIG.MAX_RECONNECT_ATTEMPTS) {\n const delay = SUBSCRIPTION_CONFIG.BASE_RECONNECT_DELAY_MS * Math.pow(2, state.reconnectAttempts);\n logger.debug(`Reconnection attempt ${state.reconnectAttempts + 1}, waiting ${delay}ms`, {\n collectionName,\n subscriptionKey\n });\n await new Promise(resolve => setTimeout(resolve, delay));\n\n try {\n const newUnsubscribe = await this.setupSubscription(\n collectionName,\n collection,\n recordId\n );\n\n state.unsubscribe = newUnsubscribe;\n state.reconnectAttempts = 0;\n state.isReconnecting = false;\n logger.debug('Reconnection successful', { collectionName, subscriptionKey });\n return;\n } catch (error) {\n state.reconnectAttempts++;\n logger.warn(`Reconnection attempt ${state.reconnectAttempts} failed`, {\n collectionName,\n subscriptionKey,\n error\n });\n }\n }\n\n logger.error('Max reconnection attempts reached', {\n collectionName,\n subscriptionKey,\n attempts: state.reconnectAttempts\n });\n state.isReconnecting = false;\n collectionSubs.delete(subscriptionKey);\n }\n\n // ============================================================================\n // Core Subscription Methods\n // ============================================================================\n\n /**\n * Subscribe to real-time updates for a collection.\n * Returns a promise that resolves when the subscription is fully established.\n *\n * @param collectionName - The PocketBase collection name\n * @param collection - The TanStack DB collection to sync with\n * @param recordId - Optional: Subscribe to specific record, or omit for collection-wide updates\n */\n async subscribe<T extends object>(\n collectionName: string,\n collection: Collection<T>,\n recordId?: string\n ): Promise<void> {\n if (!this.subscriptions.has(collectionName)) {\n this.subscriptions.set(collectionName, new Map());\n }\n if (!this.subscriptionPromises.has(collectionName)) {\n this.subscriptionPromises.set(collectionName, new Map());\n }\n\n const collectionSubs = this.subscriptions.get(collectionName)!;\n const collectionPromises = this.subscriptionPromises.get(collectionName)!;\n const subscriptionKey = getSubscriptionKey(recordId);\n\n if (collectionSubs.has(subscriptionKey)) {\n logger.debug('Already subscribed, skipping', { collectionName, subscriptionKey });\n return;\n }\n\n const existingPromise = collectionPromises.get(subscriptionKey);\n if (existingPromise) {\n logger.debug('Pending subscription found, waiting', { collectionName, subscriptionKey });\n return existingPromise;\n }\n\n // Placeholder ensures isSubscribed() returns true immediately\n collectionSubs.set(subscriptionKey, createPendingSubscriptionState(recordId));\n\n const subscriptionPromise = (async () => {\n try {\n const unsubscribe = await this.setupSubscription(collectionName, collection, recordId);\n\n const state = collectionSubs.get(subscriptionKey);\n if (state) {\n state.unsubscribe = unsubscribe;\n }\n } catch (error) {\n collectionSubs.delete(subscriptionKey);\n collectionPromises.delete(subscriptionKey);\n logger.error('Subscription failed', { collectionName, subscriptionKey, error });\n await this.handleReconnection(collectionName, collection, recordId);\n throw error;\n }\n // Keep promise for waitForSubscription to check completion\n })();\n\n collectionPromises.set(subscriptionKey, subscriptionPromise);\n\n try {\n await subscriptionPromise;\n } catch (_error) {\n // Error logged and reconnection initiated above\n }\n }\n\n /**\n * Unsubscribe from real-time updates.\n *\n * @param collectionName - The PocketBase collection name\n * @param recordId - Optional: Unsubscribe from specific record, or omit for collection-wide\n */\n unsubscribe(collectionName: string, recordId?: string): void {\n const collectionSubs = this.subscriptions.get(collectionName);\n if (!collectionSubs) return;\n\n const subscriptionKey = getSubscriptionKey(recordId);\n const state = collectionSubs.get(subscriptionKey);\n\n if (state) {\n const unsubPromise = state.unsubscribe();\n if (unsubPromise && typeof unsubPromise.catch === 'function') {\n unsubPromise.catch((error) => {\n logger.debug('Unsubscribe failed (expected if connection closed)', {\n collectionName,\n subscriptionKey,\n error\n });\n });\n }\n collectionSubs.delete(subscriptionKey);\n logger.debug('Unsubscribed', { collectionName, subscriptionKey });\n }\n\n const collectionPromises = this.subscriptionPromises.get(collectionName);\n if (collectionPromises) {\n collectionPromises.delete(subscriptionKey);\n if (collectionPromises.size === 0) {\n this.subscriptionPromises.delete(collectionName);\n }\n }\n\n if (collectionSubs.size === 0) {\n this.subscriptions.delete(collectionName);\n }\n }\n\n /**\n * Unsubscribe from all subscriptions for a collection.\n *\n * @param collectionName - The PocketBase collection name\n */\n unsubscribeAll(collectionName: string): void {\n const collectionSubs = this.subscriptions.get(collectionName);\n if (!collectionSubs) return;\n\n logger.debug('Unsubscribing from all subscriptions', { collectionName, count: collectionSubs.size });\n\n for (const state of collectionSubs.values()) {\n const unsubPromise = state.unsubscribe();\n if (unsubPromise && typeof unsubPromise.catch === 'function') {\n unsubPromise.catch((error) => {\n logger.debug('Unsubscribe failed (expected if connection closed)', {\n collectionName,\n error\n });\n });\n }\n }\n\n this.subscriptions.delete(collectionName);\n this.subscriptionPromises.delete(collectionName);\n }\n\n // ============================================================================\n // State Queries\n // ============================================================================\n\n /**\n * Check if subscribed to a collection.\n *\n * @param collectionName - The PocketBase collection name\n * @param recordId - Optional: Check specific record subscription, or omit for collection-wide\n */\n isSubscribed(collectionName: string, recordId?: string): boolean {\n const collectionSubs = this.subscriptions.get(collectionName);\n if (!collectionSubs) return false;\n\n const subscriptionKey = getSubscriptionKey(recordId);\n return collectionSubs.has(subscriptionKey);\n }\n\n /**\n * Wait for a subscription to be fully established (useful for testing).\n *\n * @param collectionName - The collection name\n * @param recordId - Optional specific record ID\n * @param timeoutMs - Timeout in milliseconds\n */\n async waitForSubscription(\n collectionName: string,\n recordId?: string,\n timeoutMs: number = SUBSCRIPTION_CONFIG.DEFAULT_WAIT_TIMEOUT_MS\n ): Promise<void> {\n const subscriptionKey = getSubscriptionKey(recordId);\n const collectionSubs = this.subscriptions.get(collectionName);\n\n if (collectionSubs?.has(subscriptionKey)) {\n const collectionPromises = this.subscriptionPromises.get(collectionName);\n if (!collectionPromises?.has(subscriptionKey)) {\n return;\n }\n }\n\n const collectionPromises = this.subscriptionPromises.get(collectionName);\n const promise = collectionPromises?.get(subscriptionKey);\n\n if (!promise) {\n const isSubscribed = collectionSubs?.has(subscriptionKey);\n if (!isSubscribed) {\n throw new Error(`No subscription found for ${collectionName}:${subscriptionKey}`);\n }\n return;\n }\n\n const timeoutPromise = new Promise<void>((_, reject) => {\n setTimeout(() => reject(new Error(`Subscription timeout after ${timeoutMs}ms`)), timeoutMs);\n });\n\n await Promise.race([promise, timeoutPromise]);\n }\n\n // ============================================================================\n // Lifecycle Management\n // ============================================================================\n\n /**\n * Track subscriber addition for a collection.\n * Automatically subscribes when first subscriber is added.\n *\n * @param collectionName - The PocketBase collection name\n * @param collection - The TanStack DB collection to sync with\n */\n async addSubscriber<T extends object>(\n collectionName: string,\n collection: Collection<T>\n ): Promise<void> {\n const currentCount = this.subscriberCounts.get(collectionName) || 0;\n const newCount = currentCount + 1;\n this.subscriberCounts.set(collectionName, newCount);\n\n logger.debug('Subscriber added', { collectionName, count: newCount });\n\n const cleanupTimer = this.cleanupTimers.get(collectionName);\n if (cleanupTimer) {\n clearTimeout(cleanupTimer);\n this.cleanupTimers.delete(collectionName);\n logger.debug('Cleanup timer cancelled', { collectionName });\n }\n\n if (newCount === 1 && !this.isSubscribed(collectionName)) {\n logger.debug('First subscriber - starting subscription', { collectionName });\n await this.subscribe(collectionName, collection);\n }\n }\n\n /**\n * Track subscriber removal for a collection.\n * Automatically unsubscribes (with delay) when last subscriber is removed.\n *\n * @param collectionName - The PocketBase collection name\n */\n removeSubscriber(collectionName: string): void {\n const currentCount = this.subscriberCounts.get(collectionName) || 0;\n const newCount = Math.max(0, currentCount - 1);\n this.subscriberCounts.set(collectionName, newCount);\n\n logger.debug('Subscriber removed', { collectionName, count: newCount });\n\n if (newCount === 0) {\n const existingTimer = this.cleanupTimers.get(collectionName);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n const cleanupTimer = setTimeout(() => {\n const finalCount = this.subscriberCounts.get(collectionName) || 0;\n if (finalCount === 0) {\n logger.debug('Cleanup timer fired - unsubscribing', { collectionName });\n this.unsubscribeAll(collectionName);\n this.subscriberCounts.delete(collectionName);\n }\n this.cleanupTimers.delete(collectionName);\n }, SUBSCRIPTION_CONFIG.CLEANUP_DELAY_MS);\n\n this.cleanupTimers.set(collectionName, cleanupTimer);\n logger.debug('Cleanup timer scheduled', {\n collectionName,\n delayMs: SUBSCRIPTION_CONFIG.CLEANUP_DELAY_MS\n });\n }\n }\n\n /**\n * Get the current subscriber count for a collection.\n * Useful for debugging and testing.\n *\n * @param collectionName - The PocketBase collection name\n * @returns Current subscriber count\n */\n getSubscriberCount(collectionName: string): number {\n return this.subscriberCounts.get(collectionName) || 0;\n }\n}\n","import React, { createContext, useContext, type ReactNode } from 'react';\nimport type { Collection } from '@tanstack/db';\nimport type { SchemaDeclaration } from './types';\n\n/**\n * Map of collection names to TanStack DB Collection instances.\n * Keys are user-defined strings, values are Collection instances.\n *\n * @example\n * ```ts\n * const stores = {\n * jobs: jobsCollection,\n * customers: customersCollection,\n * addresses: addressesCollection\n * };\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type CollectionsMap = Record<string, Collection<any>>;\n\n/**\n * Context for providing collections to React components.\n * @internal\n */\nconst CollectionsContext = createContext<CollectionsMap | null>(null);\n\n/**\n * Props for the CollectionsProvider component.\n */\nexport interface CollectionsProviderProps {\n /** Map of collection name to Collection instance */\n collections: CollectionsMap;\n /** React children to render */\n children: ReactNode;\n}\n\n/**\n * Provider component that makes collections available to all child components.\n * Wrap your app with this provider to use the useStore and useStores hooks.\n *\n * @example\n * ```tsx\n * const factory = new CollectionFactory<Schema>(pb, queryClient);\n * const collections = {\n * jobs: factory.create('jobs'),\n * customers: factory.create('customers'),\n * addresses: factory.create('addresses')\n * };\n *\n * function App() {\n * return (\n * <CollectionsProvider collections={collections}>\n * <YourApp />\n * </CollectionsProvider>\n * );\n * }\n * ```\n */\nexport function CollectionsProvider({ collections, children }: CollectionsProviderProps) {\n return (\n <CollectionsContext.Provider value={collections}>\n {children}\n </CollectionsContext.Provider>\n );\n}\n\n/**\n * Hook to access a single collection from the provider.\n * Returns the Collection instance for the specified key.\n *\n * @template T - The record type for the collection\n * @param key - The collection key as defined in the provider\n * @returns The Collection instance\n * @throws Error if used outside of CollectionsProvider or if key doesn't exist\n *\n * @example\n * ```tsx\n * function JobsList() {\n * const jobsCollection = useStore<JobsRecord>('jobs');\n *\n * const { data } = useLiveQuery((q) =>\n * q.from({ jobs: jobsCollection })\n * );\n *\n * return (\n * <ul>\n * {data?.map(job => <li key={job.id}>{job.name}</li>)}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useStore<T extends object = object>(key: string): Collection<T> {\n const context = useContext(CollectionsContext);\n\n if (!context) {\n throw new Error('useStore must be used within a CollectionsProvider');\n }\n\n if (!(key in context)) {\n throw new Error(`Collection \"${key}\" not found in CollectionsProvider`);\n }\n\n return context[key] as Collection<T>;\n}\n\n/**\n * Hook to access multiple collections from the provider.\n * Returns an array of Collection instances matching the order of the keys array.\n *\n * @template T - Tuple type of record types for each collection\n * @param keys - Array of collection keys as defined in the provider\n * @returns Array of Collection instances in the same order as keys\n * @throws Error if used outside of CollectionsProvider or if any key doesn't exist\n *\n * @example\n * ```tsx\n * function JobsWithCustomers() {\n * const [jobsCollection, customersCollection] = useStores<\n * [JobsRecord, CustomersRecord]\n * >(['jobs', 'customers']);\n *\n * const { data } = useLiveQuery((q) =>\n * q.from({ job: jobsCollection })\n * .join(\n * { customer: customersCollection },\n * ({ job, customer }) => eq(job.customer, customer.id),\n * 'left'\n * )\n * );\n *\n * return <div>...</div>;\n * }\n * ```\n */\nexport function useStores<T extends readonly object[]>(\n keys: readonly string[]\n): { [K in keyof T]: Collection<T[K]> } {\n const context = useContext(CollectionsContext);\n\n if (!context) {\n throw new Error('useStores must be used within a CollectionsProvider');\n }\n\n const collections = keys.map((key) => {\n if (!(key in context)) {\n throw new Error(`Collection \"${key}\" not found in CollectionsProvider`);\n }\n return context[key];\n });\n\n return collections as { [K in keyof T]: Collection<T[K]> };\n}\n","import PocketBase from 'pocketbase';\nimport { createCollection, type Collection } from \"@tanstack/db\"\nimport { queryCollectionOptions } from \"@tanstack/query-db-collection\"\nimport { QueryClient } from '@tanstack/react-query'\nimport { SubscriptionManager } from './subscription-manager';\nimport type {\n SchemaDeclaration,\n SubscribableCollection,\n JoinHelper,\n CreateCollectionOptions,\n RelationsConfig,\n WithExpand,\n} from './types';\n\nexport type {\n SchemaDeclaration,\n SubscribableCollection,\n JoinHelper,\n CreateCollectionOptions,\n RelationsConfig,\n} from './types';\n\nexport {\n CollectionsProvider,\n useStore,\n useStores,\n type CollectionsMap,\n type CollectionsProviderProps,\n} from './provider';\n\n/**\n * Factory for creating type-safe TanStack DB collections backed by PocketBase.\n * Integrates real-time subscriptions with automatic synchronization.\n */\nexport class CollectionFactory<Schema extends SchemaDeclaration, TMaxDepth extends 0 | 1 | 2 | 3 | 4 | 5 | 6 = 2> {\n private subscriptionManager: SubscriptionManager;\n\n constructor(public pocketbase: PocketBase, public queryClient: QueryClient) {\n this.subscriptionManager = new SubscriptionManager(pocketbase);\n }\n\n /**\n * Setup automatic subscription lifecycle management.\n * Hooks into TanStack DB's subscriber events to manage real-time subscriptions.\n */\n private setupSubscriptionLifecycle<T extends object>(\n collectionName: string,\n baseCollection: Collection<T>\n ): void {\n baseCollection.on('subscribers:change', (event) => {\n const newCount = event.subscriberCount;\n const previousCount = event.previousSubscriberCount;\n\n if (newCount > previousCount) {\n // Fire and forget - subscription handled asynchronously\n this.subscriptionManager.addSubscriber(collectionName, baseCollection).catch(() => {\n // Silently handle subscription errors - reconnection will be attempted\n });\n } else if (newCount < previousCount) {\n this.subscriptionManager.removeSubscriber(collectionName);\n }\n });\n }\n\n /**\n * Create a TanStack DB collection from a PocketBase collection.\n *\n * Collections are lazy by default - they don't fetch data or subscribe until queried.\n * Real-time subscriptions automatically start when the first query becomes active\n * and stop when the last query unmounts (with a cleanup delay to prevent thrashing).\n *\n * @param collection - The name of the collection\n * @param options - Optional configuration including relations and expand\n *\n * @example\n * Basic usage with automatic lifecycle management:\n * ```ts\n * const jobsCollection = factory.create('jobs');\n *\n * // In your component - subscription starts automatically\n * const { data } = useLiveQuery((q) =>\n * q.from({ jobs: jobsCollection })\n * );\n * // Subscription stops automatically when component unmounts\n * ```\n *\n * @example\n * With query operators (filters, sorting):\n * ```ts\n * const jobsCollection = factory.create('jobs');\n *\n * // In your component:\n * const { data } = useLiveQuery((q) =>\n * q.from({ jobs: jobsCollection })\n * .where(({ jobs }) => and(\n * eq(jobs.status, 'ACTIVE'),\n * gt(jobs.created, new Date('2025-01-01'))\n * ))\n * .orderBy(({ jobs }) => jobs.created, 'desc')\n * );\n * ```\n *\n * @example\n * With relation expansion:\n * ```ts\n * const jobsCollection = factory.create('jobs', {\n * expand: 'customer,location'\n * });\n *\n * // Expanded relations available in record.expand\n * ```\n *\n * @example\n * With relations (for manual joins):\n * ```ts\n * const customersCollection = factory.create('customers');\n * const jobsCollection = factory.create('jobs', {\n * relations: { customer: customersCollection }\n * });\n *\n * // In your component, manually build joins:\n * const { data } = useLiveQuery((q) =>\n * q.from({ job: jobsCollection })\n * .join(\n * { customer: customersCollection },\n * ({ job, customer }) => eq(job.customer, customer.id),\n * \"left\"\n * )\n * .select(({ job, customer }) => ({\n * ...job,\n * expand: {\n * customer: customer ? { ...customer } : undefined\n * }\n * }))\n * );\n * ```\n *\n * @example\n * Manual subscription control (advanced):\n * ```ts\n * const jobsCollection = factory.create('jobs');\n *\n * // Manually subscribe to specific record (bypasses automatic lifecycle)\n * await jobsCollection.subscribe('record_id_123');\n *\n * // Check subscription status\n * const isSubbed = jobsCollection.isSubscribed('record_id_123');\n *\n * // Manually unsubscribe\n * jobsCollection.unsubscribe('record_id_123');\n * ```\n */\n create<\n C extends keyof Schema & string,\n E extends string | undefined = undefined\n >(\n collection: C,\n options?: CreateCollectionOptions<Schema, C, E>\n ): Collection<WithExpand<Schema, C, E>> & SubscribableCollection<WithExpand<Schema, C, E>> & JoinHelper<Schema, C, WithExpand<Schema, C, E>> {\n type RecordType = WithExpand<Schema, C, E>;\n\n const baseCollection = createCollection(\n queryCollectionOptions<RecordType>({\n queryKey: [collection],\n queryFn: async () => {\n const queryOptions: { expand?: string } = {};\n if (options?.expand) {\n queryOptions.expand = options.expand;\n }\n\n const result = await this.pocketbase\n .collection(collection)\n .getFullList(queryOptions);\n\n return result as unknown as RecordType[];\n },\n queryClient: this.queryClient,\n getKey: (item: RecordType) => (item as { id: string }).id,\n startSync: options?.startSync ?? false,\n })\n );\n\n const subscribableCollection = Object.assign(baseCollection, {\n subscribe: async (recordId?: string) => {\n await this.subscriptionManager.subscribe(collection, baseCollection, recordId);\n },\n unsubscribe: (recordId?: string) => {\n this.subscriptionManager.unsubscribe(collection, recordId);\n },\n unsubscribeAll: () => {\n this.subscriptionManager.unsubscribeAll(collection);\n },\n isSubscribed: (recordId?: string) => {\n return this.subscriptionManager.isSubscribed(collection, recordId);\n },\n waitForSubscription: async (recordId?: string, timeoutMs?: number) => {\n await this.subscriptionManager.waitForSubscription(collection, recordId, timeoutMs);\n },\n relations: options?.relations || {} as RelationsConfig<Schema, C>\n });\n\n this.setupSubscriptionLifecycle(collection, baseCollection);\n\n return subscribableCollection as Collection<RecordType> & SubscribableCollection<RecordType> & JoinHelper<Schema, C, RecordType>;\n }\n}\n"]}