pbtsdb 0.11.0 → 2.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/core.d.ts CHANGED
@@ -1,9 +1,7 @@
1
- import { Collection, InsertMutationFn, UpdateMutationFn, DeleteMutationFn, BaseCollectionConfig, IR } from '@tanstack/db';
1
+ import { Collection, InsertMutationFn, UpdateMutationFn, DeleteMutationFn, BaseCollectionConfig, UtilsRecord, IR } from '@tanstack/db';
2
2
  export { BTreeIndex, BasicIndex, DeltaEvent, DeltaType, EffectConfig, EffectContext, IndexConstructor, ReverseIndex, createEffect, materialize, toArray } from '@tanstack/db';
3
3
  import PocketBase, { RecordSubscribeOptions } from 'pocketbase';
4
4
  export { RecordSubscribeOptions } from 'pocketbase';
5
- import { QueryCollectionUtils } from '@tanstack/query-db-collection';
6
- import { QueryClient } from '@tanstack/react-query';
7
5
 
8
6
  /** Which rows a collection's realtime subscription covers. */
9
7
  type RealtimeMode = 'collection' | 'query';
@@ -102,8 +100,8 @@ type RelationAsCollection<T> = T extends Array<infer U> ? U extends object ? Col
102
100
  interface RelationTarget {
103
101
  /** Relation targets of this collection, for nested expand paths. */
104
102
  readonly relationTargets: Record<string, RelationTarget> | undefined;
105
- /** Upsert filed rows into the store. False when the store cannot take them yet. */
106
- writeFiled: (records: object[]) => Promise<boolean>;
103
+ /** Upsert filed rows into the store for `holder`, a parent's token. False when the store cannot take them yet. */
104
+ writeFiled: (records: object[], holder: object) => Promise<boolean>;
107
105
  /**
108
106
  * A parent's fetch may file and mark `field` once `settles` resolves; the
109
107
  * target's own fetch for that subset waits for it (see docs/internals.md,
@@ -112,11 +110,16 @@ interface RelationTarget {
112
110
  expectFiling: (field: string, settles: Promise<void>) => () => void;
113
111
  /** Record that every row with `field === value` is now in this collection's store. */
114
112
  markSubsetLoaded: (field: string, value: string) => void;
113
+ /** Release rows `holder` filed that no parent row files any more. */
114
+ releaseFiled: (ids: readonly string[], holder: object) => void;
115
+ /** Whether a `writeFiled` would land within the current task: the store is syncing and ready. */
116
+ isReady: () => boolean;
115
117
  /**
116
- * Hold this collection live for a parent, with the filters covering the
117
- * rows the parent filed here (query mode only).
118
+ * Hold this collection live for a parent identified by `holder`, with the
119
+ * filters covering the rows the parent filed here (query mode only).
120
+ * Releasing the hold releases every row filed under `holder`.
118
121
  */
119
- holdLive: () => HeldTarget;
122
+ holdLive: (holder: object) => HeldTarget;
120
123
  }
121
124
  /**
122
125
  * A parent's hold on a relation target, returned by `holdLive`.
@@ -205,7 +208,7 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
205
208
  * @example
206
209
  * ```ts
207
210
  * // Allow inserting without created, updated (server-generated timestamps)
208
- * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
211
+ * const booksCollection = createCollection<Schema>(pb)('books', {
209
212
  * omitOnInsert: ['created', 'updated'] as const
210
213
  * });
211
214
  *
@@ -234,20 +237,21 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
234
237
  * @example
235
238
  * ```ts
236
239
  * // Use default automatic handler (recommended)
237
- * const collection = createCollection<Schema>(pb, queryClient)('books');
240
+ * const collection = createCollection<Schema>(pb)('books');
238
241
  *
239
242
  * // Custom handler
240
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
243
+ * const collection = createCollection<Schema>(pb)('books', {
241
244
  * onInsert: async ({ transaction }) => {
242
- * for (const mutation of transaction.mutations) {
243
- * await customInsertLogic(mutation.modified);
244
- * }
245
- * await queryClient.invalidateQueries({ queryKey: ['books'] });
245
+ * const created = await Promise.all(
246
+ * transaction.mutations.map(mutation => customInsertLogic(mutation.modified))
247
+ * );
248
+ * // Land the server rows before the optimistic state drops
249
+ * await collection.accept(created);
246
250
  * }
247
251
  * });
248
252
  *
249
253
  * // Disable inserts (read-only collection)
250
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
254
+ * const collection = createCollection<Schema>(pb)('books', {
251
255
  * onInsert: false
252
256
  * });
253
257
  * ```
@@ -266,20 +270,23 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
266
270
  * @example
267
271
  * ```ts
268
272
  * // Use default automatic handler (recommended)
269
- * const collection = createCollection<Schema>(pb, queryClient)('books');
273
+ * const collection = createCollection<Schema>(pb)('books');
270
274
  *
271
275
  * // Custom handler
272
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
276
+ * const collection = createCollection<Schema>(pb)('books', {
273
277
  * onUpdate: async ({ transaction }) => {
274
- * for (const mutation of transaction.mutations) {
275
- * await customUpdateLogic(mutation.original.id, mutation.changes);
276
- * }
277
- * await queryClient.invalidateQueries({ queryKey: ['books'] });
278
+ * const updated = await Promise.all(
279
+ * transaction.mutations.map(mutation =>
280
+ * customUpdateLogic(mutation.original.id, mutation.changes)
281
+ * )
282
+ * );
283
+ * // Land the server rows before the optimistic state drops
284
+ * await collection.accept(updated);
278
285
  * }
279
286
  * });
280
287
  *
281
288
  * // Disable updates (read-only collection)
282
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
289
+ * const collection = createCollection<Schema>(pb)('books', {
283
290
  * onUpdate: false
284
291
  * });
285
292
  * ```
@@ -297,41 +304,44 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
297
304
  * @example
298
305
  * ```ts
299
306
  * // Use default automatic handler (recommended)
300
- * const collection = createCollection<Schema>(pb, queryClient)('books');
307
+ * const collection = createCollection<Schema>(pb)('books');
301
308
  *
302
309
  * // Custom handler
303
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
310
+ * const collection = createCollection<Schema>(pb)('books', {
304
311
  * onDelete: async ({ transaction }) => {
305
- * for (const mutation of transaction.mutations) {
306
- * await customDeleteLogic(mutation.original.id);
307
- * }
308
- * await queryClient.invalidateQueries({ queryKey: ['books'] });
312
+ * const ids = transaction.mutations.map(mutation => mutation.original.id);
313
+ * await Promise.all(ids.map(id => customDeleteLogic(id)));
314
+ * // Remove the rows before the optimistic state drops
315
+ * await collection.evict(ids);
309
316
  * }
310
317
  * });
311
318
  *
312
319
  * // Disable deletes (read-only collection)
313
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
320
+ * const collection = createCollection<Schema>(pb)('books', {
314
321
  * onDelete: false
315
322
  * });
316
323
  * ```
317
324
  */
318
325
  onDelete?: DeleteMutationFn<ExtractRecordType<Schema, CollectionName>> | false;
319
326
  /**
320
- * If true, refetch the entire collection after a successful insert,
321
- * update, or delete. Defaults to false: the built-in insert/update handlers
322
- * write the server response straight back into the synced layer, and the
323
- * realtime subscription reconciles everything else. Set true if you do not
324
- * trust realtime to reconcile in time (e.g. flaky socket, server-side hooks
325
- * producing fields you must read synchronously).
326
- *
327
- * Only affects the built-in default handlers. If you supply your own
328
- * onInsert/onUpdate/onDelete, you control the return value yourself.
327
+ * If true, the built-in handlers reload the collection's live subsets
328
+ * after a successful insert, update, or delete, before the mutation
329
+ * settles. Defaults to false: the built-in handlers write the server
330
+ * response into the synced layer before they settle, and the realtime
331
+ * subscription reconciles everything else. Set true when a server-side
332
+ * hook changes rows you must read right after the mutation.
333
+ *
334
+ * Only affects the built-in default handlers. A custom
335
+ * onInsert/onUpdate should land the server response with
336
+ * `await collection.accept(rows)` before it returns, and a custom
337
+ * onDelete should `await collection.evict(ids)`. A custom handler that
338
+ * needs a refetch can call `await collection.reload()` instead.
329
339
  *
330
340
  * @default false
331
341
  *
332
342
  * @example
333
343
  * ```ts
334
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
344
+ * const collection = createCollection<Schema>(pb)('books', {
335
345
  * refetchOnMutation: true,
336
346
  * });
337
347
  * ```
@@ -353,10 +363,10 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
353
363
  * @example
354
364
  * ```ts
355
365
  * // Default: eager mode - client-side filtering
356
- * const collection = createCollection<Schema>(pb, queryClient)('books');
366
+ * const collection = createCollection<Schema>(pb)('books');
357
367
  *
358
368
  * // On-demand mode - server-side filtering
359
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
369
+ * const collection = createCollection<Schema>(pb)('books', {
360
370
  * syncMode: 'on-demand'
361
371
  * });
362
372
  * ```
@@ -377,7 +387,7 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
377
387
  *
378
388
  * @example
379
389
  * ```ts
380
- * const books = createCollection<Schema>(pb, queryClient)('books', {
390
+ * const books = createCollection<Schema>(pb)('books', {
381
391
  * syncMode: 'on-demand',
382
392
  * realtime: 'query',
383
393
  * });
@@ -385,35 +395,39 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
385
395
  */
386
396
  realtime?: RealtimeMode;
387
397
  /**
388
- * Whether to ignore PocketBase auto-cancellation errors.
389
- *
390
- * PocketBase automatically cancels pending requests when a new request is made
391
- * to the same endpoint. This can throw ClientResponseError with a message
392
- * containing "autocancelled". When this option is true, such errors are
393
- * silently ignored and the existing cached data is returned for the cancelled request.
394
- *
395
- * @default true
396
- *
397
- * @example
398
- * ```ts
399
- * // Default: auto-cancellation errors are ignored
400
- * const collection = createCollection<Schema>(pb, queryClient)('books');
401
- *
402
- * // Explicitly handle auto-cancellation errors
403
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
404
- * ignoreAutoCancellation: false
405
- * });
406
- * ```
398
+ * How long, in milliseconds, an on-demand subset stays loaded after its
399
+ * last live query unsubscribes. A query with an equal request that mounts
400
+ * within the window reuses the rows with no request; realtime keeps them
401
+ * fresh meanwhile. Rows a parent filed through a relation follow the
402
+ * same window, and so does the accepted holder a mutation write-back or
403
+ * `accept()` gives a row. `0` releases a subset as soon as it unloads
404
+ * and keeps an accepted row until a reload or idle. A `reload()`
405
+ * releases every waiting subset.
406
+ *
407
+ * @default 5000
408
+ */
409
+ subsetGcTime?: number;
410
+ /**
411
+ * Waits, in milliseconds, before each retry of a failed subset or eager
412
+ * load. The last delay repeats until the load succeeds, so a live query
413
+ * stays loading through an outage instead of entering an error state it
414
+ * cannot leave; a `reload()` or a realtime reconnect retries at once. A
415
+ * response the server gave on purpose (a 4xx other than 401, 408 or
416
+ * 429) is not retried and reports its error; a 401 waits for the next
417
+ * auth change. Each delay carries up to a quarter of jitter either way.
418
+ * `[]` disables retries.
419
+ *
420
+ * @default [1000, 2000, 4000, 8000, 15000, 30000]
407
421
  */
408
- ignoreAutoCancellation?: boolean;
422
+ loadRetryDelays?: readonly number[];
409
423
  /**
410
424
  * Additional options passed directly to the underlying TanStack DB collection.
411
425
  * Use this to configure indexing, garbage collection, comparison functions,
412
426
  * and any other TanStack DB collection options not explicitly exposed by pbtsdb.
413
427
  *
414
- * Options set here are spread into the `queryCollectionOptions()` call.
428
+ * Options set here are spread into the TanStack DB `createCollection()` call.
415
429
  * Fields managed by pbtsdb (`getKey`, `syncMode`, `onInsert`, `onUpdate`,
416
- * `onDelete`, `schema`) are excluded from the type.
430
+ * `onDelete`, `schema`, `utils`) are excluded from the type.
417
431
  *
418
432
  * pbtsdb defaults `autoIndex` to `'eager'` with `defaultIndexType: BTreeIndex`
419
433
  * so `orderBy` + `limit` queries page lazily; both can be overridden here.
@@ -421,7 +435,7 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
421
435
  * @example
422
436
  * ```ts
423
437
  * import { BasicIndex } from 'pbtsdb'
424
- * const collection = createCollection<Schema>(pb, queryClient)('books', {
438
+ * const collection = createCollection<Schema>(pb)('books', {
425
439
  * collectionOptions: {
426
440
  * autoIndex: 'off',
427
441
  * gcTime: 60000,
@@ -429,7 +443,7 @@ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionNa
429
443
  * });
430
444
  * ```
431
445
  */
432
- collectionOptions?: Omit<Partial<BaseCollectionConfig<ExtractRecordType<Schema, CollectionName>, string | number>>, 'getKey' | 'syncMode' | 'onInsert' | 'onUpdate' | 'onDelete' | 'schema'>;
446
+ collectionOptions?: Omit<Partial<BaseCollectionConfig<ExtractRecordType<Schema, CollectionName>, string | number>>, 'getKey' | 'syncMode' | 'onInsert' | 'onUpdate' | 'onDelete' | 'schema' | 'utils'>;
433
447
  }
434
448
 
435
449
  type RelationTargets = Record<string, RelationTarget>;
@@ -452,30 +466,92 @@ interface CreateCollectionFactoryOptions {
452
466
  subscribeOptions?: () => RecordSubscribeOptions | undefined;
453
467
  }
454
468
  /**
455
- * Subscription helpers added to collection instances.
469
+ * pbtsdb's utilities on `collection.utils`. The same functions are also
470
+ * assigned on the collection itself.
471
+ */
472
+ interface PbCollectionUtils<T extends object> extends UtilsRecord {
473
+ /**
474
+ * Land rows the server returned as confirmed state, for example a custom
475
+ * endpoint's response. A row older than the stored one is ignored.
476
+ * Resolves when the rows are accepted; they become visible with the
477
+ * settlement of any persisting mutation, so a mutation handler can await it.
478
+ * Throws on an eager collection that is idle: it does not start a full load.
479
+ */
480
+ accept: (rows: readonly T[]) => Promise<void>;
481
+ /**
482
+ * Refetch every live query's subset (the whole collection in eager mode)
483
+ * and release the realtime-topic and accepted holders of rows the results
484
+ * do not confirm; rows a subset or a parent holds stay. Resolves when the
485
+ * rows are accepted; they become visible with the settlement of any
486
+ * persisting mutation.
487
+ */
488
+ reload: () => Promise<void>;
489
+ /**
490
+ * Remove rows the server deleted, for example after a custom endpoint
491
+ * deleted them. The rows leave every holder, and a fetch in flight does
492
+ * not put them back. Resolves when the removal is accepted, so a custom
493
+ * delete handler can await it. A no-op while the collection is not
494
+ * syncing.
495
+ */
496
+ evict: (ids: readonly string[]) => Promise<void>;
497
+ }
498
+ /**
499
+ * Helpers added to collection instances.
456
500
  * @internal
457
501
  */
458
- interface CollectionSubscriptionHelpers {
502
+ interface CollectionSubscriptionHelpers<T extends object> {
459
503
  /** The PocketBase collection name */
460
504
  collectionName: string;
461
505
  /** Wait for subscription to be established (useful in tests) */
462
506
  waitForSubscription: (timeout?: number) => Promise<void>;
463
507
  /** Check if collection has an active subscription */
464
508
  isSubscribed: () => boolean;
465
- /** Relation targets declared through `relations` */
509
+ /**
510
+ * Relation targets declared through `relations`. Relation plumbing, not public API.
511
+ * @internal
512
+ */
466
513
  relationTargets: RelationTargets | undefined;
467
- /** Number of relation targets currently held live */
514
+ /**
515
+ * Number of relation targets currently held live. Relation plumbing, not public API.
516
+ * @internal
517
+ */
468
518
  heldRelationTargetCount: () => number;
469
- /** Record that every row with `field === value` is now in this collection's store */
519
+ /**
520
+ * Record that every row with `field === value` is now in this collection's store. Relation plumbing, not public API.
521
+ * @internal
522
+ */
470
523
  markSubsetLoaded: (field: string, value: string) => void;
471
- /** Number of field/value pairs currently marked loaded */
524
+ /**
525
+ * Number of field/value pairs currently marked loaded. Relation plumbing, not public API.
526
+ * @internal
527
+ */
472
528
  loadedSubsetCount: () => number;
473
- /** Hold this collection live as a relation target; see RelationTarget.holdLive */
474
- holdLive: () => HeldTarget;
475
- /** Receive rows a parent expanded into this collection; see RelationTarget.writeFiled */
476
- writeFiled: (records: object[]) => Promise<boolean>;
477
- /** A parent's fetch may file a subset here; see RelationTarget.expectFiling */
529
+ /**
530
+ * Hold this collection live as a relation target; see RelationTarget.holdLive. Relation plumbing, not public API.
531
+ * @internal
532
+ */
533
+ holdLive: (holder: object) => HeldTarget;
534
+ /**
535
+ * Receive rows a parent expanded into this collection; see RelationTarget.writeFiled. Relation plumbing, not public API.
536
+ * @internal
537
+ */
538
+ writeFiled: (records: object[], holder: object) => Promise<boolean>;
539
+ /**
540
+ * Release rows a parent stopped filing here; see RelationTarget.releaseFiled. Relation plumbing, not public API.
541
+ * @internal
542
+ */
543
+ releaseFiled: (ids: readonly string[], holder: object) => void;
544
+ /**
545
+ * A parent's fetch may file a subset here; see RelationTarget.expectFiling. Relation plumbing, not public API.
546
+ * @internal
547
+ */
478
548
  expectFiling: (field: string, settles: Promise<void>) => () => void;
549
+ /** See {@link PbCollectionUtils.accept}. */
550
+ accept: PbCollectionUtils<T>['accept'];
551
+ /** See {@link PbCollectionUtils.reload}. */
552
+ reload: PbCollectionUtils<T>['reload'];
553
+ /** See {@link PbCollectionUtils.evict}. */
554
+ evict: PbCollectionUtils<T>['evict'];
479
555
  }
480
556
 
481
557
  /**
@@ -484,7 +560,7 @@ interface CollectionSubscriptionHelpers {
484
560
  * Views are created by {@link PbCollectionView.fetchRelations} and
485
561
  * {@link PbCollectionView.withRealtime}, and compose in either order.
486
562
  */
487
- type PbCollectionView<Schema extends SchemaDeclaration, C extends keyof Schema & string, Opts> = Collection<ExtractRecordType<Schema, C>, string | number, QueryCollectionUtils<ExtractRecordType<Schema, C>, string | number, ExtractRecordType<Schema, C>>, never, InsertInputOf<Schema, C, Opts>> & CollectionSubscriptionHelpers & {
563
+ type PbCollectionView<Schema extends SchemaDeclaration, C extends keyof Schema & string, Opts> = Collection<ExtractRecordType<Schema, C>, string | number, PbCollectionUtils<ExtractRecordType<Schema, C>>, never, InsertInputOf<Schema, C, Opts>> & CollectionSubscriptionHelpers<ExtractRecordType<Schema, C>> & {
488
564
  /** The PocketBase collection name */
489
565
  readonly collectionName: C;
490
566
  /** @internal phantom; never present at runtime */
@@ -516,13 +592,13 @@ type AlwaysFetchRelationsCheck<Opts> = {
516
592
  * Use this when you need fine-grained control or need to create collections with dependencies.
517
593
  *
518
594
  * @param pb - PocketBase client instance
519
- * @param queryClient - TanStack Query client
595
+ * @param factoryOptions - Options applied to every collection this factory creates
520
596
  * @returns A curried function that takes collection name and options
521
597
  *
522
598
  * @example
523
599
  * Basic usage:
524
600
  * ```ts
525
- * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {});
601
+ * const booksCollection = createCollection<Schema>(pb)('books', {});
526
602
  *
527
603
  * // Use directly
528
604
  * const books = await booksCollection.getFullList();
@@ -531,8 +607,8 @@ type AlwaysFetchRelationsCheck<Opts> = {
531
607
  * @example
532
608
  * With relations fetched on every request:
533
609
  * ```ts
534
- * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors', {});
535
- * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
610
+ * const authorsCollection = createCollection<Schema>(pb)('authors', {});
611
+ * const booksCollection = createCollection<Schema>(pb)('books', {
536
612
  * relations: { author: authorsCollection },
537
613
  * alwaysFetchRelations: ['author'],
538
614
  * });
@@ -545,7 +621,7 @@ type AlwaysFetchRelationsCheck<Opts> = {
545
621
  * @example
546
622
  * With a per-query fetchRelations view:
547
623
  * ```ts
548
- * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
624
+ * const booksCollection = createCollection<Schema>(pb)('books', {
549
625
  * relations: { author: authorsCollection },
550
626
  * });
551
627
  *
@@ -555,7 +631,7 @@ type AlwaysFetchRelationsCheck<Opts> = {
555
631
  * authorsCollection.get(data[0].author)?.name
556
632
  * ```
557
633
  */
558
- declare function createCollection<Schema extends SchemaDeclaration>(pb: PocketBase, queryClient: QueryClient, factoryOptions?: CreateCollectionFactoryOptions): <C extends keyof Schema & string, const Opts extends CreateCollectionOptions<Schema, C>>(collectionName: C, options?: Opts & AlwaysFetchRelationsCheck<Opts>) => PbCollection<Schema, C, Opts>;
634
+ declare function createCollection<Schema extends SchemaDeclaration>(pb: PocketBase, factoryOptions?: CreateCollectionFactoryOptions): <C extends keyof Schema & string, const Opts extends CreateCollectionOptions<Schema, C>>(collectionName: C, options?: Opts & AlwaysFetchRelationsCheck<Opts>) => PbCollection<Schema, C, Opts>;
559
635
 
560
636
  /**
561
637
  * Logger interface for subscription events and internal operations.
@@ -628,6 +704,76 @@ type BasicExpression<T = unknown> = IR.BasicExpression<T>;
628
704
  declare function convertToPocketBaseFilter(where: BasicExpression<boolean> | undefined | null): string | undefined;
629
705
  declare function convertToPocketBaseSort(orderBy: IR.OrderBy | undefined | null): string | undefined;
630
706
 
707
+ /** The state of pbtsdb's realtime connection for one PocketBase client. */
708
+ type RealtimeStatus =
709
+ /** `disconnectRealtime(pb)` is in effect, or no collection has subscribed yet. */
710
+ {
711
+ state: 'disabled';
712
+ }
713
+ /** The first connection of a session is opening. */
714
+ | {
715
+ state: 'connecting';
716
+ }
717
+ /** The stream is open and `PB_CONNECT` was received. */
718
+ | {
719
+ state: 'connected';
720
+ }
721
+ /**
722
+ * The stream dropped, or never came up, and a retry is scheduled.
723
+ * `attempt` is the number of the retry, `nextRetryAt` when it fires and
724
+ * `since` when the connection was lost, both epoch milliseconds.
725
+ */
726
+ | {
727
+ state: 'reconnecting';
728
+ attempt: number;
729
+ nextRetryAt: number;
730
+ since: number;
731
+ };
732
+ /** Loads that are not progressing, across every collection of one client. */
733
+ type LoadStatus = {
734
+ /** Live demands sleeping in their `loadRetryDelays` backoff. */
735
+ retrying: number;
736
+ /** When the oldest still-retrying load first failed, epoch milliseconds. */
737
+ failingSince?: number;
738
+ /** Live demands whose load ended in an error that is not retried, such as a 403 or 404. */
739
+ failed: number;
740
+ };
741
+ type SyncStatus = {
742
+ realtime: RealtimeStatus;
743
+ loads: LoadStatus;
744
+ };
745
+ type SyncStatusListener = (status: SyncStatus) => void;
746
+
747
+ /**
748
+ * Forgets the shared realtime connection's server-side session and
749
+ * reconnects under `pb`'s current auth, re-sending every subscribed topic.
750
+ * pbtsdb does this itself when `pb.authStore` changes to another auth
751
+ * record; call it for a change the store cannot see, such as a server
752
+ * switch. It also lifts {@link disconnectRealtime}. A no-op if `pb` has no
753
+ * realtime connection yet.
754
+ */
755
+ declare function resetRealtime(pb: PocketBase): void;
756
+ /**
757
+ * Closes pbtsdb's realtime connection for `pb` and keeps it closed: no
758
+ * connection opens until {@link resetRealtime}. Collections keep working
759
+ * over REST and keep their subscriptions registered, so a later
760
+ * `resetRealtime(pb)` resumes every topic and reloads every ready
761
+ * collection. Use it at logout, or at startup where realtime is not wanted.
762
+ */
763
+ declare function disconnectRealtime(pb: PocketBase): void;
764
+ /**
765
+ * The sync state of `pb`'s collections: whether pbtsdb's realtime stream
766
+ * is up, and how many live loads are stuck in retry or ended in an error
767
+ * the server meant. The snapshot is stable until a value changes, so it
768
+ * suits `useSyncExternalStore`; see {@link subscribeSyncStatus}.
769
+ */
770
+ declare function getSyncStatus(pb: PocketBase): SyncStatus;
771
+ /**
772
+ * Calls `listener` with each new {@link getSyncStatus} snapshot. Returns
773
+ * the unsubscribe function.
774
+ */
775
+ declare function subscribeSyncStatus(pb: PocketBase, listener: SyncStatusListener): () => void;
776
+
631
777
  /**
632
778
  * Generates a new PocketBase-compatible record ID.
633
779
  * Returns a 15-character alphanumeric string (lowercase letters and numbers).
@@ -643,4 +789,4 @@ declare function convertToPocketBaseSort(orderBy: IR.OrderBy | undefined | null)
643
789
  */
644
790
  declare function newRecordId(): string;
645
791
 
646
- export { type CreateCollectionFactoryOptions, type CreateCollectionOptions, type ExcludeUndefined, type ExpandPath, type ExtractRecordType, type ExtractRelations, type Logger, type OmittableFields, type PbCollection, type PbCollectionView, type RealtimeMode, type RelationAsCollection, type RelationsConfig, type SchemaDeclaration, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, newRecordId, resetLogger, setLogger };
792
+ export { type CreateCollectionFactoryOptions, type CreateCollectionOptions, type ExcludeUndefined, type ExpandPath, type ExtractRecordType, type ExtractRelations, type LoadStatus, type Logger, type OmittableFields, type PbCollection, type PbCollectionUtils, type PbCollectionView, type RealtimeMode, type RealtimeStatus, type RelationAsCollection, type RelationsConfig, type SchemaDeclaration, type SyncStatus, type SyncStatusListener, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, disconnectRealtime, getSyncStatus, newRecordId, resetLogger, resetRealtime, setLogger, subscribeSyncStatus };
package/dist/core.js CHANGED
@@ -1,3 +1,3 @@
1
- export { BTreeIndex, BasicIndex, ReverseIndex, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, createEffect, materialize, newRecordId, resetLogger, setLogger, toArray } from './chunk-OBDOVDCI.js';
1
+ export { BTreeIndex, BasicIndex, ReverseIndex, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, createEffect, disconnectRealtime, getSyncStatus, materialize, newRecordId, resetLogger, resetRealtime, setLogger, subscribeSyncStatus, toArray } from './chunk-TTENGDS3.js';
2
2
  //# sourceMappingURL=core.js.map
3
3
  //# sourceMappingURL=core.js.map
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export { BTreeIndex, BasicIndex, DeltaEvent, DeltaType, EffectConfig, EffectContext, IndexConstructor, ReverseIndex, createEffect, materialize, toArray } from '@tanstack/db';
2
+ import PocketBase from 'pocketbase';
2
3
  export { RecordSubscribeOptions } from 'pocketbase';
3
- export { CreateCollectionFactoryOptions, CreateCollectionOptions, ExcludeUndefined, ExpandPath, ExtractRecordType, ExtractRelations, Logger, OmittableFields, PbCollection, PbCollectionView, RealtimeMode, RelationAsCollection, RelationsConfig, SchemaDeclaration, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, newRecordId, resetLogger, setLogger } from './core.js';
4
+ import { SyncStatus } from './core.js';
5
+ export { CreateCollectionFactoryOptions, CreateCollectionOptions, ExcludeUndefined, ExpandPath, ExtractRecordType, ExtractRelations, LoadStatus, Logger, OmittableFields, PbCollection, PbCollectionUtils, PbCollectionView, RealtimeMode, RealtimeStatus, RelationAsCollection, RelationsConfig, SchemaDeclaration, SyncStatusListener, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, disconnectRealtime, getSyncStatus, newRecordId, resetLogger, resetRealtime, setLogger, subscribeSyncStatus } from './core.js';
4
6
  import React, { ReactNode } from 'react';
5
- import '@tanstack/query-db-collection';
6
- import '@tanstack/react-query';
7
7
 
8
8
  /**
9
9
  * UseStore hook type for variadic collection access.
@@ -51,7 +51,7 @@ interface ReactProviderResult<CollectionsMap> {
51
51
  * import { useLiveQuery } from '@tanstack/react-db';
52
52
  *
53
53
  * // Step 1: Create collections
54
- * const c = createCollection<Schema>(pb, queryClient);
54
+ * const c = createCollection<Schema>(pb);
55
55
  * const collections = {
56
56
  * books: c('books', {}),
57
57
  * authors: c('authors', {}),
@@ -63,11 +63,9 @@ interface ReactProviderResult<CollectionsMap> {
63
63
  * // Step 3: Wrap your app
64
64
  * function App() {
65
65
  * return (
66
- * <QueryClientProvider client={queryClient}>
67
- * <Provider>
68
- * <BooksList />
69
- * </Provider>
70
- * </QueryClientProvider>
66
+ * <Provider>
67
+ * <BooksList />
68
+ * </Provider>
71
69
  * );
72
70
  * }
73
71
  *
@@ -101,7 +99,7 @@ interface ReactProviderResult<CollectionsMap> {
101
99
  * @example
102
100
  * With relations filed via alwaysFetchRelations:
103
101
  * ```tsx
104
- * const c = createCollection<Schema>(pb, queryClient);
102
+ * const c = createCollection<Schema>(pb);
105
103
  * const authors = c('authors', { syncMode: 'on-demand' });
106
104
  * const books = c('books', {
107
105
  * relations: { author: authors },
@@ -134,5 +132,21 @@ interface ReactProviderResult<CollectionsMap> {
134
132
  * ```
135
133
  */
136
134
  declare function createReactProvider<CollectionsMap extends Record<string, unknown>>(collections: CollectionsMap): ReactProviderResult<CollectionsMap>;
135
+ /**
136
+ * The current {@link SyncStatus} of `pb`'s collections, re-rendering on
137
+ * each change. Use it for a connectivity notice: the realtime stream
138
+ * dropping while REST still works, or queries stuck in retry.
139
+ *
140
+ * @example
141
+ * ```tsx
142
+ * function SyncNotice() {
143
+ * const { realtime, loads } = useSyncStatus(pb);
144
+ * if (realtime.state === 'reconnecting') return <p>Reconnecting…</p>;
145
+ * if (loads.retrying > 0) return <p>Retrying {loads.retrying} queries</p>;
146
+ * return null;
147
+ * }
148
+ * ```
149
+ */
150
+ declare function useSyncStatus(pb: PocketBase): SyncStatus;
137
151
 
138
- export { type ReactProviderResult, createReactProvider };
152
+ export { type ReactProviderResult, SyncStatus, createReactProvider, useSyncStatus };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- export { BTreeIndex, BasicIndex, ReverseIndex, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, createEffect, materialize, newRecordId, resetLogger, setLogger, toArray } from './chunk-OBDOVDCI.js';
2
- import { createContext, useContext } from 'react';
1
+ import { subscribeSyncStatus, getSyncStatus } from './chunk-TTENGDS3.js';
2
+ export { BTreeIndex, BasicIndex, ReverseIndex, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, createEffect, disconnectRealtime, getSyncStatus, materialize, newRecordId, resetLogger, resetRealtime, setLogger, subscribeSyncStatus, toArray } from './chunk-TTENGDS3.js';
3
+ import { createContext, useSyncExternalStore, useContext } from 'react';
3
4
  import { jsx } from 'react/jsx-runtime';
4
5
 
5
6
  function createReactProvider(collections) {
@@ -24,7 +25,14 @@ function createReactProvider(collections) {
24
25
  useStore
25
26
  };
26
27
  }
28
+ function useSyncStatus(pb) {
29
+ return useSyncExternalStore(
30
+ (listener) => subscribeSyncStatus(pb, listener),
31
+ () => getSyncStatus(pb),
32
+ () => getSyncStatus(pb)
33
+ );
34
+ }
27
35
 
28
- export { createReactProvider };
36
+ export { createReactProvider, useSyncStatus };
29
37
  //# sourceMappingURL=index.js.map
30
38
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.tsx"],"names":[],"mappings":";;;;AAmIO,SAAS,oBACZ,WAAA,EACmC;AACnC,EAAA,MAAM,OAAA,GAAU,cAAqC,IAAI,CAAA;AAEzD,EAAA,MAAM,QAAA,GAA8C,CAAC,EAAE,QAAA,EAAS,qBAC5D,GAAA,CAAC,OAAA,CAAQ,QAAA,EAAR,EAAiB,KAAA,EAAO,WAAA,EAAc,QAAA,EAAS,CAAA;AAGpD,EAAA,SAAS,YACF,IAAA,EACiF;AACpF,IAAA,MAAM,OAAA,GAAU,WAAW,OAAO,CAAA;AAElC,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA,MAAM,IAAI,KAAA;AAAA,QACN;AAAA,OACJ;AAAA,IACJ;AAEA,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,GAAA,KAAO;AACnB,MAAA,IAAI,EAAE,OAAO,OAAA,CAAA,EAAU;AACnB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,MAAA,CAAO,GAAG,CAAC,CAAA,0BAAA,CAA4B,CAAA;AAAA,MAC1E;AACA,MAAA,OAAO,QAAQ,GAAG,CAAA;AAAA,IACtB,CAAC,CAAA;AAAA,EACL;AAEA,EAAA,OAAO;AAAA,IACH,QAAA;AAAA,IACA;AAAA,GACJ;AACJ","file":"index.js","sourcesContent":["import React, { createContext, type ReactNode, useContext } from 'react'\n\n/**\n * UseStore hook type for variadic collection access.\n * @internal\n */\ntype UseStoreFn<CollectionsMap> = <K extends (keyof CollectionsMap)[]>(\n ...keys: K\n) => { [I in keyof K]: K[I] extends keyof CollectionsMap ? CollectionsMap[K[I]] : never }\n\n/**\n * Return type for createReactProvider function.\n */\nexport interface ReactProviderResult<CollectionsMap> {\n /**\n * React Context Provider component.\n * Wrap your app with this provider to make collections available to useStore.\n */\n Provider: React.FC<{ children: ReactNode }>\n\n /**\n * Hook to access collections from the provider.\n * Uses variadic arguments for clean syntax with automatic type inference.\n *\n * @example\n * ```tsx\n * // Single collection\n * const [books] = useStore('books');\n *\n * // Multiple collections (no 'as const' needed!)\n * const [books, authors] = useStore('books', 'authors');\n * ```\n */\n useStore: UseStoreFn<CollectionsMap>\n}\n\n/**\n * Creates a React Provider and useStore hook from a collections map.\n *\n * @param collections - Map of collection keys to Collection instances\n * @returns Object containing Provider component and useStore hook\n *\n * @example\n * Basic usage:\n * ```tsx\n * import { createCollection, createReactProvider } from 'pbtsdb';\n * import { useLiveQuery } from '@tanstack/react-db';\n *\n * // Step 1: Create collections\n * const c = createCollection<Schema>(pb, queryClient);\n * const collections = {\n * books: c('books', {}),\n * authors: c('authors', {}),\n * };\n *\n * // Step 2: Wrap for React\n * const { Provider, useStore } = createReactProvider(collections);\n *\n * // Step 3: Wrap your app\n * function App() {\n * return (\n * <QueryClientProvider client={queryClient}>\n * <Provider>\n * <BooksList />\n * </Provider>\n * </QueryClientProvider>\n * );\n * }\n *\n * // Step 4: Use in components\n * function BooksList() {\n * const [books] = useStore('books');\n * const { data } = useLiveQuery((q) => q.from({ books }));\n * return <div>{data?.map(book => <p key={book.id}>{book.title}</p>)}</div>;\n * }\n * ```\n *\n * @example\n * Variadic useStore pattern:\n * ```tsx\n * function BooksWithAuthors() {\n * const [books, authors] = useStore('books', 'authors');\n *\n * const { data } = useLiveQuery((q) =>\n * q.from({ book: books })\n * .join(\n * { author: authors },\n * ({ book, author }) => eq(book.author, author.id),\n * 'left'\n * )\n * );\n *\n * return <div>...</div>;\n * }\n * ```\n *\n * @example\n * With relations filed via alwaysFetchRelations:\n * ```tsx\n * const c = createCollection<Schema>(pb, queryClient);\n * const authors = c('authors', { syncMode: 'on-demand' });\n * const books = c('books', {\n * relations: { author: authors },\n * alwaysFetchRelations: ['author'],\n * });\n *\n * const { Provider, useStore } = createReactProvider({ authors, books });\n *\n * function BooksWithAuthors() {\n * const [books, authors] = useStore('books', 'authors');\n * const { data } = useLiveQuery((q) =>\n * q.from({ b: books }).select(({ b }) => ({\n * ...b,\n * author: materialize(\n * q.from({ a: authors }).where(({ a }) => eq(a.id, b.author)).findOne()\n * ),\n * }))\n * );\n *\n * return (\n * <ul>\n * {data?.map(row => (\n * <li key={row.id}>\n * {row.title} by {row.author?.name}\n * </li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function createReactProvider<CollectionsMap extends Record<string, unknown>>(\n collections: CollectionsMap\n): ReactProviderResult<CollectionsMap> {\n const Context = createContext<CollectionsMap | null>(null)\n\n const Provider: React.FC<{ children: ReactNode }> = ({ children }) => (\n <Context.Provider value={collections}>{children}</Context.Provider>\n )\n\n function useStore<K extends (keyof CollectionsMap)[]>(\n ...keys: K\n ): { [I in keyof K]: K[I] extends keyof CollectionsMap ? CollectionsMap[K[I]] : never } {\n const context = useContext(Context)\n\n if (!context) {\n throw new Error(\n 'useStore must be used within the Provider returned by createReactProvider'\n )\n }\n\n return keys.map(key => {\n if (!(key in context)) {\n throw new Error(`Collection \"${String(key)}\" not found in collections`)\n }\n return context[key]\n }) as { [I in keyof K]: K[I] extends keyof CollectionsMap ? CollectionsMap[K[I]] : never }\n }\n\n return {\n Provider,\n useStore: useStore as UseStoreFn<CollectionsMap>,\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/react.tsx"],"names":[],"mappings":";;;;;AAoIO,SAAS,oBACZ,WAAA,EACmC;AACnC,EAAA,MAAM,OAAA,GAAU,cAAqC,IAAI,CAAA;AAEzD,EAAA,MAAM,QAAA,GAA8C,CAAC,EAAE,QAAA,EAAS,qBAC5D,GAAA,CAAC,OAAA,CAAQ,QAAA,EAAR,EAAiB,KAAA,EAAO,WAAA,EAAc,QAAA,EAAS,CAAA;AAGpD,EAAA,SAAS,YACF,IAAA,EACiF;AACpF,IAAA,MAAM,OAAA,GAAU,WAAW,OAAO,CAAA;AAElC,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA,MAAM,IAAI,KAAA;AAAA,QACN;AAAA,OACJ;AAAA,IACJ;AAEA,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,GAAA,KAAO;AACnB,MAAA,IAAI,EAAE,OAAO,OAAA,CAAA,EAAU;AACnB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,YAAA,EAAe,MAAA,CAAO,GAAG,CAAC,CAAA,0BAAA,CAA4B,CAAA;AAAA,MAC1E;AACA,MAAA,OAAO,QAAQ,GAAG,CAAA;AAAA,IACtB,CAAC,CAAA;AAAA,EACL;AAEA,EAAA,OAAO;AAAA,IACH,QAAA;AAAA,IACA;AAAA,GACJ;AACJ;AAiBO,SAAS,cAAc,EAAA,EAA4B;AACtD,EAAA,OAAO,oBAAA;AAAA,IACH,CAAA,QAAA,KAAY,mBAAA,CAAoB,EAAA,EAAI,QAAQ,CAAA;AAAA,IAC5C,MAAM,cAAc,EAAE,CAAA;AAAA,IACtB,MAAM,cAAc,EAAE;AAAA,GAC1B;AACJ","file":"index.js","sourcesContent":["import type PocketBase from 'pocketbase'\nimport React, { createContext, type ReactNode, useContext, useSyncExternalStore } from 'react'\nimport type { SyncStatus } from './sync-status'\nimport { getSyncStatus, subscribeSyncStatus } from './transport'\n\n/**\n * UseStore hook type for variadic collection access.\n * @internal\n */\ntype UseStoreFn<CollectionsMap> = <K extends (keyof CollectionsMap)[]>(\n ...keys: K\n) => { [I in keyof K]: K[I] extends keyof CollectionsMap ? CollectionsMap[K[I]] : never }\n\n/**\n * Return type for createReactProvider function.\n */\nexport interface ReactProviderResult<CollectionsMap> {\n /**\n * React Context Provider component.\n * Wrap your app with this provider to make collections available to useStore.\n */\n Provider: React.FC<{ children: ReactNode }>\n\n /**\n * Hook to access collections from the provider.\n * Uses variadic arguments for clean syntax with automatic type inference.\n *\n * @example\n * ```tsx\n * // Single collection\n * const [books] = useStore('books');\n *\n * // Multiple collections (no 'as const' needed!)\n * const [books, authors] = useStore('books', 'authors');\n * ```\n */\n useStore: UseStoreFn<CollectionsMap>\n}\n\n/**\n * Creates a React Provider and useStore hook from a collections map.\n *\n * @param collections - Map of collection keys to Collection instances\n * @returns Object containing Provider component and useStore hook\n *\n * @example\n * Basic usage:\n * ```tsx\n * import { createCollection, createReactProvider } from 'pbtsdb';\n * import { useLiveQuery } from '@tanstack/react-db';\n *\n * // Step 1: Create collections\n * const c = createCollection<Schema>(pb);\n * const collections = {\n * books: c('books', {}),\n * authors: c('authors', {}),\n * };\n *\n * // Step 2: Wrap for React\n * const { Provider, useStore } = createReactProvider(collections);\n *\n * // Step 3: Wrap your app\n * function App() {\n * return (\n * <Provider>\n * <BooksList />\n * </Provider>\n * );\n * }\n *\n * // Step 4: Use in components\n * function BooksList() {\n * const [books] = useStore('books');\n * const { data } = useLiveQuery((q) => q.from({ books }));\n * return <div>{data?.map(book => <p key={book.id}>{book.title}</p>)}</div>;\n * }\n * ```\n *\n * @example\n * Variadic useStore pattern:\n * ```tsx\n * function BooksWithAuthors() {\n * const [books, authors] = useStore('books', 'authors');\n *\n * const { data } = useLiveQuery((q) =>\n * q.from({ book: books })\n * .join(\n * { author: authors },\n * ({ book, author }) => eq(book.author, author.id),\n * 'left'\n * )\n * );\n *\n * return <div>...</div>;\n * }\n * ```\n *\n * @example\n * With relations filed via alwaysFetchRelations:\n * ```tsx\n * const c = createCollection<Schema>(pb);\n * const authors = c('authors', { syncMode: 'on-demand' });\n * const books = c('books', {\n * relations: { author: authors },\n * alwaysFetchRelations: ['author'],\n * });\n *\n * const { Provider, useStore } = createReactProvider({ authors, books });\n *\n * function BooksWithAuthors() {\n * const [books, authors] = useStore('books', 'authors');\n * const { data } = useLiveQuery((q) =>\n * q.from({ b: books }).select(({ b }) => ({\n * ...b,\n * author: materialize(\n * q.from({ a: authors }).where(({ a }) => eq(a.id, b.author)).findOne()\n * ),\n * }))\n * );\n *\n * return (\n * <ul>\n * {data?.map(row => (\n * <li key={row.id}>\n * {row.title} by {row.author?.name}\n * </li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function createReactProvider<CollectionsMap extends Record<string, unknown>>(\n collections: CollectionsMap\n): ReactProviderResult<CollectionsMap> {\n const Context = createContext<CollectionsMap | null>(null)\n\n const Provider: React.FC<{ children: ReactNode }> = ({ children }) => (\n <Context.Provider value={collections}>{children}</Context.Provider>\n )\n\n function useStore<K extends (keyof CollectionsMap)[]>(\n ...keys: K\n ): { [I in keyof K]: K[I] extends keyof CollectionsMap ? CollectionsMap[K[I]] : never } {\n const context = useContext(Context)\n\n if (!context) {\n throw new Error(\n 'useStore must be used within the Provider returned by createReactProvider'\n )\n }\n\n return keys.map(key => {\n if (!(key in context)) {\n throw new Error(`Collection \"${String(key)}\" not found in collections`)\n }\n return context[key]\n }) as { [I in keyof K]: K[I] extends keyof CollectionsMap ? CollectionsMap[K[I]] : never }\n }\n\n return {\n Provider,\n useStore: useStore as UseStoreFn<CollectionsMap>,\n }\n}\n\n/**\n * The current {@link SyncStatus} of `pb`'s collections, re-rendering on\n * each change. Use it for a connectivity notice: the realtime stream\n * dropping while REST still works, or queries stuck in retry.\n *\n * @example\n * ```tsx\n * function SyncNotice() {\n * const { realtime, loads } = useSyncStatus(pb);\n * if (realtime.state === 'reconnecting') return <p>Reconnecting…</p>;\n * if (loads.retrying > 0) return <p>Retrying {loads.retrying} queries</p>;\n * return null;\n * }\n * ```\n */\nexport function useSyncStatus(pb: PocketBase): SyncStatus {\n return useSyncExternalStore(\n listener => subscribeSyncStatus(pb, listener),\n () => getSyncStatus(pb),\n () => getSyncStatus(pb)\n )\n}\n"]}