pbtsdb 0.3.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/dist/core.d.ts ADDED
@@ -0,0 +1,506 @@
1
+ import { Collection, InsertMutationFn, UpdateMutationFn, DeleteMutationFn, BaseCollectionConfig } from '@tanstack/db';
2
+ export { BTreeIndex, BasicIndex, DeltaEvent, DeltaType, EffectConfig, EffectContext, IndexConstructor, ReverseIndex, createEffect, toArray } from '@tanstack/db';
3
+ import PocketBase from 'pocketbase';
4
+ import { QueryCollectionUtils } from '@tanstack/query-db-collection';
5
+ import { QueryClient } from '@tanstack/react-query';
6
+
7
+ /**
8
+ * Base record type required by PocketBase collections.
9
+ * All records must have an 'id' field.
10
+ */
11
+ interface BaseRecord {
12
+ id: string;
13
+ }
14
+ /**
15
+ * Schema declaration for type-safe collection management.
16
+ * Define your PocketBase collections with their record types and relations.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * interface MySchema extends SchemaDeclaration {
21
+ * users: {
22
+ * type: UserRecord;
23
+ * relations: {
24
+ * org: OrgRecord;
25
+ * };
26
+ * };
27
+ * }
28
+ * ```
29
+ */
30
+ interface SchemaDeclaration {
31
+ [collectionName: string]: {
32
+ type: BaseRecord;
33
+ relations?: {
34
+ [fieldName: string]: BaseRecord | BaseRecord[];
35
+ };
36
+ };
37
+ }
38
+ /**
39
+ * Extracts the record type from a schema collection.
40
+ * @internal
41
+ */
42
+ type ExtractRecordType<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = Schema[CollectionName]['type'];
43
+ /**
44
+ * Valid field names that can be omitted during insert operations.
45
+ * Excludes 'id' which is always required for TanStack DB record tracking.
46
+ * @internal
47
+ */
48
+ type OmittableFields<T extends object> = Exclude<keyof T, 'id'>;
49
+ /**
50
+ * Computes the insert input type by making specified fields optional.
51
+ * Used to support omitting server-generated fields (created, updated) during insertion.
52
+ *
53
+ * IMPORTANT: The 'id' field can NEVER be omitted as TanStack DB requires it for record tracking.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * type BookInsert = ComputeInsertType<Books, ['created', 'updated']>
58
+ * // Result: Omit<Books, 'created' | 'updated'> & Partial<Pick<Books, 'created' | 'updated'>>
59
+ * ```
60
+ * @internal
61
+ */
62
+ type ComputeInsertType<T extends object, OmitFields extends readonly OmittableFields<T>[]> = Omit<T, OmitFields[number]> & Partial<Pick<T, OmitFields[number]>>;
63
+ /**
64
+ * Extracts the relations object from a schema collection.
65
+ * Returns never if the collection has no relations defined.
66
+ * @internal
67
+ */
68
+ type ExtractRelations<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = Schema[CollectionName] extends {
69
+ relations: infer R;
70
+ } ? R : never;
71
+ /**
72
+ * Parses comma-separated relation field names into a union type.
73
+ * Recursively processes "field1,field2,field3" into "field1" | "field2" | "field3".
74
+ *
75
+ * @example
76
+ * ParseExpandFields<"customer,address"> => "customer" | "address"
77
+ * @internal
78
+ */
79
+ type ParseExpandFields<T extends string> = T extends `${infer Field},${infer Rest}` ? Field | ParseExpandFields<Rest> : T;
80
+ /**
81
+ * Builds the expand object type based on field names.
82
+ * If expand fields are provided, adds an optional `expand` property with properly typed relations.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * // Without expand
87
+ * WithExpand<Schema, 'jobs', undefined> => JobRecord
88
+ *
89
+ * // With expand
90
+ * WithExpand<Schema, 'jobs', 'customer'> => JobRecord & {
91
+ * expand?: { customer?: CustomerRecord }
92
+ * }
93
+ * ```
94
+ */
95
+ type WithExpand<Schema extends SchemaDeclaration, CollectionName extends keyof Schema, ExpandFields extends string | undefined> = ExpandFields extends string ? ExtractRecordType<Schema, CollectionName> & {
96
+ expand?: {
97
+ [K in ParseExpandFields<ExpandFields>]?: K extends keyof ExtractRelations<Schema, CollectionName> ? ExtractRelations<Schema, CollectionName>[K] extends Array<infer U> ? U[] : ExtractRelations<Schema, CollectionName>[K] : never;
98
+ };
99
+ } : ExtractRecordType<Schema, CollectionName>;
100
+ /**
101
+ * Removes undefined from a union type.
102
+ * Used to unwrap optional relation types.
103
+ *
104
+ * @example
105
+ * ExcludeUndefined<Customer | undefined> => Customer
106
+ * @internal
107
+ */
108
+ type ExcludeUndefined<T> = T extends (infer U) | undefined ? U : T;
109
+ /**
110
+ * Converts a schema relation type to its corresponding Collection constraint.
111
+ * Handles both single relations (T) and array relations (T[]).
112
+ * Accepts collections with any insert type to support omitOnInsert configurations.
113
+ *
114
+ * Uses constraint (extends Collection<T, ...>) rather than exact type to allow
115
+ * collections with different TInput types (from omitOnInsert) to be compatible.
116
+ *
117
+ * @example
118
+ * RelationAsCollection<Customer> => Collection<Customer, string | number, any, any, any>
119
+ * RelationAsCollection<Customer[]> => Collection<Customer, string | number, any, any, any>
120
+ * @internal
121
+ */
122
+ type RelationAsCollection<T> = T extends Array<infer U> ? U extends object ? Collection<U, string | number, any, any, any> : Collection<object, string | number, any, any, any> : T extends object ? Collection<T, string | number, any, any, any> : Collection<object, string | number, any, any, any>;
123
+ /**
124
+ * Configuration for per-collection expand - maps relation field names to their target collections.
125
+ * Relations configured here are automatically expanded on every fetch and auto-upserted into target collections.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors');
130
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
131
+ * expand: {
132
+ * author: authorsCollection // Always expand 'author', upsert into authorsCollection
133
+ * }
134
+ * });
135
+ * ```
136
+ */
137
+ type ExpandConfig<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> = ExtractRelations<Schema, CollectionName> extends never ? Record<string, never> : Partial<{
138
+ [K in keyof ExtractRelations<Schema, CollectionName>]: RelationAsCollection<ExcludeUndefined<ExtractRelations<Schema, CollectionName>[K]>>;
139
+ }>;
140
+ /**
141
+ * Options for creating a collection.
142
+ */
143
+ interface CreateCollectionOptions<Schema extends SchemaDeclaration, CollectionName extends keyof Schema> {
144
+ /**
145
+ * Configure relations to automatically expand on every fetch.
146
+ * Maps relation field names to their target collections for auto-upsert.
147
+ *
148
+ * Expanded records are automatically inserted into their target collections.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors');
153
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
154
+ * expand: {
155
+ * author: authorsCollection // Always expand, auto-upsert into authorsCollection
156
+ * }
157
+ * });
158
+ *
159
+ * // Expand is automatic - no .expand() call needed
160
+ * const { data } = useLiveQuery((q) => q.from({ books: booksCollection }));
161
+ * // data[0].expand.author is typed and populated
162
+ * ```
163
+ */
164
+ expand?: ExpandConfig<Schema, CollectionName>;
165
+ /**
166
+ * Fields that can be omitted during insert operations.
167
+ * Useful for server-generated fields like 'created', 'updated'.
168
+ *
169
+ * When specified, the insert() method will accept records without these fields,
170
+ * and the omitted fields become optional in the insert input type.
171
+ *
172
+ * **Type safety:** Only valid field names from the record type are accepted.
173
+ * **IMPORTANT:** The 'id' field can NEVER be omitted as TanStack DB requires it.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * // Allow inserting without created, updated (server-generated timestamps)
178
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
179
+ * omitOnInsert: ['created', 'updated'] as const
180
+ * });
181
+ *
182
+ * // Now insert() accepts records without those fields
183
+ * booksCollection.insert({
184
+ * id: newRecordId(), // id is always required
185
+ * title: 'New Book',
186
+ * isbn: '1234567890',
187
+ * genre: 'Fiction',
188
+ * author: authorId
189
+ * // created, updated are optional
190
+ * });
191
+ * ```
192
+ */
193
+ omitOnInsert?: readonly OmittableFields<ExtractRecordType<Schema, CollectionName>>[];
194
+ /**
195
+ * Custom handler for insert mutations.
196
+ *
197
+ * **Default behavior (not provided):** Automatically creates records in PocketBase,
198
+ * excluding auto-generated fields (id, created, updated, collectionId, collectionName).
199
+ *
200
+ * **Custom handler:** Provide your own handler to customize insert behavior.
201
+ *
202
+ * **Disable:** Set to `false` to disable insert mutations entirely (will throw error if insert is called).
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * // Use default automatic handler (recommended)
207
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
208
+ *
209
+ * // Custom handler
210
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
211
+ * onInsert: async ({ transaction }) => {
212
+ * for (const mutation of transaction.mutations) {
213
+ * await customInsertLogic(mutation.modified);
214
+ * }
215
+ * await queryClient.invalidateQueries({ queryKey: ['books'] });
216
+ * }
217
+ * });
218
+ *
219
+ * // Disable inserts (read-only collection)
220
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
221
+ * onInsert: false
222
+ * });
223
+ * ```
224
+ */
225
+ onInsert?: InsertMutationFn<ExtractRecordType<Schema, CollectionName>> | false;
226
+ /**
227
+ * Custom handler for update mutations.
228
+ *
229
+ * **Default behavior (not provided):** Automatically updates records in PocketBase
230
+ * with the changed fields.
231
+ *
232
+ * **Custom handler:** Provide your own handler to customize update behavior.
233
+ *
234
+ * **Disable:** Set to `false` to disable update mutations entirely (will throw error if update is called).
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * // Use default automatic handler (recommended)
239
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
240
+ *
241
+ * // Custom handler
242
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
243
+ * onUpdate: async ({ transaction }) => {
244
+ * for (const mutation of transaction.mutations) {
245
+ * await customUpdateLogic(mutation.original.id, mutation.changes);
246
+ * }
247
+ * await queryClient.invalidateQueries({ queryKey: ['books'] });
248
+ * }
249
+ * });
250
+ *
251
+ * // Disable updates (read-only collection)
252
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
253
+ * onUpdate: false
254
+ * });
255
+ * ```
256
+ */
257
+ onUpdate?: UpdateMutationFn<ExtractRecordType<Schema, CollectionName>> | false;
258
+ /**
259
+ * Custom handler for delete mutations.
260
+ *
261
+ * **Default behavior (not provided):** Automatically deletes records from PocketBase.
262
+ *
263
+ * **Custom handler:** Provide your own handler to customize delete behavior.
264
+ *
265
+ * **Disable:** Set to `false` to disable delete mutations entirely (will throw error if delete is called).
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * // Use default automatic handler (recommended)
270
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
271
+ *
272
+ * // Custom handler
273
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
274
+ * onDelete: async ({ transaction }) => {
275
+ * for (const mutation of transaction.mutations) {
276
+ * await customDeleteLogic(mutation.original.id);
277
+ * }
278
+ * await queryClient.invalidateQueries({ queryKey: ['books'] });
279
+ * }
280
+ * });
281
+ *
282
+ * // Disable deletes (read-only collection)
283
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
284
+ * onDelete: false
285
+ * });
286
+ * ```
287
+ */
288
+ onDelete?: DeleteMutationFn<ExtractRecordType<Schema, CollectionName>> | false;
289
+ /**
290
+ * Sync mode for the collection. Controls when and how data is fetched from PocketBase.
291
+ *
292
+ * - `'eager'` (default): Fetches all data immediately when collection is created.
293
+ * Queries are evaluated client-side against the cached data. Fast for small datasets
294
+ * but loads entire collection into memory. Matches TanStack DB default.
295
+ *
296
+ * - `'on-demand'`: Fetches data only when queries execute. Each query with different
297
+ * filters/sorting triggers a new fetch from PocketBase. Enables true server-side
298
+ * filtering and is better for large datasets.
299
+ *
300
+ * @default 'eager'
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * // Default: eager mode - client-side filtering
305
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
306
+ *
307
+ * // On-demand mode - server-side filtering
308
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
309
+ * syncMode: 'on-demand'
310
+ * });
311
+ * ```
312
+ */
313
+ syncMode?: 'eager' | 'on-demand';
314
+ /**
315
+ * Whether to ignore PocketBase auto-cancellation errors.
316
+ *
317
+ * PocketBase automatically cancels pending requests when a new request is made
318
+ * to the same endpoint. This can throw ClientResponseError with a message
319
+ * containing "autocancelled". When this option is true, such errors are
320
+ * silently ignored and the existing cached data is returned for the cancelled request.
321
+ *
322
+ * @default true
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * // Default: auto-cancellation errors are ignored
327
+ * const collection = createCollection<Schema>(pb, queryClient)('books');
328
+ *
329
+ * // Explicitly handle auto-cancellation errors
330
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
331
+ * ignoreAutoCancellation: false
332
+ * });
333
+ * ```
334
+ */
335
+ ignoreAutoCancellation?: boolean;
336
+ /**
337
+ * Additional options passed directly to the underlying TanStack DB collection.
338
+ * Use this to configure indexing, garbage collection, comparison functions,
339
+ * and any other TanStack DB collection options not explicitly exposed by pbtsdb.
340
+ *
341
+ * Options set here are spread into the `queryCollectionOptions()` call.
342
+ * Fields managed by pbtsdb (`getKey`, `syncMode`, `onInsert`, `onUpdate`,
343
+ * `onDelete`, `schema`) are excluded from the type.
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * import { BasicIndex } from 'pbtsdb'
348
+ * const collection = createCollection<Schema>(pb, queryClient)('books', {
349
+ * collectionOptions: {
350
+ * autoIndex: 'eager',
351
+ * defaultIndexType: BasicIndex,
352
+ * gcTime: 60000,
353
+ * }
354
+ * });
355
+ * ```
356
+ */
357
+ collectionOptions?: Omit<Partial<BaseCollectionConfig<ExtractRecordType<Schema, CollectionName>, string | number>>, 'getKey' | 'syncMode' | 'onInsert' | 'onUpdate' | 'onDelete' | 'schema'>;
358
+ }
359
+
360
+ /**
361
+ * Compute the record type with expand property when expand option is configured.
362
+ * @internal
363
+ */
364
+ type WithExpandFromConfig<Schema extends SchemaDeclaration, C extends keyof Schema, Opts> = Opts extends {
365
+ expand: infer E;
366
+ } ? ExtractRecordType<Schema, C> & {
367
+ expand?: {
368
+ [K in keyof E]: K extends keyof ExtractRelations<Schema, C> ? ExtractRelations<Schema, C>[K] extends Array<infer U> ? U[] : ExtractRelations<Schema, C>[K] : never;
369
+ };
370
+ } : ExtractRecordType<Schema, C>;
371
+ /**
372
+ * Subscription helpers added to collection instances.
373
+ * @internal
374
+ */
375
+ interface CollectionSubscriptionHelpers {
376
+ /** The PocketBase collection name */
377
+ collectionName: string;
378
+ /** Wait for subscription to be established (useful in tests) */
379
+ waitForSubscription: (timeout?: number) => Promise<void>;
380
+ /** Check if collection has an active subscription */
381
+ isSubscribed: () => boolean;
382
+ }
383
+ /**
384
+ * Inferred collection type from config options.
385
+ * @internal
386
+ */
387
+ type InferCollectionType<Schema extends SchemaDeclaration, C extends keyof Schema, Opts extends CreateCollectionOptions<Schema, C>> = Collection<WithExpandFromConfig<Schema, C, Opts>, string | number, QueryCollectionUtils<WithExpandFromConfig<Schema, C, Opts>, string | number, WithExpandFromConfig<Schema, C, Opts>>, never, Opts extends {
388
+ omitOnInsert: infer O extends readonly OmittableFields<ExtractRecordType<Schema, C>>[];
389
+ } ? ComputeInsertType<ExtractRecordType<Schema, C>, O> : ExtractRecordType<Schema, C>> & CollectionSubscriptionHelpers;
390
+ /**
391
+ * Creates a type-safe TanStack DB collection backed by PocketBase.
392
+ * Use this when you need fine-grained control or need to create collections with dependencies.
393
+ *
394
+ * @param pb - PocketBase client instance
395
+ * @param queryClient - TanStack Query client
396
+ * @returns A curried function that takes collection name and options
397
+ *
398
+ * @example
399
+ * Basic usage:
400
+ * ```ts
401
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {});
402
+ *
403
+ * // Use directly
404
+ * const books = await booksCollection.getFullList();
405
+ * ```
406
+ *
407
+ * @example
408
+ * With auto-expand relations:
409
+ * ```ts
410
+ * const authorsCollection = createCollection<Schema>(pb, queryClient)('authors', {});
411
+ * const booksCollection = createCollection<Schema>(pb, queryClient)('books', {
412
+ * expand: {
413
+ * author: authorsCollection // Always expand, auto-upsert into authorsCollection
414
+ * }
415
+ * });
416
+ *
417
+ * // Expand is automatic - no .expand() call needed
418
+ * const { data } = useLiveQuery((q) => q.from({ books: booksCollection }));
419
+ * // data[0].expand.author is typed and populated
420
+ * ```
421
+ */
422
+ declare function createCollection<Schema extends SchemaDeclaration>(pb: PocketBase, queryClient: QueryClient): <C extends keyof Schema & string, Opts extends CreateCollectionOptions<Schema, C> = CreateCollectionOptions<Schema, C>>(collectionName: C, options?: Opts) => InferCollectionType<Schema, C, Opts>;
423
+
424
+ /**
425
+ * Logger interface for subscription events and internal operations.
426
+ * Users can provide their own implementation to integrate with external logging services.
427
+ */
428
+ interface Logger {
429
+ /**
430
+ * Log debug-level messages (typically only shown in development).
431
+ * @param msg - The message to log
432
+ * @param context - Optional context object with additional information
433
+ */
434
+ debug: (msg: string, context?: object) => void;
435
+ /**
436
+ * Log info-level messages.
437
+ * @param msg - The message to log
438
+ * @param context - Optional context object with additional information
439
+ */
440
+ info: (msg: string, context?: object) => void;
441
+ /**
442
+ * Log warning-level messages.
443
+ * @param msg - The message to log
444
+ * @param context - Optional context object with additional information
445
+ */
446
+ warn: (msg: string, context?: object) => void;
447
+ /**
448
+ * Log error-level messages.
449
+ * @param msg - The message to log
450
+ * @param context - Optional context object with additional information
451
+ */
452
+ error: (msg: string, context?: object) => void;
453
+ }
454
+ /**
455
+ * Set a custom logger implementation.
456
+ * This allows users to integrate with their own logging services (e.g., Sentry, LogRocket, etc.).
457
+ *
458
+ * @param customLogger - The custom logger implementation
459
+ *
460
+ * @example
461
+ * ```ts
462
+ * import { setLogger } from 'pbtsdb';
463
+ *
464
+ * // Integration with a custom logging service
465
+ * setLogger({
466
+ * debug: (msg, context) => myLogger.debug(msg, context),
467
+ * warn: (msg, context) => myLogger.warn(msg, context),
468
+ * error: (msg, context) => {
469
+ * myLogger.error(msg, context);
470
+ * Sentry.captureMessage(msg, { level: 'error', extra: context });
471
+ * },
472
+ * });
473
+ * ```
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * // Disable all logging
478
+ * setLogger({
479
+ * debug: () => {},
480
+ * warn: () => {},
481
+ * error: () => {},
482
+ * });
483
+ * ```
484
+ */
485
+ declare function setLogger(customLogger: Logger): void;
486
+ /**
487
+ * Reset the logger to the default implementation.
488
+ */
489
+ declare function resetLogger(): void;
490
+
491
+ /**
492
+ * Generates a new PocketBase-compatible record ID.
493
+ * Returns a 15-character alphanumeric string (lowercase letters and numbers).
494
+ *
495
+ * PocketBase uses 15-character IDs for records, formatted as lowercase alphanumeric.
496
+ *
497
+ * @returns A 15-character alphanumeric string suitable for use as a PocketBase record ID
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * const id = newRecordId(); // "a1b2c3d4e5f6g7h"
502
+ * ```
503
+ */
504
+ declare function newRecordId(): string;
505
+
506
+ export { type CreateCollectionOptions, type ExcludeUndefined, type ExtractRecordType, type ExtractRelations, type Logger, type OmittableFields, type ParseExpandFields, type RelationAsCollection, type SchemaDeclaration, type WithExpand, createCollection, newRecordId, resetLogger, setLogger };
package/dist/core.js ADDED
@@ -0,0 +1,3 @@
1
+ export { BTreeIndex, BasicIndex, ReverseIndex, createCollection, createEffect, newRecordId, resetLogger, setLogger, toArray } from './chunk-B3RODB7I.js';
2
+ //# sourceMappingURL=core.js.map
3
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"core.js"}