pbtsdb 2.0.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
@@ -704,6 +704,46 @@ type BasicExpression<T = unknown> = IR.BasicExpression<T>;
704
704
  declare function convertToPocketBaseFilter(where: BasicExpression<boolean> | undefined | null): string | undefined;
705
705
  declare function convertToPocketBaseSort(orderBy: IR.OrderBy | undefined | null): string | undefined;
706
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
+
707
747
  /**
708
748
  * Forgets the shared realtime connection's server-side session and
709
749
  * reconnects under `pb`'s current auth, re-sending every subscribed topic.
@@ -721,6 +761,18 @@ declare function resetRealtime(pb: PocketBase): void;
721
761
  * collection. Use it at logout, or at startup where realtime is not wanted.
722
762
  */
723
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;
724
776
 
725
777
  /**
726
778
  * Generates a new PocketBase-compatible record ID.
@@ -737,4 +789,4 @@ declare function disconnectRealtime(pb: PocketBase): void;
737
789
  */
738
790
  declare function newRecordId(): string;
739
791
 
740
- export { type CreateCollectionFactoryOptions, type CreateCollectionOptions, type ExcludeUndefined, type ExpandPath, type ExtractRecordType, type ExtractRelations, type Logger, type OmittableFields, type PbCollection, type PbCollectionUtils, type PbCollectionView, type RealtimeMode, type RelationAsCollection, type RelationsConfig, type SchemaDeclaration, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, disconnectRealtime, newRecordId, resetLogger, resetRealtime, 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, disconnectRealtime, materialize, newRecordId, resetLogger, resetRealtime, setLogger, toArray } from './chunk-4CRCARB3.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,6 +1,8 @@
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, PbCollectionUtils, PbCollectionView, RealtimeMode, RelationAsCollection, RelationsConfig, SchemaDeclaration, convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, disconnectRealtime, newRecordId, resetLogger, resetRealtime, 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
7
 
6
8
  /**
@@ -130,5 +132,21 @@ interface ReactProviderResult<CollectionsMap> {
130
132
  * ```
131
133
  */
132
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;
133
151
 
134
- 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, disconnectRealtime, materialize, newRecordId, resetLogger, resetRealtime, setLogger, toArray } from './chunk-4CRCARB3.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":";;;;AAiIO,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);\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"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pbtsdb",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Type-safe PocketBase integration with TanStack DB",
5
5
  "keywords": [
6
6
  "pocketbase",