pbtsdb 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,7 @@ A TypeScript library that seamlessly integrates [PocketBase](https://pocketbase.
25
25
  - [Basic Queries](#basic-queries)
26
26
  - [Filtering and Sorting](#filtering-and-sorting)
27
27
  - [Relations and Joins](#relations-and-joins)
28
+ - [Includes (Nested Subqueries)](#includes-nested-subqueries)
28
29
  - [Real-time Updates](#real-time-updates)
29
30
  - [Mutations](#mutations)
30
31
  - [TypeScript](#typescript)
@@ -306,6 +307,8 @@ const collection = c(collectionName: string, options?: CreateCollectionOptions);
306
307
  - `onInsert?: InsertMutationFn | false` - Custom insert handler or `false` to disable
307
308
  - `onUpdate?: UpdateMutationFn | false` - Custom update handler or `false` to disable
308
309
  - `onDelete?: DeleteMutationFn | false` - Custom delete handler or `false` to disable
310
+ - `ignoreAutoCancellation?: boolean` - Ignore PocketBase auto-cancellation errors (default: `true`)
311
+ - `collectionOptions?: object` - Additional TanStack DB collection options passed through directly (see [Collection Options Passthrough](#collection-options-passthrough))
309
312
 
310
313
  **Returns:** Fully-typed Collection instance with subscription capabilities
311
314
 
@@ -333,6 +336,26 @@ const { data } = useLiveQuery((q) => q.from({ books: booksCollection }));
333
336
  // Expanded records auto-inserted into authorsCollection
334
337
  ```
335
338
 
339
+ #### Collection Options Passthrough
340
+
341
+ Pass any [TanStack DB `BaseCollectionConfig`](https://tanstack.com/db/latest/docs/overview) option directly via `collectionOptions`. This is useful for configuring indexing, garbage collection, and other collection-level settings:
342
+
343
+ ```typescript
344
+ import { BasicIndex } from 'pbtsdb';
345
+
346
+ const c = createCollection<MySchema>(pb, queryClient);
347
+ const booksCollection = c('books', {
348
+ collectionOptions: {
349
+ autoIndex: 'eager',
350
+ defaultIndexType: BasicIndex,
351
+ gcTime: 60000, // 1 minute GC
352
+ startSync: true, // Start syncing immediately
353
+ }
354
+ });
355
+ ```
356
+
357
+ The following fields are managed by pbtsdb and excluded from `collectionOptions`: `getKey`, `syncMode`, `onInsert`, `onUpdate`, `onDelete`, `schema`.
358
+
336
359
  ### React Integration
337
360
 
338
361
  #### createReactProvider()
@@ -480,6 +503,30 @@ booksCollection.insert(newBook);
480
503
 
481
504
  **Returns:** `string` - 15-character lowercase alphanumeric ID
482
505
 
506
+ #### Re-exported TanStack DB Utilities
507
+
508
+ pbtsdb re-exports commonly used TanStack DB utilities so you don't need to depend on `@tanstack/db` directly:
509
+
510
+ ```typescript
511
+ import {
512
+ // Includes helpers
513
+ toArray,
514
+ createEffect,
515
+
516
+ // Index types (for collectionOptions.defaultIndexType)
517
+ BasicIndex,
518
+ BTreeIndex,
519
+ ReverseIndex,
520
+
521
+ // Types
522
+ type DeltaEvent,
523
+ type DeltaType,
524
+ type EffectConfig,
525
+ type EffectContext,
526
+ type IndexConstructor,
527
+ } from 'pbtsdb';
528
+ ```
529
+
483
530
  ## Usage Examples
484
531
 
485
532
  ### Example 1: Task Manager with Filtering
@@ -739,6 +786,78 @@ export function ProjectDashboard({ projectId }: { projectId: string }) {
739
786
 
740
787
  Demonstrates variadic `useStore()`, client-side aggregations, and TanStack DB joins.
741
788
 
789
+ ### Includes (Nested Subqueries)
790
+
791
+ TanStack DB 0.6.0 introduces **includes** — nested subqueries within `select()` that project normalized data into hierarchical shapes. This is useful when you want to compose related data from multiple collections reactively.
792
+
793
+ #### Single relation with `findOne()`
794
+
795
+ ```typescript
796
+ import { useLiveQuery, eq } from '@tanstack/react-db';
797
+
798
+ const { data: booksWithAuthors } = useLiveQuery((q) =>
799
+ q.from({ b: booksCollection }).select(({ b }) => ({
800
+ id: b.id,
801
+ title: b.title,
802
+ author: q
803
+ .from({ a: authorsCollection })
804
+ .where(({ a }) => eq(a.id, b.author))
805
+ .select(({ a }) => ({ id: a.id, name: a.name }))
806
+ .findOne(),
807
+ }))
808
+ );
809
+ ```
810
+
811
+ #### Many relation with `toArray()`
812
+
813
+ ```typescript
814
+ import { useLiveQuery, eq, toArray } from '@tanstack/react-db';
815
+ // Or: import { toArray } from 'pbtsdb';
816
+
817
+ const { data: booksWithTags } = useLiveQuery((q) =>
818
+ q.from({ b: booksCollection }).select(({ b }) => ({
819
+ id: b.id,
820
+ title: b.title,
821
+ tags: toArray(
822
+ q.from({ bt: bookTagsCollection })
823
+ .where(({ bt }) => eq(bt.book, b.id))
824
+ .join({ t: tagsCollection }, ({ bt, t }) => eq(bt.tag, t.id))
825
+ .select(({ t }) => ({ id: t.id, name: t.name }))
826
+ ),
827
+ }))
828
+ );
829
+ ```
830
+
831
+ #### Combining expand with includes
832
+
833
+ Use PocketBase's `expand` to auto-populate a related collection, then use includes to query from it:
834
+
835
+ ```typescript
836
+ const authorsCollection = c('authors', { syncMode: 'on-demand' });
837
+ const booksCollection = c('books', {
838
+ syncMode: 'on-demand',
839
+ expand: { author: authorsCollection }, // Auto-populates authorsCollection
840
+ });
841
+
842
+ const { data } = useLiveQuery((q) =>
843
+ q.from({ b: booksCollection }).select(({ b }) => ({
844
+ id: b.id,
845
+ title: b.title,
846
+ // Query from the expand-populated authorsCollection
847
+ author: q.from({ a: authorsCollection })
848
+ .where(({ a }) => eq(a.id, b.author))
849
+ .select(({ a }) => ({ id: a.id, name: a.name }))
850
+ .findOne(),
851
+ tags: toArray(
852
+ q.from({ bt: bookTagsCollection })
853
+ .where(({ bt }) => eq(bt.book, b.id))
854
+ .join({ t: tagsCollection }, ({ bt, t }) => eq(bt.tag, t.id))
855
+ .select(({ t }) => ({ id: t.id, name: t.name }))
856
+ ),
857
+ }))
858
+ );
859
+ ```
860
+
742
861
  ## TypeScript
743
862
 
744
863
  pbtsdb is fully type-safe. Here's what you need to know:
@@ -0,0 +1,349 @@
1
+ import { createCollection as createCollection$1, parseWhereExpression, parseOrderByExpression } from '@tanstack/db';
2
+ export { BTreeIndex, BasicIndex, ReverseIndex, createEffect, toArray } from '@tanstack/db';
3
+ import { queryCollectionOptions } from '@tanstack/query-db-collection';
4
+
5
+ // src/collection.ts
6
+ function escapeValue(value) {
7
+ if (value === null) {
8
+ return "null";
9
+ }
10
+ if (typeof value === "boolean") {
11
+ return value ? "true" : "false";
12
+ }
13
+ if (typeof value === "number") {
14
+ return value.toString();
15
+ }
16
+ if (typeof value === "string") {
17
+ return `"${value.replace(/"/g, '\\"')}"`;
18
+ }
19
+ if (value instanceof Date) {
20
+ return `"${value.toISOString()}"`;
21
+ }
22
+ if (Array.isArray(value)) {
23
+ return `[${value.map(escapeValue).join(",")}]`;
24
+ }
25
+ return `"${String(value)}"`;
26
+ }
27
+ function fieldPathToString(path) {
28
+ return path.join(".");
29
+ }
30
+ function convertToPocketBaseFilter(where) {
31
+ if (!where) {
32
+ return void 0;
33
+ }
34
+ const result = parseWhereExpression(where, {
35
+ handlers: {
36
+ eq: (field, value) => {
37
+ return `${fieldPathToString(field)} = ${escapeValue(value)}`;
38
+ },
39
+ gt: (field, value) => {
40
+ return `${fieldPathToString(field)} > ${escapeValue(value)}`;
41
+ },
42
+ gte: (field, value) => {
43
+ return `${fieldPathToString(field)} >= ${escapeValue(value)}`;
44
+ },
45
+ lt: (field, value) => {
46
+ return `${fieldPathToString(field)} < ${escapeValue(value)}`;
47
+ },
48
+ lte: (field, value) => {
49
+ return `${fieldPathToString(field)} <= ${escapeValue(value)}`;
50
+ },
51
+ and: (...conditions) => {
52
+ if (conditions.length === 0) return "";
53
+ if (conditions.length === 1) return conditions[0];
54
+ return `(${conditions.join(" && ")})`;
55
+ },
56
+ or: (...conditions) => {
57
+ if (conditions.length === 0) return "";
58
+ if (conditions.length === 1) return conditions[0];
59
+ return `(${conditions.join(" || ")})`;
60
+ },
61
+ not: (condition) => {
62
+ return `!(${condition})`;
63
+ },
64
+ in: (field, values) => {
65
+ const valueArray = Array.isArray(values) ? values : [values];
66
+ const uniqueValues = [...new Set(valueArray)];
67
+ const fieldStr = fieldPathToString(field);
68
+ const conditions = uniqueValues.map((v) => `${fieldStr} = ${escapeValue(v)}`);
69
+ return conditions.length > 1 ? `(${conditions.join(" || ")})` : conditions[0];
70
+ },
71
+ like: (field, value) => {
72
+ return `${fieldPathToString(field)} ~ ${escapeValue(value)}`;
73
+ },
74
+ isNull: (field) => {
75
+ return `${fieldPathToString(field)} = null`;
76
+ },
77
+ isUndefined: (field) => {
78
+ return `${fieldPathToString(field)} = null`;
79
+ }
80
+ },
81
+ onUnknownOperator: (operator, args) => {
82
+ throw new Error(
83
+ `Unsupported operator '${operator}' for PocketBase filter conversion. Supported operators: eq, gt, gte, lt, lte, in, like, and, or, not, isNull, isUndefined`
84
+ );
85
+ }
86
+ });
87
+ return result || void 0;
88
+ }
89
+ function convertToPocketBaseSort(orderBy) {
90
+ if (!orderBy) {
91
+ return void 0;
92
+ }
93
+ const sorts = parseOrderByExpression(orderBy);
94
+ if (sorts.length === 0) {
95
+ return void 0;
96
+ }
97
+ return sorts.map((sort) => {
98
+ const field = fieldPathToString(sort.field);
99
+ return sort.direction === "desc" ? `-${field}` : field;
100
+ }).join(",");
101
+ }
102
+
103
+ // src/logger.ts
104
+ var defaultLogger = {
105
+ debug: (msg, context) => {
106
+ if (process.env.NODE_ENV === "development") {
107
+ console.log(`[pbtsdb] ${msg}`, context || "");
108
+ }
109
+ },
110
+ info: (msg, context) => {
111
+ console.info(`[pbtsdb] ${msg}`, context || "");
112
+ },
113
+ warn: (msg, context) => {
114
+ console.warn(`[pbtsdb] ${msg}`, context || "");
115
+ },
116
+ error: (msg, context) => {
117
+ console.error(`[pbtsdb] ${msg}`, context || "");
118
+ }
119
+ };
120
+ var currentLogger = defaultLogger;
121
+ var logger = {
122
+ debug: (msg, context) => currentLogger.debug(msg, context),
123
+ info: (msg, context) => currentLogger.info(msg, context),
124
+ warn: (msg, context) => currentLogger.warn(msg, context),
125
+ error: (msg, context) => currentLogger.error(msg, context)
126
+ };
127
+ function setLogger(customLogger) {
128
+ currentLogger = customLogger;
129
+ }
130
+ function resetLogger() {
131
+ currentLogger = defaultLogger;
132
+ }
133
+
134
+ // src/collection.ts
135
+ function createCollection(pb, queryClient) {
136
+ return (collectionName, options) => {
137
+ const expandStores = options?.expand;
138
+ const expandString = expandStores ? Object.keys(expandStores).sort().join(",") : void 0;
139
+ const ignoreAutoCancellation = options?.ignoreAutoCancellation ?? true;
140
+ async function fetchRecords(loadOptions) {
141
+ const filter = convertToPocketBaseFilter(loadOptions?.where);
142
+ const sort = convertToPocketBaseSort(loadOptions?.orderBy);
143
+ const limit = loadOptions?.limit;
144
+ let items;
145
+ try {
146
+ if (limit) {
147
+ const result = await pb.collection(collectionName).getList(1, limit, {
148
+ filter,
149
+ sort,
150
+ skipTotal: true,
151
+ // Optimize by skipping total count
152
+ expand: expandString
153
+ });
154
+ items = result.items;
155
+ } else {
156
+ items = await pb.collection(collectionName).getFullList({
157
+ filter,
158
+ sort,
159
+ expand: expandString
160
+ });
161
+ }
162
+ } catch (error) {
163
+ if (ignoreAutoCancellation && error instanceof Error && error.message.includes("autocancelled")) {
164
+ return queryClient.getQueryData([collectionName]) ?? [];
165
+ }
166
+ throw error;
167
+ }
168
+ if (expandStores) {
169
+ for (const record of items) {
170
+ const expandData = record.expand;
171
+ if (!expandData) continue;
172
+ for (const [key, value] of Object.entries(expandData)) {
173
+ const targetStore = expandStores[key];
174
+ if (!targetStore.utils) continue;
175
+ if (!targetStore.isReady()) {
176
+ if (targetStore.config?.syncMode === "on-demand") {
177
+ await targetStore._sync.startSync();
178
+ } else {
179
+ logger.warn(`not syncing ${key} on ${collectionName} because store is not yet ready`);
180
+ continue;
181
+ }
182
+ }
183
+ const values = Array.isArray(value) ? value : [value];
184
+ targetStore.utils.writeUpsert(values);
185
+ }
186
+ }
187
+ }
188
+ return items;
189
+ }
190
+ const collectionOptions = queryCollectionOptions({
191
+ ...options?.collectionOptions,
192
+ queryClient,
193
+ queryKey: [collectionName],
194
+ syncMode: options?.syncMode ?? "eager",
195
+ queryFn: async (ctx) => {
196
+ return fetchRecords(ctx.meta?.loadSubsetOptions);
197
+ },
198
+ getKey: (item) => {
199
+ const record = item;
200
+ if (!record || typeof record !== "object" || !("id" in record)) {
201
+ throw new Error(
202
+ `Record in collection '${collectionName}' is missing required 'id' field. Received: ${JSON.stringify(item)}`
203
+ );
204
+ }
205
+ return record.id;
206
+ },
207
+ onInsert: options?.onInsert === false ? void 0 : options?.onInsert ?? (async ({ transaction }) => {
208
+ await Promise.all(
209
+ transaction.mutations.map(async (mutation) => {
210
+ const {
211
+ created,
212
+ updated,
213
+ collectionId,
214
+ collectionName: _,
215
+ ...data
216
+ } = mutation.modified;
217
+ await pb.collection(collectionName).create(data);
218
+ })
219
+ );
220
+ }),
221
+ onUpdate: options?.onUpdate === false ? void 0 : options?.onUpdate ?? (async ({ transaction }) => {
222
+ await Promise.all(
223
+ transaction.mutations.map(async (mutation) => {
224
+ const recordWithId = mutation.original;
225
+ await pb.collection(collectionName).update(recordWithId.id, mutation.changes);
226
+ })
227
+ );
228
+ }),
229
+ onDelete: options?.onDelete === false ? void 0 : options?.onDelete ?? (async ({ transaction }) => {
230
+ await Promise.all(
231
+ transaction.mutations.map(async (mutation) => {
232
+ const recordWithId = mutation.original;
233
+ await pb.collection(collectionName).delete(recordWithId.id);
234
+ })
235
+ );
236
+ })
237
+ });
238
+ const collection = createCollection$1(collectionOptions);
239
+ let unsubscribeFn = null;
240
+ let isSubscribed = false;
241
+ let subscriptionPromise = null;
242
+ let subscriptionResolve = null;
243
+ const handleRealtimeEvent = (event) => {
244
+ if (!collection.utils) return;
245
+ collection.utils.writeBatch(() => {
246
+ switch (event.action) {
247
+ case "create":
248
+ collection.utils.writeInsert(event.record);
249
+ break;
250
+ case "update":
251
+ collection.utils.writeUpsert(event.record);
252
+ break;
253
+ case "delete":
254
+ if (event.record && "id" in event.record) {
255
+ collection.utils.writeDelete(event.record.id);
256
+ }
257
+ break;
258
+ }
259
+ });
260
+ };
261
+ const startSubscription = async () => {
262
+ if (isSubscribed) return;
263
+ if (!subscriptionPromise) {
264
+ subscriptionPromise = new Promise((resolve) => {
265
+ subscriptionResolve = resolve;
266
+ });
267
+ }
268
+ try {
269
+ unsubscribeFn = await pb.collection(collectionName).subscribe("*", handleRealtimeEvent);
270
+ isSubscribed = true;
271
+ logger.debug("Subscription started", { collectionName });
272
+ if (subscriptionResolve) {
273
+ subscriptionResolve();
274
+ }
275
+ } catch (error) {
276
+ logger.error("Failed to start subscription", { collectionName, error });
277
+ }
278
+ };
279
+ const stopSubscription = async () => {
280
+ if (!isSubscribed || !unsubscribeFn) return;
281
+ try {
282
+ await unsubscribeFn();
283
+ unsubscribeFn = null;
284
+ isSubscribed = false;
285
+ subscriptionPromise = null;
286
+ subscriptionResolve = null;
287
+ logger.debug("Subscription stopped", { collectionName });
288
+ } catch (error) {
289
+ logger.debug("Unsubscribe failed (expected if connection closed)", { collectionName, error });
290
+ }
291
+ };
292
+ const waitForSubscription = async (timeout = 5e3) => {
293
+ if (isSubscribed) return;
294
+ if (!subscriptionPromise) {
295
+ await new Promise((resolve) => {
296
+ const checkInterval = setInterval(() => {
297
+ if (subscriptionPromise) {
298
+ clearInterval(checkInterval);
299
+ resolve();
300
+ }
301
+ }, 10);
302
+ setTimeout(() => {
303
+ clearInterval(checkInterval);
304
+ resolve();
305
+ }, timeout);
306
+ });
307
+ }
308
+ if (subscriptionPromise) {
309
+ await Promise.race([
310
+ subscriptionPromise,
311
+ new Promise(
312
+ (_, reject) => setTimeout(() => reject(new Error("Subscription timeout")), timeout)
313
+ )
314
+ ]);
315
+ }
316
+ };
317
+ collection.on("subscribers:change", (event) => {
318
+ const newCount = event.subscriberCount;
319
+ const previousCount = event.previousSubscriberCount;
320
+ if (newCount > 0 && previousCount === 0) {
321
+ startSubscription().catch(() => {
322
+ });
323
+ } else if (newCount === 0 && previousCount > 0) {
324
+ stopSubscription().catch(() => {
325
+ });
326
+ }
327
+ });
328
+ Object.assign(collection, {
329
+ collectionName,
330
+ waitForSubscription,
331
+ isSubscribed: () => isSubscribed
332
+ });
333
+ return collection;
334
+ };
335
+ }
336
+
337
+ // src/util.ts
338
+ function newRecordId() {
339
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
340
+ let result = "";
341
+ for (let i = 0; i < 15; i++) {
342
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
343
+ }
344
+ return result;
345
+ }
346
+
347
+ export { createCollection, newRecordId, resetLogger, setLogger };
348
+ //# sourceMappingURL=chunk-B3RODB7I.js.map
349
+ //# sourceMappingURL=chunk-B3RODB7I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pocketbase-query-converter.ts","../src/logger.ts","../src/collection.ts","../src/util.ts"],"names":["createTanStackCollection"],"mappings":"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","file":"chunk-B3RODB7I.js","sourcesContent":["import { parseWhereExpression, parseOrderByExpression, type FieldPath, type ParsedOrderBy } from '@tanstack/db';\nimport type { IR } from '@tanstack/db';\n\ntype BasicExpression<T = unknown> = IR.BasicExpression<T>;\n\nfunction escapeValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (typeof value === 'boolean') {\n return value ? 'true' : 'false';\n }\n if (typeof value === 'number') {\n return value.toString();\n }\n if (typeof value === 'string') {\n return `\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n if (value instanceof Date) {\n return `\"${value.toISOString()}\"`;\n }\n if (Array.isArray(value)) {\n return `[${value.map(escapeValue).join(',')}]`;\n }\n return `\"${String(value)}\"`;\n}\n\nfunction fieldPathToString(path: FieldPath): string {\n return path.join('.');\n}\n\nexport function convertToPocketBaseFilter(\n where: BasicExpression<boolean> | undefined | null\n): string | undefined {\n if (!where) {\n return undefined;\n }\n\n const result = parseWhereExpression(where, {\n handlers: {\n eq: (field: FieldPath, value: unknown) => {\n return `${fieldPathToString(field)} = ${escapeValue(value)}`;\n },\n gt: (field: FieldPath, value: unknown) => {\n return `${fieldPathToString(field)} > ${escapeValue(value)}`;\n },\n gte: (field: FieldPath, value: unknown) => {\n return `${fieldPathToString(field)} >= ${escapeValue(value)}`;\n },\n lt: (field: FieldPath, value: unknown) => {\n return `${fieldPathToString(field)} < ${escapeValue(value)}`;\n },\n lte: (field: FieldPath, value: unknown) => {\n return `${fieldPathToString(field)} <= ${escapeValue(value)}`;\n },\n and: (...conditions: string[]) => {\n if (conditions.length === 0) return '';\n if (conditions.length === 1) return conditions[0];\n return `(${conditions.join(' && ')})`;\n },\n or: (...conditions: string[]) => {\n if (conditions.length === 0) return '';\n if (conditions.length === 1) return conditions[0];\n return `(${conditions.join(' || ')})`;\n },\n not: (condition: string) => {\n return `!(${condition})`;\n },\n in: (field: FieldPath, values: unknown) => {\n const valueArray = Array.isArray(values) ? values : [values];\n const uniqueValues = [...new Set(valueArray)];\n const fieldStr = fieldPathToString(field);\n const conditions = uniqueValues.map((v) => `${fieldStr} = ${escapeValue(v)}`);\n return conditions.length > 1 ? `(${conditions.join(' || ')})` : conditions[0];\n },\n like: (field: FieldPath, value: unknown) => {\n return `${fieldPathToString(field)} ~ ${escapeValue(value)}`;\n },\n isNull: (field: FieldPath) => {\n return `${fieldPathToString(field)} = null`;\n },\n isUndefined: (field: FieldPath) => {\n return `${fieldPathToString(field)} = null`;\n },\n },\n onUnknownOperator: (operator: string, args: unknown[]) => {\n throw new Error(\n `Unsupported operator '${operator}' for PocketBase filter conversion. ` +\n `Supported operators: eq, gt, gte, lt, lte, in, like, and, or, not, isNull, isUndefined`\n );\n },\n });\n\n return result || undefined;\n}\n\nexport function convertToPocketBaseSort(\n orderBy: IR.OrderBy | undefined | null\n): string | undefined {\n if (!orderBy) {\n return undefined;\n }\n\n const sorts = parseOrderByExpression(orderBy);\n\n if (sorts.length === 0) {\n return undefined;\n }\n\n return sorts\n .map((sort: ParsedOrderBy) => {\n const field = fieldPathToString(sort.field);\n return sort.direction === 'desc' ? `-${field}` : field;\n })\n .join(',');\n}\n","/**\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 info-level messages.\n * @param msg - The message to log\n * @param context - Optional context object with additional information\n */\n info: (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 info: (msg: string, context?: object) => {\n // biome-ignore lint/suspicious/noConsoleLog: Info logging is acceptable\n console.info(`[pbtsdb] ${msg}`, context || '');\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 info: (msg: string, context?: object) => currentLogger.info(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 'pbtsdb';\n *\n * // Integration with a custom logging service\n * setLogger({\n * debug: (msg, context) => myLogger.debug(msg, context),\n * warn: (msg, context) => myLogger.warn(msg, context),\n * error: (msg, context) => {\n * myLogger.error(msg, context);\n * Sentry.captureMessage(msg, { level: 'error', extra: context });\n * },\n * });\n * ```\n *\n * @example\n * ```ts\n * // 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 { RecordSubscription } from \"pocketbase\";\nimport {\n createCollection as createTanStackCollection,\n type Collection,\n type LoadSubsetOptions,\n} from \"@tanstack/db\";\nimport { queryCollectionOptions, type QueryCollectionUtils } from \"@tanstack/query-db-collection\";\nimport { QueryClient } from \"@tanstack/react-query\";\nimport { convertToPocketBaseFilter, convertToPocketBaseSort } from \"./pocketbase-query-converter\";\nimport type {\n SchemaDeclaration,\n CreateCollectionOptions,\n ExtractRecordType,\n ExpandTargetCollection,\n BaseRecord,\n} from \"./types\";\nimport { logger } from \"./logger\";\n\nexport type { SchemaDeclaration, CreateCollectionOptions, BaseRecord } from \"./types\";\n\n/**\n * Extended LoadSubsetOptions that includes PocketBase-specific expand parameter.\n * @internal\n */\ntype ExtendedLoadSubsetOptions = LoadSubsetOptions & {\n pbExpand?: string;\n};\n\n/**\n * Compute the record type with expand property when expand option is configured.\n * @internal\n */\ntype WithExpandFromConfig<Schema extends SchemaDeclaration, C extends keyof Schema, Opts> = Opts extends {\n expand: infer E;\n}\n ? ExtractRecordType<Schema, C> & {\n expand?: {\n [K in keyof E]: K extends keyof import(\"./types\").ExtractRelations<Schema, C>\n ? import(\"./types\").ExtractRelations<Schema, C>[K] extends Array<infer U>\n ? U[]\n : import(\"./types\").ExtractRelations<Schema, C>[K]\n : never;\n };\n }\n : ExtractRecordType<Schema, C>;\n\n/**\n * Subscription helpers added to collection instances.\n * @internal\n */\ninterface CollectionSubscriptionHelpers {\n /** The PocketBase collection name */\n collectionName: string;\n /** Wait for subscription to be established (useful in tests) */\n waitForSubscription: (timeout?: number) => Promise<void>;\n /** Check if collection has an active subscription */\n isSubscribed: () => boolean;\n}\n\n/**\n * Inferred collection type from config options.\n * @internal\n */\ntype InferCollectionType<\n Schema extends SchemaDeclaration,\n C extends keyof Schema,\n Opts extends CreateCollectionOptions<Schema, C>,\n> = Collection<\n WithExpandFromConfig<Schema, C, Opts>,\n string | number,\n // TUtils - QueryCollectionUtils from TanStack Query DB Collection\n QueryCollectionUtils<WithExpandFromConfig<Schema, C, Opts>, string | number, WithExpandFromConfig<Schema, C, Opts>>,\n // TSchema - we don't use StandardSchema validation\n never,\n Opts extends {\n omitOnInsert: infer O extends readonly import(\"./types\").OmittableFields<ExtractRecordType<Schema, C>>[];\n }\n ? import(\"./types\").ComputeInsertType<ExtractRecordType<Schema, C>, O>\n : ExtractRecordType<Schema, C>\n> &\n CollectionSubscriptionHelpers;\n\n/**\n * Creates a type-safe TanStack DB collection backed by PocketBase.\n * Use this when you need fine-grained control or need to create collections with dependencies.\n *\n * @param pb - PocketBase client instance\n * @param queryClient - TanStack Query client\n * @returns A curried function that takes collection name and options\n *\n * @example\n * Basic usage:\n * ```ts\n * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {});\n *\n * // Use directly\n * const books = await booksCollection.getFullList();\n * ```\n *\n * @example\n * With auto-expand relations:\n * ```ts\n * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors', {});\n * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {\n * expand: {\n * author: authorsCollection // Always expand, auto-upsert into authorsCollection\n * }\n * });\n *\n * // Expand is automatic - no .expand() call needed\n * const { data } = useLiveQuery((q) => q.from({ books: booksCollection }));\n * // data[0].expand.author is typed and populated\n * ```\n */\nexport function createCollection<Schema extends SchemaDeclaration>(pb: PocketBase, queryClient: QueryClient) {\n return <\n C extends keyof Schema & string,\n Opts extends CreateCollectionOptions<Schema, C> = CreateCollectionOptions<Schema, C>,\n >(\n collectionName: C,\n options?: Opts,\n ): InferCollectionType<Schema, C, Opts> => {\n type RecordType = ExtractRecordType<Schema, C>;\n const expandStores = options?.expand as Record<string, ExpandTargetCollection> | undefined;\n const expandString = expandStores ? Object.keys(expandStores).sort().join(\",\") : undefined;\n\n const ignoreAutoCancellation = options?.ignoreAutoCancellation ?? true;\n\n async function fetchRecords(loadOptions?: ExtendedLoadSubsetOptions): Promise<RecordType[]> {\n const filter = convertToPocketBaseFilter(loadOptions?.where);\n const sort = convertToPocketBaseSort(loadOptions?.orderBy);\n const limit = loadOptions?.limit;\n\n let items: RecordType[];\n try {\n if (limit) {\n // Use getList when limit is specified to avoid fetching all records\n const result = await pb.collection(collectionName).getList(1, limit, {\n filter,\n sort,\n skipTotal: true, // Optimize by skipping total count\n expand: expandString,\n });\n items = result.items as unknown as RecordType[];\n } else {\n // Use getFullList to fetch all records with automatic pagination\n items = (await pb.collection(collectionName).getFullList({\n filter,\n sort,\n expand: expandString,\n })) as unknown as RecordType[];\n }\n } catch (error) {\n if (ignoreAutoCancellation && error instanceof Error && error.message.includes(\"autocancelled\")) {\n return queryClient.getQueryData<RecordType[]>([collectionName]) ?? [];\n }\n throw error;\n }\n\n if (expandStores) {\n for (const record of items) {\n const expandData = (record as RecordType & { expand?: Record<string, object | object[]> }).expand;\n if (!expandData) continue;\n\n for (const [key, value] of Object.entries(expandData)) {\n const targetStore = expandStores[key];\n if (!targetStore.utils) continue;\n if (!targetStore.isReady()) {\n if (targetStore.config?.syncMode === \"on-demand\") {\n await targetStore._sync.startSync();\n } else {\n logger.warn(`not syncing ${key} on ${collectionName} because store is not yet ready`);\n continue;\n }\n }\n const values = Array.isArray(value) ? value : [value];\n targetStore.utils.writeUpsert(values);\n }\n }\n }\n\n return items;\n }\n\n const collectionOptions = queryCollectionOptions({\n ...options?.collectionOptions,\n queryClient,\n queryKey: [collectionName],\n syncMode: options?.syncMode ?? \"eager\",\n queryFn: async (ctx): Promise<RecordType[]> => {\n return fetchRecords(ctx.meta?.loadSubsetOptions as ExtendedLoadSubsetOptions | undefined);\n },\n getKey: (item: RecordType) => {\n const record = item as any;\n if (!record || typeof record !== \"object\" || !(\"id\" in record)) {\n throw new Error(\n `Record in collection '${collectionName}' is missing required 'id' field. Received: ${JSON.stringify(item)}`,\n );\n }\n return record.id;\n },\n onInsert:\n options?.onInsert === false\n ? undefined\n : (options?.onInsert ??\n (async ({ transaction }) => {\n await Promise.all(\n transaction.mutations.map(async (mutation) => {\n const {\n created,\n updated,\n collectionId,\n collectionName: _,\n ...data\n } = mutation.modified as unknown as Record<string, unknown>;\n await pb.collection(collectionName).create(data);\n }),\n );\n })),\n onUpdate:\n options?.onUpdate === false\n ? undefined\n : (options?.onUpdate ??\n (async ({ transaction }) => {\n await Promise.all(\n transaction.mutations.map(async (mutation) => {\n const recordWithId = mutation.original as { id: string };\n await pb.collection(collectionName).update(recordWithId.id, mutation.changes);\n }),\n );\n })),\n onDelete:\n options?.onDelete === false\n ? undefined\n : (options?.onDelete ??\n (async ({ transaction }) => {\n await Promise.all(\n transaction.mutations.map(async (mutation) => {\n const recordWithId = mutation.original as { id: string };\n await pb.collection(collectionName).delete(recordWithId.id);\n }),\n );\n })),\n });\n\n const collection = createTanStackCollection(collectionOptions);\n\n // Real-time subscription state\n let unsubscribeFn: (() => Promise<void>) | null = null;\n let isSubscribed = false;\n let subscriptionPromise: Promise<void> | null = null;\n let subscriptionResolve: (() => void) | null = null;\n\n // Handle real-time events from PocketBase\n const handleRealtimeEvent = (event: RecordSubscription<RecordType>) => {\n if (!collection.utils) return;\n\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.writeUpsert(event.record);\n break;\n case \"delete\":\n if (event.record && \"id\" in event.record) {\n collection.utils.writeDelete((event.record as { id: string }).id);\n }\n break;\n }\n });\n };\n\n // Start PocketBase real-time subscription\n const startSubscription = async () => {\n if (isSubscribed) return;\n\n // Create promise before starting so waiters can await it\n if (!subscriptionPromise) {\n subscriptionPromise = new Promise<void>((resolve) => {\n subscriptionResolve = resolve;\n });\n }\n\n try {\n unsubscribeFn = await pb.collection(collectionName).subscribe(\"*\", handleRealtimeEvent);\n isSubscribed = true;\n logger.debug(\"Subscription started\", { collectionName });\n // Resolve the promise to notify waiters\n if (subscriptionResolve) {\n subscriptionResolve();\n }\n } catch (error) {\n logger.error(\"Failed to start subscription\", { collectionName, error });\n }\n };\n\n // Stop PocketBase real-time subscription\n const stopSubscription = async () => {\n if (!isSubscribed || !unsubscribeFn) return;\n\n try {\n await unsubscribeFn();\n unsubscribeFn = null;\n isSubscribed = false;\n // Reset promise for next subscription cycle\n subscriptionPromise = null;\n subscriptionResolve = null;\n logger.debug(\"Subscription stopped\", { collectionName });\n } catch (error) {\n logger.debug(\"Unsubscribe failed (expected if connection closed)\", { collectionName, error });\n }\n };\n\n // Wait for subscription to be established (for testing)\n const waitForSubscription = async (timeout = 5000): Promise<void> => {\n if (isSubscribed) return;\n\n if (!subscriptionPromise) {\n // No subscription in progress, wait for one to start\n await new Promise<void>((resolve) => {\n const checkInterval = setInterval(() => {\n if (subscriptionPromise) {\n clearInterval(checkInterval);\n resolve();\n }\n }, 10);\n setTimeout(() => {\n clearInterval(checkInterval);\n resolve();\n }, timeout);\n });\n }\n\n if (subscriptionPromise) {\n await Promise.race([\n subscriptionPromise,\n new Promise<void>((_, reject) =>\n setTimeout(() => reject(new Error(\"Subscription timeout\")), timeout),\n ),\n ]);\n }\n };\n\n // Manage subscription based on collection subscriber count\n collection.on(\"subscribers:change\", (event: { subscriberCount: number; previousSubscriberCount: number }) => {\n const newCount = event.subscriberCount;\n const previousCount = event.previousSubscriberCount;\n\n if (newCount > 0 && previousCount === 0) {\n // First subscriber - start real-time subscription\n startSubscription().catch(() => {});\n } else if (newCount === 0 && previousCount > 0) {\n // Last subscriber removed - stop real-time subscription\n stopSubscription().catch(() => {});\n }\n });\n\n // Add collectionName and subscription helpers\n Object.assign(collection, {\n collectionName,\n waitForSubscription,\n isSubscribed: () => isSubscribed,\n });\n\n return collection as any;\n };\n}\n","/**\n * Generates a new PocketBase-compatible record ID.\n * Returns a 15-character alphanumeric string (lowercase letters and numbers).\n *\n * PocketBase uses 15-character IDs for records, formatted as lowercase alphanumeric.\n *\n * @returns A 15-character alphanumeric string suitable for use as a PocketBase record ID\n *\n * @example\n * ```ts\n * const id = newRecordId(); // \"a1b2c3d4e5f6g7h\"\n * ```\n */\nexport function newRecordId(): string {\n const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n for (let i = 0; i < 15; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n"]}