@tanstack/db 0.2.4 → 0.2.5
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/cjs/collection.cjs +23 -4
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +35 -41
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-only.d.cts +17 -43
- package/dist/cjs/local-storage.cjs +3 -12
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/local-storage.d.cts +16 -39
- package/dist/cjs/query/builder/types.d.cts +3 -10
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/types.d.cts +10 -31
- package/dist/esm/collection.d.ts +35 -41
- package/dist/esm/collection.js +23 -4
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/local-only.d.ts +17 -43
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.d.ts +16 -39
- package/dist/esm/local-storage.js +3 -12
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +3 -10
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/types.d.ts +10 -31
- package/package.json +2 -2
- package/src/collection.ts +148 -196
- package/src/local-only.ts +57 -77
- package/src/local-storage.ts +53 -85
- package/src/query/builder/types.ts +3 -12
- package/src/query/live-query-collection.ts +1 -1
- package/src/types.ts +25 -55
|
@@ -4,7 +4,7 @@ import { IndexProxy } from './indexes/lazy-index.js';
|
|
|
4
4
|
import { Transaction } from './transactions.cjs';
|
|
5
5
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
6
6
|
import { SingleRowRefProxy } from './query/builder/ref-proxy.cjs';
|
|
7
|
-
import { ChangeListener, ChangeMessage, CollectionConfig, CollectionStatus, CurrentStateAsChangesOptions, Fn,
|
|
7
|
+
import { ChangeListener, ChangeMessage, CollectionConfig, CollectionStatus, CurrentStateAsChangesOptions, Fn, InferSchemaInput, InferSchemaOutput, InsertConfig, OperationConfig, OptimisticChangeMessage, SubscribeChangesOptions, Transaction as TransactionType, UtilsRecord, WritableDeep } from './types.cjs';
|
|
8
8
|
import { IndexOptions } from './indexes/index-options.js';
|
|
9
9
|
import { BaseIndex, IndexResolver } from './indexes/base-index.js';
|
|
10
10
|
interface PendingSyncedTransaction<T extends object = Record<string, unknown>> {
|
|
@@ -26,11 +26,9 @@ export interface Collection<T extends object = Record<string, unknown>, TKey ext
|
|
|
26
26
|
/**
|
|
27
27
|
* Creates a new Collection instance with the given configuration
|
|
28
28
|
*
|
|
29
|
-
* @template
|
|
29
|
+
* @template T - The schema type if a schema is provided, otherwise the type of items in the collection
|
|
30
30
|
* @template TKey - The type of the key for the collection
|
|
31
31
|
* @template TUtils - The utilities record type
|
|
32
|
-
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
33
|
-
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
34
32
|
* @param options - Collection options with optional utilities
|
|
35
33
|
* @returns A new Collection with utilities exposed both at top level and under .utils
|
|
36
34
|
*
|
|
@@ -94,26 +92,22 @@ export interface Collection<T extends object = Record<string, unknown>, TKey ext
|
|
|
94
92
|
* sync: { sync: () => {} }
|
|
95
93
|
* })
|
|
96
94
|
*
|
|
97
|
-
* // Note: You can provide an explicit type, a schema, or both. When both are provided, the explicit type takes precedence.
|
|
98
95
|
*/
|
|
99
|
-
export declare function createCollection<
|
|
100
|
-
schema:
|
|
96
|
+
export declare function createCollection<T extends StandardSchemaV1, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}>(options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
97
|
+
schema: T;
|
|
101
98
|
utils?: TUtils;
|
|
102
|
-
}): Collection<
|
|
103
|
-
export declare function createCollection<
|
|
104
|
-
schema
|
|
99
|
+
}): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>>;
|
|
100
|
+
export declare function createCollection<T extends object, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}>(options: CollectionConfig<T, TKey, never> & {
|
|
101
|
+
schema?: never;
|
|
105
102
|
utils?: TUtils;
|
|
106
|
-
}): Collection<
|
|
107
|
-
export declare
|
|
108
|
-
|
|
109
|
-
}): Collection<ResolveType<TExplicit, TSchema, TFallback>, TKey, TUtils, TSchema, ResolveInsertInput<TExplicit, TSchema, TFallback>>;
|
|
110
|
-
export declare class CollectionImpl<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInsertInput extends object = T> {
|
|
111
|
-
config: CollectionConfig<T, TKey, TSchema, TInsertInput>;
|
|
103
|
+
}): Collection<T, TKey, TUtils, never, T>;
|
|
104
|
+
export declare class CollectionImpl<TOutput extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = {}, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInput extends object = TOutput> {
|
|
105
|
+
config: CollectionConfig<TOutput, TKey, TSchema>;
|
|
112
106
|
transactions: SortedMap<string, Transaction<any>>;
|
|
113
|
-
pendingSyncedTransactions: Array<PendingSyncedTransaction<
|
|
114
|
-
syncedData: Map<TKey,
|
|
107
|
+
pendingSyncedTransactions: Array<PendingSyncedTransaction<TOutput>>;
|
|
108
|
+
syncedData: Map<TKey, TOutput> | SortedMap<TKey, TOutput>;
|
|
115
109
|
syncedMetadata: Map<TKey, unknown>;
|
|
116
|
-
optimisticUpserts: Map<TKey,
|
|
110
|
+
optimisticUpserts: Map<TKey, TOutput>;
|
|
117
111
|
optimisticDeletes: Set<TKey>;
|
|
118
112
|
private _size;
|
|
119
113
|
private lazyIndexes;
|
|
@@ -194,7 +188,7 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
194
188
|
* @param config - Configuration object for the collection
|
|
195
189
|
* @throws Error if sync config is missing
|
|
196
190
|
*/
|
|
197
|
-
constructor(config: CollectionConfig<
|
|
191
|
+
constructor(config: CollectionConfig<TOutput, TKey, TSchema>);
|
|
198
192
|
/**
|
|
199
193
|
* Start sync immediately - internal method for compiled queries
|
|
200
194
|
* This bypasses lazy loading for special cases like live query results
|
|
@@ -261,7 +255,7 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
261
255
|
/**
|
|
262
256
|
* Get the current value for a key (virtual derived state)
|
|
263
257
|
*/
|
|
264
|
-
get(key: TKey):
|
|
258
|
+
get(key: TKey): TOutput | undefined;
|
|
265
259
|
/**
|
|
266
260
|
* Check if a key exists in the collection (virtual derived state)
|
|
267
261
|
*/
|
|
@@ -277,23 +271,23 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
277
271
|
/**
|
|
278
272
|
* Get all values (virtual derived state)
|
|
279
273
|
*/
|
|
280
|
-
values(): IterableIterator<
|
|
274
|
+
values(): IterableIterator<TOutput>;
|
|
281
275
|
/**
|
|
282
276
|
* Get all entries (virtual derived state)
|
|
283
277
|
*/
|
|
284
|
-
entries(): IterableIterator<[TKey,
|
|
278
|
+
entries(): IterableIterator<[TKey, TOutput]>;
|
|
285
279
|
/**
|
|
286
280
|
* Get all entries (virtual derived state)
|
|
287
281
|
*/
|
|
288
|
-
[Symbol.iterator](): IterableIterator<[TKey,
|
|
282
|
+
[Symbol.iterator](): IterableIterator<[TKey, TOutput]>;
|
|
289
283
|
/**
|
|
290
284
|
* Execute a callback for each entry in the collection
|
|
291
285
|
*/
|
|
292
|
-
forEach(callbackfn: (value:
|
|
286
|
+
forEach(callbackfn: (value: TOutput, key: TKey, index: number) => void): void;
|
|
293
287
|
/**
|
|
294
288
|
* Create a new array with the results of calling a function for each entry in the collection
|
|
295
289
|
*/
|
|
296
|
-
map<U>(callbackfn: (value:
|
|
290
|
+
map<U>(callbackfn: (value: TOutput, key: TKey, index: number) => U): Array<U>;
|
|
297
291
|
/**
|
|
298
292
|
* Attempts to commit pending synced transactions if there are no active transactions
|
|
299
293
|
* This method processes operations from pending transactions and applies them to the synced data
|
|
@@ -305,7 +299,7 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
305
299
|
*/
|
|
306
300
|
private scheduleTransactionCleanup;
|
|
307
301
|
private ensureStandardSchema;
|
|
308
|
-
getKeyFromItem(item:
|
|
302
|
+
getKeyFromItem(item: TOutput): TKey;
|
|
309
303
|
generateGlobalKey(key: any, item: any): string;
|
|
310
304
|
/**
|
|
311
305
|
* Creates an index on a collection for faster queries.
|
|
@@ -337,7 +331,7 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
337
331
|
* options: { language: 'en' }
|
|
338
332
|
* })
|
|
339
333
|
*/
|
|
340
|
-
createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(indexCallback: (row: SingleRowRefProxy<
|
|
334
|
+
createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(indexCallback: (row: SingleRowRefProxy<TOutput>) => any, config?: IndexOptions<TResolver>): IndexProxy<TKey>;
|
|
341
335
|
/**
|
|
342
336
|
* Resolve all lazy indexes (called when collection first syncs)
|
|
343
337
|
* @private
|
|
@@ -357,7 +351,7 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
357
351
|
* @private
|
|
358
352
|
*/
|
|
359
353
|
private updateIndexes;
|
|
360
|
-
validateData(data: unknown, type: `insert` | `update`, key?: TKey):
|
|
354
|
+
validateData(data: unknown, type: `insert` | `update`, key?: TKey): TOutput | never;
|
|
361
355
|
/**
|
|
362
356
|
* Inserts one or more items into the collection
|
|
363
357
|
* @param items - Single item or array of items to insert
|
|
@@ -394,7 +388,7 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
394
388
|
* console.log('Insert failed:', error)
|
|
395
389
|
* }
|
|
396
390
|
*/
|
|
397
|
-
insert: (data:
|
|
391
|
+
insert: (data: TInput | Array<TInput>, config?: InsertConfig) => Transaction<Record<string, unknown>> | Transaction<TOutput>;
|
|
398
392
|
/**
|
|
399
393
|
* Updates one or more items in the collection using a callback function
|
|
400
394
|
* @param keys - Single key or array of keys to update
|
|
@@ -434,10 +428,10 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
434
428
|
* console.log('Update failed:', error)
|
|
435
429
|
* }
|
|
436
430
|
*/
|
|
437
|
-
update
|
|
438
|
-
update
|
|
439
|
-
update
|
|
440
|
-
update
|
|
431
|
+
update(key: Array<TKey | unknown>, callback: (drafts: Array<WritableDeep<TInput>>) => void): TransactionType;
|
|
432
|
+
update(keys: Array<TKey | unknown>, config: OperationConfig, callback: (drafts: Array<WritableDeep<TInput>>) => void): TransactionType;
|
|
433
|
+
update(id: TKey | unknown, callback: (draft: WritableDeep<TInput>) => void): TransactionType;
|
|
434
|
+
update(id: TKey | unknown, config: OperationConfig, callback: (draft: WritableDeep<TInput>) => void): TransactionType;
|
|
441
435
|
/**
|
|
442
436
|
* Deletes one or more items from the collection
|
|
443
437
|
* @param keys - Single key or array of keys to delete
|
|
@@ -485,27 +479,27 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
485
479
|
* console.log("Todo 1 exists:", itemsMap.get("todo-1"))
|
|
486
480
|
* }
|
|
487
481
|
*/
|
|
488
|
-
get state(): Map<TKey,
|
|
482
|
+
get state(): Map<TKey, TOutput>;
|
|
489
483
|
/**
|
|
490
484
|
* Gets the current state of the collection as a Map, but only resolves when data is available
|
|
491
485
|
* Waits for the first sync commit to complete before resolving
|
|
492
486
|
*
|
|
493
487
|
* @returns Promise that resolves to a Map containing all items in the collection
|
|
494
488
|
*/
|
|
495
|
-
stateWhenReady(): Promise<Map<TKey,
|
|
489
|
+
stateWhenReady(): Promise<Map<TKey, TOutput>>;
|
|
496
490
|
/**
|
|
497
491
|
* Gets the current state of the collection as an Array
|
|
498
492
|
*
|
|
499
493
|
* @returns An Array containing all items in the collection
|
|
500
494
|
*/
|
|
501
|
-
get toArray():
|
|
495
|
+
get toArray(): TOutput[];
|
|
502
496
|
/**
|
|
503
497
|
* Gets the current state of the collection as an Array, but only resolves when data is available
|
|
504
498
|
* Waits for the first sync commit to complete before resolving
|
|
505
499
|
*
|
|
506
500
|
* @returns Promise that resolves to an Array containing all items in the collection
|
|
507
501
|
*/
|
|
508
|
-
toArrayWhenReady(): Promise<Array<
|
|
502
|
+
toArrayWhenReady(): Promise<Array<TOutput>>;
|
|
509
503
|
/**
|
|
510
504
|
* Returns the current state of the collection as an array of changes
|
|
511
505
|
* @param options - Options including optional where filter
|
|
@@ -524,7 +518,7 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
524
518
|
* whereExpression: eq(row.status, 'active')
|
|
525
519
|
* })
|
|
526
520
|
*/
|
|
527
|
-
currentStateAsChanges(options?: CurrentStateAsChangesOptions<
|
|
521
|
+
currentStateAsChanges(options?: CurrentStateAsChangesOptions<TOutput>): Array<ChangeMessage<TOutput>>;
|
|
528
522
|
/**
|
|
529
523
|
* Subscribe to changes in the collection
|
|
530
524
|
* @param callback - Function called when items change
|
|
@@ -564,11 +558,11 @@ export declare class CollectionImpl<T extends object = Record<string, unknown>,
|
|
|
564
558
|
* whereExpression: eq(row.status, 'active')
|
|
565
559
|
* })
|
|
566
560
|
*/
|
|
567
|
-
subscribeChanges(callback: (changes: Array<ChangeMessage<
|
|
561
|
+
subscribeChanges(callback: (changes: Array<ChangeMessage<TOutput>>) => void, options?: SubscribeChangesOptions<TOutput>): () => void;
|
|
568
562
|
/**
|
|
569
563
|
* Subscribe to changes for a specific key
|
|
570
564
|
*/
|
|
571
|
-
subscribeChangesKey(key: TKey, listener: ChangeListener<
|
|
565
|
+
subscribeChangesKey(key: TKey, listener: ChangeListener<TOutput, TKey>, { includeInitialState }?: {
|
|
572
566
|
includeInitialState?: boolean;
|
|
573
567
|
}): () => void;
|
|
574
568
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-only.cjs","sources":["../../src/local-only.ts"],"sourcesContent":["import type {\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n OperationType,\n ResolveType,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"./types\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Configuration interface for Local-only collection options\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @template TKey - The type of the key returned by getKey\n *\n * @remarks\n * Type resolution follows a priority order:\n * 1. If you provide an explicit type via generic parameter, it will be used\n * 2. If no explicit type is provided but a schema is, the schema's output type will be inferred\n * 3. If neither explicit type nor schema is provided, the fallback type will be used\n *\n * You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.\n */\nexport interface LocalOnlyCollectionConfig<\n TExplicit = unknown,\n TSchema extends StandardSchemaV1 = never,\n TFallback extends Record<string, unknown> = Record<string, unknown>,\n TKey extends string | number = string | number,\n> {\n /**\n * Standard Collection configuration properties\n */\n id?: string\n schema?: TSchema\n getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => TKey\n\n /**\n * Optional initial data to populate the collection with on creation\n * This data will be applied during the initial sync process\n */\n initialData?: Array<ResolveType<TExplicit, TSchema, TFallback>>\n\n /**\n * Optional asynchronous handler function called after an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onInsert?: (\n params: InsertMutationFnParams<\n ResolveType<TExplicit, TSchema, TFallback>,\n TKey,\n LocalOnlyCollectionUtils\n >\n ) => Promise<any>\n\n /**\n * Optional asynchronous handler function called after an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onUpdate?: (\n params: UpdateMutationFnParams<\n ResolveType<TExplicit, TSchema, TFallback>,\n TKey,\n LocalOnlyCollectionUtils\n >\n ) => Promise<any>\n\n /**\n * Optional asynchronous handler function called after a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onDelete?: (\n params: DeleteMutationFnParams<\n ResolveType<TExplicit, TSchema, TFallback>,\n TKey,\n LocalOnlyCollectionUtils\n >\n ) => Promise<any>\n}\n\n/**\n * Local-only collection utilities type (currently empty but matches the pattern)\n */\nexport interface LocalOnlyCollectionUtils extends UtilsRecord {}\n\n/**\n * Creates Local-only collection options for use with a standard Collection\n *\n * This is an in-memory collection that doesn't sync with external sources but uses a loopback sync config\n * that immediately \"syncs\" all optimistic changes to the collection, making them permanent.\n * Perfect for local-only data that doesn't need persistence or external synchronization.\n *\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @template TKey - The type of the key returned by getKey\n * @param config - Configuration options for the Local-only collection\n * @returns Collection options with utilities (currently empty but follows the pattern)\n *\n * @example\n * // Basic local-only collection\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Local-only collection with initial data\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * initialData: [\n * { id: 1, name: 'Item 1' },\n * { id: 2, name: 'Item 2' },\n * ],\n * })\n * )\n *\n * @example\n * // Local-only collection with mutation handlers\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * console.log('Item inserted:', transaction.mutations[0].modified)\n * // Custom logic after insert\n * },\n * })\n * )\n */\nexport function localOnlyCollectionOptions<\n TExplicit = unknown,\n TSchema extends StandardSchemaV1 = never,\n TFallback extends Record<string, unknown> = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<TExplicit, TSchema, TFallback, TKey>\n): CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>, TKey> & {\n utils: LocalOnlyCollectionUtils\n} {\n type ResolvedType = ResolveType<TExplicit, TSchema, TFallback>\n\n const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config\n\n // Create the sync configuration with transaction confirmation capability\n const syncResult = createLocalOnlySync<ResolvedType, TKey>(initialData)\n\n /**\n * Create wrapper handlers that call user handlers first, then confirm transactions\n * Wraps the user's onInsert handler to also confirm the transaction immediately\n */\n const wrappedOnInsert = async (\n params: InsertMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onInsert) {\n handlerResult = (await onInsert(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onUpdate handler that also confirms the transaction immediately\n */\n const wrappedOnUpdate = async (\n params: UpdateMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onUpdate) {\n handlerResult = (await onUpdate(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onDelete handler that also confirms the transaction immediately\n */\n const wrappedOnDelete = async (\n params: DeleteMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onDelete) {\n handlerResult = (await onDelete(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n return {\n ...restConfig,\n sync: syncResult.sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {} as LocalOnlyCollectionUtils,\n startSync: true,\n gcTime: 0,\n }\n}\n\n/**\n * Internal function to create Local-only sync configuration with transaction confirmation\n *\n * This captures the sync functions and provides synchronous confirmation of operations.\n * It creates a loopback sync that immediately confirms all optimistic operations,\n * making them permanent in the collection.\n *\n * @param initialData - Optional array of initial items to populate the collection\n * @returns Object with sync configuration and confirmOperationsSync function\n */\nfunction createLocalOnlySync<T extends object, TKey extends string | number>(\n initialData?: Array<T>\n) {\n // Capture sync functions for transaction confirmation\n let syncBegin: (() => void) | null = null\n let syncWrite: ((message: { type: OperationType; value: T }) => void) | null =\n null\n let syncCommit: (() => void) | null = null\n\n const sync: SyncConfig<T, TKey> = {\n /**\n * Sync function that captures sync parameters and applies initial data\n * @param params - Sync parameters containing begin, write, and commit functions\n * @returns Unsubscribe function (empty since no ongoing sync is needed)\n */\n sync: (params) => {\n const { begin, write, commit, markReady } = params\n\n // Capture sync functions for later use by confirmOperationsSync\n syncBegin = begin\n syncWrite = write\n syncCommit = commit\n\n // Apply initial data if provided\n if (initialData && initialData.length > 0) {\n begin()\n initialData.forEach((item) => {\n write({\n type: `insert`,\n value: item,\n })\n })\n commit()\n }\n\n // Mark collection as ready since local-only collections are immediately ready\n markReady()\n\n // Return empty unsubscribe function - no ongoing sync needed\n return () => {}\n },\n /**\n * Get sync metadata - returns empty object for local-only collections\n * @returns Empty metadata object\n */\n getSyncMetadata: () => ({}),\n }\n\n /**\n * Synchronously confirms optimistic operations by immediately writing through sync\n *\n * This loops through transaction mutations and applies them to move from optimistic to synced state.\n * It's called after user handlers to make optimistic changes permanent.\n *\n * @param mutations - Array of mutation objects from the transaction\n */\n const confirmOperationsSync = (mutations: Array<any>) => {\n if (!syncBegin || !syncWrite || !syncCommit) {\n return // Sync not initialized yet, which is fine\n }\n\n // Immediately write back through sync interface\n syncBegin()\n mutations.forEach((mutation) => {\n if (syncWrite) {\n syncWrite({\n type: mutation.type,\n value: mutation.modified,\n })\n }\n })\n syncCommit()\n }\n\n return {\n sync,\n confirmOperationsSync,\n }\n}\n"],"names":[],"mappings":";;AAyIO,SAAS,2BAMd,QAGA;AAGA,QAAM,EAAE,aAAa,UAAU,UAAU,UAAU,GAAG,eAAe;AAGrE,QAAM,aAAa,oBAAwC,WAAW;AAMtE,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,WAAW;AAAA,IACjB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO,CAAA;AAAA,IACP,WAAW;AAAA,IACX,QAAQ;AAAA,EAAA;AAEZ;AAYA,SAAS,oBACP,aACA;AAEA,MAAI,YAAiC;AACrC,MAAI,YACF;AACF,MAAI,aAAkC;AAEtC,QAAM,OAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMhC,MAAM,CAAC,WAAW;AAChB,YAAM,EAAE,OAAO,OAAO,QAAQ,cAAc;AAG5C,kBAAY;AACZ,kBAAY;AACZ,mBAAa;AAGb,UAAI,eAAe,YAAY,SAAS,GAAG;AACzC,cAAA;AACA,oBAAY,QAAQ,CAAC,SAAS;AAC5B,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH,CAAC;AACD,eAAA;AAAA,MACF;AAGA,gBAAA;AAGA,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,iBAAiB,OAAO,CAAA;AAAA,EAAC;AAW3B,QAAM,wBAAwB,CAAC,cAA0B;AACvD,QAAI,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY;AAC3C;AAAA,IACF;AAGA,cAAA;AACA,cAAU,QAAQ,CAAC,aAAa;AAC9B,UAAI,WAAW;AACb,kBAAU;AAAA,UACR,MAAM,SAAS;AAAA,UACf,OAAO,SAAS;AAAA,QAAA,CACjB;AAAA,MACH;AAAA,IACF,CAAC;AACD,eAAA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;;"}
|
|
1
|
+
{"version":3,"file":"local-only.cjs","sources":["../../src/local-only.ts"],"sourcesContent":["import type {\n BaseCollectionConfig,\n CollectionConfig,\n DeleteMutationFnParams,\n InferSchemaOutput,\n InsertMutationFnParams,\n OperationType,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"./types\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Configuration interface for Local-only collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n * @template TKey - The type of the key returned by `getKey`\n */\nexport interface LocalOnlyCollectionConfig<\n T extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TKey extends string | number = string | number,\n> extends Omit<\n BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,\n `gcTime` | `startSync`\n > {\n /**\n * Optional initial data to populate the collection with on creation\n * This data will be applied during the initial sync process\n */\n initialData?: Array<T>\n}\n\n/**\n * Local-only collection utilities type (currently empty but matches the pattern)\n */\nexport interface LocalOnlyCollectionUtils extends UtilsRecord {}\n\n/**\n * Creates Local-only collection options for use with a standard Collection\n *\n * This is an in-memory collection that doesn't sync with external sources but uses a loopback sync config\n * that immediately \"syncs\" all optimistic changes to the collection, making them permanent.\n * Perfect for local-only data that doesn't need persistence or external synchronization.\n *\n * @template T - The schema type if a schema is provided, otherwise the type of items in the collection\n * @template TKey - The type of the key returned by getKey\n * @param config - Configuration options for the Local-only collection\n * @returns Collection options with utilities (currently empty but follows the pattern)\n *\n * @example\n * // Basic local-only collection\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Local-only collection with initial data\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * initialData: [\n * { id: 1, name: 'Item 1' },\n * { id: 2, name: 'Item 2' },\n * ],\n * })\n * )\n *\n * @example\n * // Local-only collection with mutation handlers\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * console.log('Item inserted:', transaction.mutations[0].modified)\n * // Custom logic after insert\n * },\n * })\n * )\n */\n\n// Overload for when schema is provided\nexport function localOnlyCollectionOptions<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<InferSchemaOutput<T>, T, TKey> & {\n schema: T\n }\n): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {\n utils: LocalOnlyCollectionUtils\n schema: T\n}\n\n// Overload for when no schema is provided\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function localOnlyCollectionOptions<\n T extends object,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<T, never, TKey> & {\n schema?: never // prohibit schema\n }\n): CollectionConfig<T, TKey> & {\n utils: LocalOnlyCollectionUtils\n schema?: never // no schema in the result\n}\n\nexport function localOnlyCollectionOptions(\n config: LocalOnlyCollectionConfig<any, any, string | number>\n): CollectionConfig<any, string | number, any> & {\n utils: LocalOnlyCollectionUtils\n schema?: StandardSchemaV1\n} {\n const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config\n\n // Create the sync configuration with transaction confirmation capability\n const syncResult = createLocalOnlySync(initialData)\n\n /**\n * Create wrapper handlers that call user handlers first, then confirm transactions\n * Wraps the user's onInsert handler to also confirm the transaction immediately\n */\n const wrappedOnInsert = async (\n params: InsertMutationFnParams<\n any,\n string | number,\n LocalOnlyCollectionUtils\n >\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onInsert) {\n handlerResult = (await onInsert(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onUpdate handler that also confirms the transaction immediately\n */\n const wrappedOnUpdate = async (\n params: UpdateMutationFnParams<\n any,\n string | number,\n LocalOnlyCollectionUtils\n >\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onUpdate) {\n handlerResult = (await onUpdate(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onDelete handler that also confirms the transaction immediately\n */\n const wrappedOnDelete = async (\n params: DeleteMutationFnParams<\n any,\n string | number,\n LocalOnlyCollectionUtils\n >\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onDelete) {\n handlerResult = (await onDelete(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n return {\n ...restConfig,\n sync: syncResult.sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {} as LocalOnlyCollectionUtils,\n startSync: true,\n gcTime: 0,\n }\n}\n\n/**\n * Internal function to create Local-only sync configuration with transaction confirmation\n *\n * This captures the sync functions and provides synchronous confirmation of operations.\n * It creates a loopback sync that immediately confirms all optimistic operations,\n * making them permanent in the collection.\n *\n * @param initialData - Optional array of initial items to populate the collection\n * @returns Object with sync configuration and confirmOperationsSync function\n */\nfunction createLocalOnlySync<T extends object, TKey extends string | number>(\n initialData?: Array<T>\n) {\n // Capture sync functions for transaction confirmation\n let syncBegin: (() => void) | null = null\n let syncWrite: ((message: { type: OperationType; value: T }) => void) | null =\n null\n let syncCommit: (() => void) | null = null\n\n const sync: SyncConfig<T, TKey> = {\n /**\n * Sync function that captures sync parameters and applies initial data\n * @param params - Sync parameters containing begin, write, and commit functions\n * @returns Unsubscribe function (empty since no ongoing sync is needed)\n */\n sync: (params) => {\n const { begin, write, commit, markReady } = params\n\n // Capture sync functions for later use by confirmOperationsSync\n syncBegin = begin\n syncWrite = write\n syncCommit = commit\n\n // Apply initial data if provided\n if (initialData && initialData.length > 0) {\n begin()\n initialData.forEach((item) => {\n write({\n type: `insert`,\n value: item,\n })\n })\n commit()\n }\n\n // Mark collection as ready since local-only collections are immediately ready\n markReady()\n\n // Return empty unsubscribe function - no ongoing sync needed\n return () => {}\n },\n /**\n * Get sync metadata - returns empty object for local-only collections\n * @returns Empty metadata object\n */\n getSyncMetadata: () => ({}),\n }\n\n /**\n * Synchronously confirms optimistic operations by immediately writing through sync\n *\n * This loops through transaction mutations and applies them to move from optimistic to synced state.\n * It's called after user handlers to make optimistic changes permanent.\n *\n * @param mutations - Array of mutation objects from the transaction\n */\n const confirmOperationsSync = (mutations: Array<any>) => {\n if (!syncBegin || !syncWrite || !syncCommit) {\n return // Sync not initialized yet, which is fine\n }\n\n // Immediately write back through sync interface\n syncBegin()\n mutations.forEach((mutation) => {\n if (syncWrite) {\n syncWrite({\n type: mutation.type,\n value: mutation.modified,\n })\n }\n })\n syncCommit()\n }\n\n return {\n sync,\n confirmOperationsSync,\n }\n}\n"],"names":[],"mappings":";;AA+GO,SAAS,2BACd,QAIA;AACA,QAAM,EAAE,aAAa,UAAU,UAAU,UAAU,GAAG,eAAe;AAGrE,QAAM,aAAa,oBAAoB,WAAW;AAMlD,QAAM,kBAAkB,OACtB,WAKG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WAKG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WAKG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,WAAW;AAAA,IACjB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO,CAAA;AAAA,IACP,WAAW;AAAA,IACX,QAAQ;AAAA,EAAA;AAEZ;AAYA,SAAS,oBACP,aACA;AAEA,MAAI,YAAiC;AACrC,MAAI,YACF;AACF,MAAI,aAAkC;AAEtC,QAAM,OAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMhC,MAAM,CAAC,WAAW;AAChB,YAAM,EAAE,OAAO,OAAO,QAAQ,cAAc;AAG5C,kBAAY;AACZ,kBAAY;AACZ,mBAAa;AAGb,UAAI,eAAe,YAAY,SAAS,GAAG;AACzC,cAAA;AACA,oBAAY,QAAQ,CAAC,SAAS;AAC5B,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH,CAAC;AACD,eAAA;AAAA,MACF;AAGA,gBAAA;AAGA,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,iBAAiB,OAAO,CAAA;AAAA,EAAC;AAW3B,QAAM,wBAAwB,CAAC,cAA0B;AACvD,QAAI,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY;AAC3C;AAAA,IACF;AAGA,cAAA;AACA,cAAU,QAAQ,CAAC,aAAa;AAC9B,UAAI,WAAW;AACb,kBAAU;AAAA,UACR,MAAM,SAAS;AAAA,UACf,OAAO,SAAS;AAAA,QAAA,CACjB;AAAA,MACH;AAAA,IACF,CAAC;AACD,eAAA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;;"}
|
|
@@ -1,50 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseCollectionConfig, CollectionConfig, InferSchemaOutput, UtilsRecord } from './types.cjs';
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
3
|
/**
|
|
4
4
|
* Configuration interface for Local-only collection options
|
|
5
|
-
* @template
|
|
6
|
-
* @template TSchema - The schema type for validation
|
|
7
|
-
* @template
|
|
8
|
-
* @template TKey - The type of the key returned by getKey
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* Type resolution follows a priority order:
|
|
12
|
-
* 1. If you provide an explicit type via generic parameter, it will be used
|
|
13
|
-
* 2. If no explicit type is provided but a schema is, the schema's output type will be inferred
|
|
14
|
-
* 3. If neither explicit type nor schema is provided, the fallback type will be used
|
|
15
|
-
*
|
|
16
|
-
* You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.
|
|
5
|
+
* @template T - The type of items in the collection
|
|
6
|
+
* @template TSchema - The schema type for validation
|
|
7
|
+
* @template TKey - The type of the key returned by `getKey`
|
|
17
8
|
*/
|
|
18
|
-
export interface LocalOnlyCollectionConfig<
|
|
19
|
-
/**
|
|
20
|
-
* Standard Collection configuration properties
|
|
21
|
-
*/
|
|
22
|
-
id?: string;
|
|
23
|
-
schema?: TSchema;
|
|
24
|
-
getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => TKey;
|
|
9
|
+
export interface LocalOnlyCollectionConfig<T extends object = object, TSchema extends StandardSchemaV1 = never, TKey extends string | number = string | number> extends Omit<BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>, `gcTime` | `startSync`> {
|
|
25
10
|
/**
|
|
26
11
|
* Optional initial data to populate the collection with on creation
|
|
27
12
|
* This data will be applied during the initial sync process
|
|
28
13
|
*/
|
|
29
|
-
initialData?: Array<
|
|
30
|
-
/**
|
|
31
|
-
* Optional asynchronous handler function called after an insert operation
|
|
32
|
-
* @param params Object containing transaction and collection information
|
|
33
|
-
* @returns Promise resolving to any value
|
|
34
|
-
*/
|
|
35
|
-
onInsert?: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
36
|
-
/**
|
|
37
|
-
* Optional asynchronous handler function called after an update operation
|
|
38
|
-
* @param params Object containing transaction and collection information
|
|
39
|
-
* @returns Promise resolving to any value
|
|
40
|
-
*/
|
|
41
|
-
onUpdate?: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
42
|
-
/**
|
|
43
|
-
* Optional asynchronous handler function called after a delete operation
|
|
44
|
-
* @param params Object containing transaction and collection information
|
|
45
|
-
* @returns Promise resolving to any value
|
|
46
|
-
*/
|
|
47
|
-
onDelete?: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
14
|
+
initialData?: Array<T>;
|
|
48
15
|
}
|
|
49
16
|
/**
|
|
50
17
|
* Local-only collection utilities type (currently empty but matches the pattern)
|
|
@@ -58,9 +25,7 @@ export interface LocalOnlyCollectionUtils extends UtilsRecord {
|
|
|
58
25
|
* that immediately "syncs" all optimistic changes to the collection, making them permanent.
|
|
59
26
|
* Perfect for local-only data that doesn't need persistence or external synchronization.
|
|
60
27
|
*
|
|
61
|
-
* @template
|
|
62
|
-
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
63
|
-
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
28
|
+
* @template T - The schema type if a schema is provided, otherwise the type of items in the collection
|
|
64
29
|
* @template TKey - The type of the key returned by getKey
|
|
65
30
|
* @param config - Configuration options for the Local-only collection
|
|
66
31
|
* @returns Collection options with utilities (currently empty but follows the pattern)
|
|
@@ -97,6 +62,15 @@ export interface LocalOnlyCollectionUtils extends UtilsRecord {
|
|
|
97
62
|
* })
|
|
98
63
|
* )
|
|
99
64
|
*/
|
|
100
|
-
export declare function localOnlyCollectionOptions<
|
|
65
|
+
export declare function localOnlyCollectionOptions<T extends StandardSchemaV1, TKey extends string | number = string | number>(config: LocalOnlyCollectionConfig<InferSchemaOutput<T>, T, TKey> & {
|
|
66
|
+
schema: T;
|
|
67
|
+
}): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
68
|
+
utils: LocalOnlyCollectionUtils;
|
|
69
|
+
schema: T;
|
|
70
|
+
};
|
|
71
|
+
export declare function localOnlyCollectionOptions<T extends object, TKey extends string | number = string | number>(config: LocalOnlyCollectionConfig<T, never, TKey> & {
|
|
72
|
+
schema?: never;
|
|
73
|
+
}): CollectionConfig<T, TKey> & {
|
|
101
74
|
utils: LocalOnlyCollectionUtils;
|
|
75
|
+
schema?: never;
|
|
102
76
|
};
|
|
@@ -70,10 +70,7 @@ function localStorageCollectionOptions(config) {
|
|
|
70
70
|
if (config.onInsert) {
|
|
71
71
|
handlerResult = await config.onInsert(params) ?? {};
|
|
72
72
|
}
|
|
73
|
-
const currentData = loadFromStorage(
|
|
74
|
-
config.storageKey,
|
|
75
|
-
storage
|
|
76
|
-
);
|
|
73
|
+
const currentData = loadFromStorage(config.storageKey, storage);
|
|
77
74
|
params.transaction.mutations.forEach((mutation) => {
|
|
78
75
|
const key = config.getKey(mutation.modified);
|
|
79
76
|
const storedItem = {
|
|
@@ -94,10 +91,7 @@ function localStorageCollectionOptions(config) {
|
|
|
94
91
|
if (config.onUpdate) {
|
|
95
92
|
handlerResult = await config.onUpdate(params) ?? {};
|
|
96
93
|
}
|
|
97
|
-
const currentData = loadFromStorage(
|
|
98
|
-
config.storageKey,
|
|
99
|
-
storage
|
|
100
|
-
);
|
|
94
|
+
const currentData = loadFromStorage(config.storageKey, storage);
|
|
101
95
|
params.transaction.mutations.forEach((mutation) => {
|
|
102
96
|
const key = config.getKey(mutation.modified);
|
|
103
97
|
const storedItem = {
|
|
@@ -115,10 +109,7 @@ function localStorageCollectionOptions(config) {
|
|
|
115
109
|
if (config.onDelete) {
|
|
116
110
|
handlerResult = await config.onDelete(params) ?? {};
|
|
117
111
|
}
|
|
118
|
-
const currentData = loadFromStorage(
|
|
119
|
-
config.storageKey,
|
|
120
|
-
storage
|
|
121
|
-
);
|
|
112
|
+
const currentData = loadFromStorage(config.storageKey, storage);
|
|
122
113
|
params.transaction.mutations.forEach((mutation) => {
|
|
123
114
|
const key = config.getKey(mutation.original);
|
|
124
115
|
currentData.delete(key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-storage.cjs","sources":["../../src/local-storage.ts"],"sourcesContent":["import {\n InvalidStorageDataFormatError,\n InvalidStorageObjectFormatError,\n NoStorageAvailableError,\n NoStorageEventApiError,\n SerializationError,\n StorageKeyRequiredError,\n} from \"./errors\"\nimport type {\n CollectionConfig,\n DeleteMutationFnParams,\n InsertMutationFnParams,\n ResolveType,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"./types\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Storage API interface - subset of DOM Storage that we need\n */\nexport type StorageApi = Pick<Storage, `getItem` | `setItem` | `removeItem`>\n\n/**\n * Storage event API - subset of Window for 'storage' events only\n */\nexport type StorageEventApi = {\n addEventListener: (\n type: `storage`,\n listener: (event: StorageEvent) => void\n ) => void\n removeEventListener: (\n type: `storage`,\n listener: (event: StorageEvent) => void\n ) => void\n}\n\n/**\n * Internal storage format that includes version tracking\n */\ninterface StoredItem<T> {\n versionKey: string\n data: T\n}\n\n/**\n * Configuration interface for localStorage collection options\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n *\n * @remarks\n * Type resolution follows a priority order:\n * 1. If you provide an explicit type via generic parameter, it will be used\n * 2. If no explicit type is provided but a schema is, the schema's output type will be inferred\n * 3. If neither explicit type nor schema is provided, the fallback type will be used\n *\n * You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.\n */\nexport interface LocalStorageCollectionConfig<\n TExplicit = unknown,\n TSchema extends StandardSchemaV1 = never,\n TFallback extends object = Record<string, unknown>,\n> {\n /**\n * The key to use for storing the collection data in localStorage/sessionStorage\n */\n storageKey: string\n\n /**\n * Storage API to use (defaults to window.localStorage)\n * Can be any object that implements the Storage interface (e.g., sessionStorage)\n */\n storage?: StorageApi\n\n /**\n * Storage event API to use for cross-tab synchronization (defaults to window)\n * Can be any object that implements addEventListener/removeEventListener for storage events\n */\n storageEventApi?: StorageEventApi\n\n /**\n * Collection identifier (defaults to \"local-collection:{storageKey}\" if not provided)\n */\n id?: string\n schema?: TSchema\n getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`getKey`]\n sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`sync`]\n\n /**\n * Optional asynchronous handler function called before an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onInsert?: (\n params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>\n ) => Promise<any>\n\n /**\n * Optional asynchronous handler function called before an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onUpdate?: (\n params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>\n ) => Promise<any>\n\n /**\n * Optional asynchronous handler function called before a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onDelete?: (\n params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>\n ) => Promise<any>\n}\n\n/**\n * Type for the clear utility function\n */\nexport type ClearStorageFn = () => void\n\n/**\n * Type for the getStorageSize utility function\n */\nexport type GetStorageSizeFn = () => number\n\n/**\n * LocalStorage collection utilities type\n */\nexport interface LocalStorageCollectionUtils extends UtilsRecord {\n clearStorage: ClearStorageFn\n getStorageSize: GetStorageSizeFn\n}\n\n/**\n * Validates that a value can be JSON serialized\n * @param value - The value to validate for JSON serialization\n * @param operation - The operation type being performed (for error messages)\n * @throws Error if the value cannot be JSON serialized\n */\nfunction validateJsonSerializable(value: any, operation: string): void {\n try {\n JSON.stringify(value)\n } catch (error) {\n throw new SerializationError(\n operation,\n error instanceof Error ? error.message : String(error)\n )\n }\n}\n\n/**\n * Generate a UUID for version tracking\n * @returns A unique identifier string for tracking data versions\n */\nfunction generateUuid(): string {\n return crypto.randomUUID()\n}\n\n/**\n * Creates localStorage collection options for use with a standard Collection\n *\n * This function creates a collection that persists data to localStorage/sessionStorage\n * and synchronizes changes across browser tabs using storage events.\n *\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @param config - Configuration options for the localStorage collection\n * @returns Collection options with utilities including clearStorage and getStorageSize\n *\n * @example\n * // Basic localStorage collection\n * const collection = createCollection(\n * localStorageCollectionOptions({\n * storageKey: 'todos',\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // localStorage collection with custom storage\n * const collection = createCollection(\n * localStorageCollectionOptions({\n * storageKey: 'todos',\n * storage: window.sessionStorage, // Use sessionStorage instead\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // localStorage collection with mutation handlers\n * const collection = createCollection(\n * localStorageCollectionOptions({\n * storageKey: 'todos',\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * console.log('Item inserted:', transaction.mutations[0].modified)\n * },\n * })\n * )\n */\nexport function localStorageCollectionOptions<\n TExplicit = unknown,\n TSchema extends StandardSchemaV1 = never,\n TFallback extends object = Record<string, unknown>,\n>(\n config: LocalStorageCollectionConfig<TExplicit, TSchema, TFallback>\n): Omit<CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>, `id`> & {\n id: string\n utils: LocalStorageCollectionUtils\n} {\n type ResolvedType = ResolveType<TExplicit, TSchema, TFallback>\n\n // Validate required parameters\n if (!config.storageKey) {\n throw new StorageKeyRequiredError()\n }\n\n // Default to window.localStorage if no storage is provided\n const storage =\n config.storage ||\n (typeof window !== `undefined` ? window.localStorage : null)\n\n if (!storage) {\n throw new NoStorageAvailableError()\n }\n\n // Default to window for storage events if not provided\n const storageEventApi =\n config.storageEventApi || (typeof window !== `undefined` ? window : null)\n\n if (!storageEventApi) {\n throw new NoStorageEventApiError()\n }\n\n // Track the last known state to detect changes\n const lastKnownData = new Map<string | number, StoredItem<ResolvedType>>()\n\n // Create the sync configuration\n const sync = createLocalStorageSync<ResolvedType>(\n config.storageKey,\n storage,\n storageEventApi,\n config.getKey,\n lastKnownData\n )\n\n /**\n * Manual trigger function for local sync updates\n * Forces a check for storage changes and updates the collection if needed\n */\n const triggerLocalSync = () => {\n if (sync.manualTrigger) {\n sync.manualTrigger()\n }\n }\n\n /**\n * Save data to storage\n * @param dataMap - Map of items with version tracking to save to storage\n */\n const saveToStorage = (\n dataMap: Map<string | number, StoredItem<ResolvedType>>\n ): void => {\n try {\n // Convert Map to object format for storage\n const objectData: Record<string, StoredItem<ResolvedType>> = {}\n dataMap.forEach((storedItem, key) => {\n objectData[String(key)] = storedItem\n })\n const serialized = JSON.stringify(objectData)\n storage.setItem(config.storageKey, serialized)\n } catch (error) {\n console.error(\n `[LocalStorageCollection] Error saving data to storage key \"${config.storageKey}\":`,\n error\n )\n throw error\n }\n }\n\n /**\n * Removes all collection data from the configured storage\n */\n const clearStorage: ClearStorageFn = (): void => {\n storage.removeItem(config.storageKey)\n }\n\n /**\n * Get the size of the stored data in bytes (approximate)\n * @returns The approximate size in bytes of the stored collection data\n */\n const getStorageSize: GetStorageSizeFn = (): number => {\n const data = storage.getItem(config.storageKey)\n return data ? new Blob([data]).size : 0\n }\n\n /*\n * Create wrapper handlers for direct persistence operations that perform actual storage operations\n * Wraps the user's onInsert handler to also save changes to localStorage\n */\n const wrappedOnInsert = async (\n params: InsertMutationFnParams<ResolvedType>\n ) => {\n // Validate that all values in the transaction can be JSON serialized\n params.transaction.mutations.forEach((mutation) => {\n validateJsonSerializable(mutation.modified, `insert`)\n })\n\n // Call the user handler BEFORE persisting changes (if provided)\n let handlerResult: any = {}\n if (config.onInsert) {\n handlerResult = (await config.onInsert(params)) ?? {}\n }\n\n // Always persist to storage\n // Load current data from storage\n const currentData = loadFromStorage<ResolvedType>(\n config.storageKey,\n storage\n )\n\n // Add new items with version keys\n params.transaction.mutations.forEach((mutation) => {\n const key = config.getKey(mutation.modified)\n const storedItem: StoredItem<ResolvedType> = {\n versionKey: generateUuid(),\n data: mutation.modified,\n }\n currentData.set(key, storedItem)\n })\n\n // Save to storage\n saveToStorage(currentData)\n\n // Manually trigger local sync since storage events don't fire for current tab\n triggerLocalSync()\n\n return handlerResult\n }\n\n const wrappedOnUpdate = async (\n params: UpdateMutationFnParams<ResolvedType>\n ) => {\n // Validate that all values in the transaction can be JSON serialized\n params.transaction.mutations.forEach((mutation) => {\n validateJsonSerializable(mutation.modified, `update`)\n })\n\n // Call the user handler BEFORE persisting changes (if provided)\n let handlerResult: any = {}\n if (config.onUpdate) {\n handlerResult = (await config.onUpdate(params)) ?? {}\n }\n\n // Always persist to storage\n // Load current data from storage\n const currentData = loadFromStorage<ResolvedType>(\n config.storageKey,\n storage\n )\n\n // Update items with new version keys\n params.transaction.mutations.forEach((mutation) => {\n const key = config.getKey(mutation.modified)\n const storedItem: StoredItem<ResolvedType> = {\n versionKey: generateUuid(),\n data: mutation.modified,\n }\n currentData.set(key, storedItem)\n })\n\n // Save to storage\n saveToStorage(currentData)\n\n // Manually trigger local sync since storage events don't fire for current tab\n triggerLocalSync()\n\n return handlerResult\n }\n\n const wrappedOnDelete = async (\n params: DeleteMutationFnParams<ResolvedType>\n ) => {\n // Call the user handler BEFORE persisting changes (if provided)\n let handlerResult: any = {}\n if (config.onDelete) {\n handlerResult = (await config.onDelete(params)) ?? {}\n }\n\n // Always persist to storage\n // Load current data from storage\n const currentData = loadFromStorage<ResolvedType>(\n config.storageKey,\n storage\n )\n\n // Remove items\n params.transaction.mutations.forEach((mutation) => {\n // For delete operations, mutation.original contains the full object\n const key = config.getKey(mutation.original as ResolvedType)\n currentData.delete(key)\n })\n\n // Save to storage\n saveToStorage(currentData)\n\n // Manually trigger local sync since storage events don't fire for current tab\n triggerLocalSync()\n\n return handlerResult\n }\n\n // Extract standard Collection config properties\n const {\n storageKey: _storageKey,\n storage: _storage,\n storageEventApi: _storageEventApi,\n onInsert: _onInsert,\n onUpdate: _onUpdate,\n onDelete: _onDelete,\n id,\n ...restConfig\n } = config\n\n // Default id to a pattern based on storage key if not provided\n const collectionId = id ?? `local-collection:${config.storageKey}`\n\n return {\n ...restConfig,\n id: collectionId,\n sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n clearStorage,\n getStorageSize,\n },\n }\n}\n\n/**\n * Load data from storage and return as a Map\n * @param storageKey - The key used to store data in the storage API\n * @param storage - The storage API to load from (localStorage, sessionStorage, etc.)\n * @returns Map of stored items with version tracking, or empty Map if loading fails\n */\nfunction loadFromStorage<T extends object>(\n storageKey: string,\n storage: StorageApi\n): Map<string | number, StoredItem<T>> {\n try {\n const rawData = storage.getItem(storageKey)\n if (!rawData) {\n return new Map()\n }\n\n const parsed = JSON.parse(rawData)\n const dataMap = new Map<string | number, StoredItem<T>>()\n\n // Handle object format where keys map to StoredItem values\n if (\n typeof parsed === `object` &&\n parsed !== null &&\n !Array.isArray(parsed)\n ) {\n Object.entries(parsed).forEach(([key, value]) => {\n // Runtime check to ensure the value has the expected StoredItem structure\n if (\n value &&\n typeof value === `object` &&\n `versionKey` in value &&\n `data` in value\n ) {\n const storedItem = value as StoredItem<T>\n dataMap.set(key, storedItem)\n } else {\n throw new InvalidStorageDataFormatError(storageKey, key)\n }\n })\n } else {\n throw new InvalidStorageObjectFormatError(storageKey)\n }\n\n return dataMap\n } catch (error) {\n console.warn(\n `[LocalStorageCollection] Error loading data from storage key \"${storageKey}\":`,\n error\n )\n return new Map()\n }\n}\n\n/**\n * Internal function to create localStorage sync configuration\n * Creates a sync configuration that handles localStorage persistence and cross-tab synchronization\n * @param storageKey - The key used for storing data in localStorage\n * @param storage - The storage API to use (localStorage, sessionStorage, etc.)\n * @param storageEventApi - The event API for listening to storage changes\n * @param getKey - Function to extract the key from an item\n * @param lastKnownData - Map tracking the last known state for change detection\n * @returns Sync configuration with manual trigger capability\n */\nfunction createLocalStorageSync<T extends object>(\n storageKey: string,\n storage: StorageApi,\n storageEventApi: StorageEventApi,\n _getKey: (item: T) => string | number,\n lastKnownData: Map<string | number, StoredItem<T>>\n): SyncConfig<T> & { manualTrigger?: () => void } {\n let syncParams: Parameters<SyncConfig<T>[`sync`]>[0] | null = null\n\n /**\n * Compare two Maps to find differences using version keys\n * @param oldData - The previous state of stored items\n * @param newData - The current state of stored items\n * @returns Array of changes with type, key, and value information\n */\n const findChanges = (\n oldData: Map<string | number, StoredItem<T>>,\n newData: Map<string | number, StoredItem<T>>\n ): Array<{\n type: `insert` | `update` | `delete`\n key: string | number\n value?: T\n }> => {\n const changes: Array<{\n type: `insert` | `update` | `delete`\n key: string | number\n value?: T\n }> = []\n\n // Check for deletions and updates\n oldData.forEach((oldStoredItem, key) => {\n const newStoredItem = newData.get(key)\n if (!newStoredItem) {\n changes.push({ type: `delete`, key, value: oldStoredItem.data })\n } else if (oldStoredItem.versionKey !== newStoredItem.versionKey) {\n changes.push({ type: `update`, key, value: newStoredItem.data })\n }\n })\n\n // Check for insertions\n newData.forEach((newStoredItem, key) => {\n if (!oldData.has(key)) {\n changes.push({ type: `insert`, key, value: newStoredItem.data })\n }\n })\n\n return changes\n }\n\n /**\n * Process storage changes and update collection\n * Loads new data from storage, compares with last known state, and applies changes\n */\n const processStorageChanges = () => {\n if (!syncParams) return\n\n const { begin, write, commit } = syncParams\n\n // Load the new data\n const newData = loadFromStorage<T>(storageKey, storage)\n\n // Find the specific changes\n const changes = findChanges(lastKnownData, newData)\n\n if (changes.length > 0) {\n begin()\n changes.forEach(({ type, value }) => {\n if (value) {\n validateJsonSerializable(value, type)\n write({ type, value })\n }\n })\n commit()\n\n // Update lastKnownData\n lastKnownData.clear()\n newData.forEach((storedItem, key) => {\n lastKnownData.set(key, storedItem)\n })\n }\n }\n\n const syncConfig: SyncConfig<T> & { manualTrigger?: () => void } = {\n sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n const { begin, write, commit, markReady } = params\n\n // Store sync params for later use\n syncParams = params\n\n // Initial load\n const initialData = loadFromStorage<T>(storageKey, storage)\n if (initialData.size > 0) {\n begin()\n initialData.forEach((storedItem) => {\n validateJsonSerializable(storedItem.data, `load`)\n write({ type: `insert`, value: storedItem.data })\n })\n commit()\n }\n\n // Update lastKnownData\n lastKnownData.clear()\n initialData.forEach((storedItem, key) => {\n lastKnownData.set(key, storedItem)\n })\n\n // Mark collection as ready after initial load\n markReady()\n\n // Listen for storage events from other tabs\n const handleStorageEvent = (event: StorageEvent) => {\n // Only respond to changes to our specific key and from our storage\n if (event.key !== storageKey || event.storageArea !== storage) {\n return\n }\n\n processStorageChanges()\n }\n\n // Add storage event listener for cross-tab sync\n storageEventApi.addEventListener(`storage`, handleStorageEvent)\n\n // Note: Cleanup is handled automatically by the collection when it's disposed\n },\n\n /**\n * Get sync metadata - returns storage key information\n * @returns Object containing storage key and storage type metadata\n */\n getSyncMetadata: () => ({\n storageKey,\n storageType:\n storage === (typeof window !== `undefined` ? window.localStorage : null)\n ? `localStorage`\n : `custom`,\n }),\n\n // Manual trigger function for local updates\n manualTrigger: processStorageChanges,\n }\n\n return syncConfig\n}\n"],"names":["SerializationError","StorageKeyRequiredError","NoStorageAvailableError","NoStorageEventApiError","InvalidStorageDataFormatError","InvalidStorageObjectFormatError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-storage.cjs","sources":["../../src/local-storage.ts"],"sourcesContent":["import {\n InvalidStorageDataFormatError,\n InvalidStorageObjectFormatError,\n NoStorageAvailableError,\n NoStorageEventApiError,\n SerializationError,\n StorageKeyRequiredError,\n} from \"./errors\"\nimport type {\n BaseCollectionConfig,\n CollectionConfig,\n DeleteMutationFnParams,\n InferSchemaOutput,\n InsertMutationFnParams,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"./types\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Storage API interface - subset of DOM Storage that we need\n */\nexport type StorageApi = Pick<Storage, `getItem` | `setItem` | `removeItem`>\n\n/**\n * Storage event API - subset of Window for 'storage' events only\n */\nexport type StorageEventApi = {\n addEventListener: (\n type: `storage`,\n listener: (event: StorageEvent) => void\n ) => void\n removeEventListener: (\n type: `storage`,\n listener: (event: StorageEvent) => void\n ) => void\n}\n\n/**\n * Internal storage format that includes version tracking\n */\ninterface StoredItem<T> {\n versionKey: string\n data: T\n}\n\n/**\n * Configuration interface for localStorage collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n * @template TKey - The type of the key returned by `getKey`\n */\nexport interface LocalStorageCollectionConfig<\n T extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TKey extends string | number = string | number,\n> extends BaseCollectionConfig<T, TKey, TSchema> {\n /**\n * The key to use for storing the collection data in localStorage/sessionStorage\n */\n storageKey: string\n\n /**\n * Storage API to use (defaults to window.localStorage)\n * Can be any object that implements the Storage interface (e.g., sessionStorage)\n */\n storage?: StorageApi\n\n /**\n * Storage event API to use for cross-tab synchronization (defaults to window)\n * Can be any object that implements addEventListener/removeEventListener for storage events\n */\n storageEventApi?: StorageEventApi\n}\n\n/**\n * Type for the clear utility function\n */\nexport type ClearStorageFn = () => void\n\n/**\n * Type for the getStorageSize utility function\n */\nexport type GetStorageSizeFn = () => number\n\n/**\n * LocalStorage collection utilities type\n */\nexport interface LocalStorageCollectionUtils extends UtilsRecord {\n clearStorage: ClearStorageFn\n getStorageSize: GetStorageSizeFn\n}\n\n/**\n * Validates that a value can be JSON serialized\n * @param value - The value to validate for JSON serialization\n * @param operation - The operation type being performed (for error messages)\n * @throws Error if the value cannot be JSON serialized\n */\nfunction validateJsonSerializable(value: any, operation: string): void {\n try {\n JSON.stringify(value)\n } catch (error) {\n throw new SerializationError(\n operation,\n error instanceof Error ? error.message : String(error)\n )\n }\n}\n\n/**\n * Generate a UUID for version tracking\n * @returns A unique identifier string for tracking data versions\n */\nfunction generateUuid(): string {\n return crypto.randomUUID()\n}\n\n/**\n * Creates localStorage collection options for use with a standard Collection\n *\n * This function creates a collection that persists data to localStorage/sessionStorage\n * and synchronizes changes across browser tabs using storage events.\n *\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @param config - Configuration options for the localStorage collection\n * @returns Collection options with utilities including clearStorage and getStorageSize\n *\n * @example\n * // Basic localStorage collection\n * const collection = createCollection(\n * localStorageCollectionOptions({\n * storageKey: 'todos',\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // localStorage collection with custom storage\n * const collection = createCollection(\n * localStorageCollectionOptions({\n * storageKey: 'todos',\n * storage: window.sessionStorage, // Use sessionStorage instead\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // localStorage collection with mutation handlers\n * const collection = createCollection(\n * localStorageCollectionOptions({\n * storageKey: 'todos',\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * console.log('Item inserted:', transaction.mutations[0].modified)\n * },\n * })\n * )\n */\n\n// Overload for when schema is provided\nexport function localStorageCollectionOptions<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n>(\n config: LocalStorageCollectionConfig<InferSchemaOutput<T>, T, TKey> & {\n schema: T\n }\n): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {\n id: string\n utils: LocalStorageCollectionUtils\n schema: T\n}\n\n// Overload for when no schema is provided\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function localStorageCollectionOptions<\n T extends object,\n TKey extends string | number = string | number,\n>(\n config: LocalStorageCollectionConfig<T, never, TKey> & {\n schema?: never // prohibit schema\n }\n): CollectionConfig<T, TKey> & {\n id: string\n utils: LocalStorageCollectionUtils\n schema?: never // no schema in the result\n}\n\nexport function localStorageCollectionOptions(\n config: LocalStorageCollectionConfig<any, any, string | number>\n): Omit<CollectionConfig<any, string | number, any>, `id`> & {\n id: string\n utils: LocalStorageCollectionUtils\n schema?: StandardSchemaV1\n} {\n // Validate required parameters\n if (!config.storageKey) {\n throw new StorageKeyRequiredError()\n }\n\n // Default to window.localStorage if no storage is provided\n const storage =\n config.storage ||\n (typeof window !== `undefined` ? window.localStorage : null)\n\n if (!storage) {\n throw new NoStorageAvailableError()\n }\n\n // Default to window for storage events if not provided\n const storageEventApi =\n config.storageEventApi || (typeof window !== `undefined` ? window : null)\n\n if (!storageEventApi) {\n throw new NoStorageEventApiError()\n }\n\n // Track the last known state to detect changes\n const lastKnownData = new Map<string | number, StoredItem<any>>()\n\n // Create the sync configuration\n const sync = createLocalStorageSync<any>(\n config.storageKey,\n storage,\n storageEventApi,\n config.getKey,\n lastKnownData\n )\n\n /**\n * Manual trigger function for local sync updates\n * Forces a check for storage changes and updates the collection if needed\n */\n const triggerLocalSync = () => {\n if (sync.manualTrigger) {\n sync.manualTrigger()\n }\n }\n\n /**\n * Save data to storage\n * @param dataMap - Map of items with version tracking to save to storage\n */\n const saveToStorage = (\n dataMap: Map<string | number, StoredItem<any>>\n ): void => {\n try {\n // Convert Map to object format for storage\n const objectData: Record<string, StoredItem<any>> = {}\n dataMap.forEach((storedItem, key) => {\n objectData[String(key)] = storedItem\n })\n const serialized = JSON.stringify(objectData)\n storage.setItem(config.storageKey, serialized)\n } catch (error) {\n console.error(\n `[LocalStorageCollection] Error saving data to storage key \"${config.storageKey}\":`,\n error\n )\n throw error\n }\n }\n\n /**\n * Removes all collection data from the configured storage\n */\n const clearStorage: ClearStorageFn = (): void => {\n storage.removeItem(config.storageKey)\n }\n\n /**\n * Get the size of the stored data in bytes (approximate)\n * @returns The approximate size in bytes of the stored collection data\n */\n const getStorageSize: GetStorageSizeFn = (): number => {\n const data = storage.getItem(config.storageKey)\n return data ? new Blob([data]).size : 0\n }\n\n /*\n * Create wrapper handlers for direct persistence operations that perform actual storage operations\n * Wraps the user's onInsert handler to also save changes to localStorage\n */\n const wrappedOnInsert = async (params: InsertMutationFnParams<any>) => {\n // Validate that all values in the transaction can be JSON serialized\n params.transaction.mutations.forEach((mutation) => {\n validateJsonSerializable(mutation.modified, `insert`)\n })\n\n // Call the user handler BEFORE persisting changes (if provided)\n let handlerResult: any = {}\n if (config.onInsert) {\n handlerResult = (await config.onInsert(params)) ?? {}\n }\n\n // Always persist to storage\n // Load current data from storage\n const currentData = loadFromStorage<any>(config.storageKey, storage)\n\n // Add new items with version keys\n params.transaction.mutations.forEach((mutation) => {\n const key = config.getKey(mutation.modified)\n const storedItem: StoredItem<any> = {\n versionKey: generateUuid(),\n data: mutation.modified,\n }\n currentData.set(key, storedItem)\n })\n\n // Save to storage\n saveToStorage(currentData)\n\n // Manually trigger local sync since storage events don't fire for current tab\n triggerLocalSync()\n\n return handlerResult\n }\n\n const wrappedOnUpdate = async (params: UpdateMutationFnParams<any>) => {\n // Validate that all values in the transaction can be JSON serialized\n params.transaction.mutations.forEach((mutation) => {\n validateJsonSerializable(mutation.modified, `update`)\n })\n\n // Call the user handler BEFORE persisting changes (if provided)\n let handlerResult: any = {}\n if (config.onUpdate) {\n handlerResult = (await config.onUpdate(params)) ?? {}\n }\n\n // Always persist to storage\n // Load current data from storage\n const currentData = loadFromStorage<any>(config.storageKey, storage)\n\n // Update items with new version keys\n params.transaction.mutations.forEach((mutation) => {\n const key = config.getKey(mutation.modified)\n const storedItem: StoredItem<any> = {\n versionKey: generateUuid(),\n data: mutation.modified,\n }\n currentData.set(key, storedItem)\n })\n\n // Save to storage\n saveToStorage(currentData)\n\n // Manually trigger local sync since storage events don't fire for current tab\n triggerLocalSync()\n\n return handlerResult\n }\n\n const wrappedOnDelete = async (params: DeleteMutationFnParams<any>) => {\n // Call the user handler BEFORE persisting changes (if provided)\n let handlerResult: any = {}\n if (config.onDelete) {\n handlerResult = (await config.onDelete(params)) ?? {}\n }\n\n // Always persist to storage\n // Load current data from storage\n const currentData = loadFromStorage<any>(config.storageKey, storage)\n\n // Remove items\n params.transaction.mutations.forEach((mutation) => {\n // For delete operations, mutation.original contains the full object\n const key = config.getKey(mutation.original)\n currentData.delete(key)\n })\n\n // Save to storage\n saveToStorage(currentData)\n\n // Manually trigger local sync since storage events don't fire for current tab\n triggerLocalSync()\n\n return handlerResult\n }\n\n // Extract standard Collection config properties\n const {\n storageKey: _storageKey,\n storage: _storage,\n storageEventApi: _storageEventApi,\n onInsert: _onInsert,\n onUpdate: _onUpdate,\n onDelete: _onDelete,\n id,\n ...restConfig\n } = config\n\n // Default id to a pattern based on storage key if not provided\n const collectionId = id ?? `local-collection:${config.storageKey}`\n\n return {\n ...restConfig,\n id: collectionId,\n sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n clearStorage,\n getStorageSize,\n },\n }\n}\n\n/**\n * Load data from storage and return as a Map\n * @param storageKey - The key used to store data in the storage API\n * @param storage - The storage API to load from (localStorage, sessionStorage, etc.)\n * @returns Map of stored items with version tracking, or empty Map if loading fails\n */\nfunction loadFromStorage<T extends object>(\n storageKey: string,\n storage: StorageApi\n): Map<string | number, StoredItem<T>> {\n try {\n const rawData = storage.getItem(storageKey)\n if (!rawData) {\n return new Map()\n }\n\n const parsed = JSON.parse(rawData)\n const dataMap = new Map<string | number, StoredItem<T>>()\n\n // Handle object format where keys map to StoredItem values\n if (\n typeof parsed === `object` &&\n parsed !== null &&\n !Array.isArray(parsed)\n ) {\n Object.entries(parsed).forEach(([key, value]) => {\n // Runtime check to ensure the value has the expected StoredItem structure\n if (\n value &&\n typeof value === `object` &&\n `versionKey` in value &&\n `data` in value\n ) {\n const storedItem = value as StoredItem<T>\n dataMap.set(key, storedItem)\n } else {\n throw new InvalidStorageDataFormatError(storageKey, key)\n }\n })\n } else {\n throw new InvalidStorageObjectFormatError(storageKey)\n }\n\n return dataMap\n } catch (error) {\n console.warn(\n `[LocalStorageCollection] Error loading data from storage key \"${storageKey}\":`,\n error\n )\n return new Map()\n }\n}\n\n/**\n * Internal function to create localStorage sync configuration\n * Creates a sync configuration that handles localStorage persistence and cross-tab synchronization\n * @param storageKey - The key used for storing data in localStorage\n * @param storage - The storage API to use (localStorage, sessionStorage, etc.)\n * @param storageEventApi - The event API for listening to storage changes\n * @param getKey - Function to extract the key from an item\n * @param lastKnownData - Map tracking the last known state for change detection\n * @returns Sync configuration with manual trigger capability\n */\nfunction createLocalStorageSync<T extends object>(\n storageKey: string,\n storage: StorageApi,\n storageEventApi: StorageEventApi,\n _getKey: (item: T) => string | number,\n lastKnownData: Map<string | number, StoredItem<T>>\n): SyncConfig<T> & { manualTrigger?: () => void } {\n let syncParams: Parameters<SyncConfig<T>[`sync`]>[0] | null = null\n\n /**\n * Compare two Maps to find differences using version keys\n * @param oldData - The previous state of stored items\n * @param newData - The current state of stored items\n * @returns Array of changes with type, key, and value information\n */\n const findChanges = (\n oldData: Map<string | number, StoredItem<T>>,\n newData: Map<string | number, StoredItem<T>>\n ): Array<{\n type: `insert` | `update` | `delete`\n key: string | number\n value?: T\n }> => {\n const changes: Array<{\n type: `insert` | `update` | `delete`\n key: string | number\n value?: T\n }> = []\n\n // Check for deletions and updates\n oldData.forEach((oldStoredItem, key) => {\n const newStoredItem = newData.get(key)\n if (!newStoredItem) {\n changes.push({ type: `delete`, key, value: oldStoredItem.data })\n } else if (oldStoredItem.versionKey !== newStoredItem.versionKey) {\n changes.push({ type: `update`, key, value: newStoredItem.data })\n }\n })\n\n // Check for insertions\n newData.forEach((newStoredItem, key) => {\n if (!oldData.has(key)) {\n changes.push({ type: `insert`, key, value: newStoredItem.data })\n }\n })\n\n return changes\n }\n\n /**\n * Process storage changes and update collection\n * Loads new data from storage, compares with last known state, and applies changes\n */\n const processStorageChanges = () => {\n if (!syncParams) return\n\n const { begin, write, commit } = syncParams\n\n // Load the new data\n const newData = loadFromStorage<T>(storageKey, storage)\n\n // Find the specific changes\n const changes = findChanges(lastKnownData, newData)\n\n if (changes.length > 0) {\n begin()\n changes.forEach(({ type, value }) => {\n if (value) {\n validateJsonSerializable(value, type)\n write({ type, value })\n }\n })\n commit()\n\n // Update lastKnownData\n lastKnownData.clear()\n newData.forEach((storedItem, key) => {\n lastKnownData.set(key, storedItem)\n })\n }\n }\n\n const syncConfig: SyncConfig<T> & { manualTrigger?: () => void } = {\n sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n const { begin, write, commit, markReady } = params\n\n // Store sync params for later use\n syncParams = params\n\n // Initial load\n const initialData = loadFromStorage<T>(storageKey, storage)\n if (initialData.size > 0) {\n begin()\n initialData.forEach((storedItem) => {\n validateJsonSerializable(storedItem.data, `load`)\n write({ type: `insert`, value: storedItem.data })\n })\n commit()\n }\n\n // Update lastKnownData\n lastKnownData.clear()\n initialData.forEach((storedItem, key) => {\n lastKnownData.set(key, storedItem)\n })\n\n // Mark collection as ready after initial load\n markReady()\n\n // Listen for storage events from other tabs\n const handleStorageEvent = (event: StorageEvent) => {\n // Only respond to changes to our specific key and from our storage\n if (event.key !== storageKey || event.storageArea !== storage) {\n return\n }\n\n processStorageChanges()\n }\n\n // Add storage event listener for cross-tab sync\n storageEventApi.addEventListener(`storage`, handleStorageEvent)\n\n // Note: Cleanup is handled automatically by the collection when it's disposed\n },\n\n /**\n * Get sync metadata - returns storage key information\n * @returns Object containing storage key and storage type metadata\n */\n getSyncMetadata: () => ({\n storageKey,\n storageType:\n storage === (typeof window !== `undefined` ? window.localStorage : null)\n ? `localStorage`\n : `custom`,\n }),\n\n // Manual trigger function for local updates\n manualTrigger: processStorageChanges,\n }\n\n return syncConfig\n}\n"],"names":["SerializationError","StorageKeyRequiredError","NoStorageAvailableError","NoStorageEventApiError","InvalidStorageDataFormatError","InvalidStorageObjectFormatError"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseCollectionConfig, CollectionConfig, InferSchemaOutput, UtilsRecord } from './types.cjs';
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
3
|
/**
|
|
4
4
|
* Storage API interface - subset of DOM Storage that we need
|
|
@@ -13,19 +13,11 @@ export type StorageEventApi = {
|
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
15
|
* Configuration interface for localStorage collection options
|
|
16
|
-
* @template
|
|
17
|
-
* @template TSchema - The schema type for validation
|
|
18
|
-
* @template
|
|
19
|
-
*
|
|
20
|
-
* @remarks
|
|
21
|
-
* Type resolution follows a priority order:
|
|
22
|
-
* 1. If you provide an explicit type via generic parameter, it will be used
|
|
23
|
-
* 2. If no explicit type is provided but a schema is, the schema's output type will be inferred
|
|
24
|
-
* 3. If neither explicit type nor schema is provided, the fallback type will be used
|
|
25
|
-
*
|
|
26
|
-
* You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.
|
|
16
|
+
* @template T - The type of items in the collection
|
|
17
|
+
* @template TSchema - The schema type for validation
|
|
18
|
+
* @template TKey - The type of the key returned by `getKey`
|
|
27
19
|
*/
|
|
28
|
-
export interface LocalStorageCollectionConfig<
|
|
20
|
+
export interface LocalStorageCollectionConfig<T extends object = object, TSchema extends StandardSchemaV1 = never, TKey extends string | number = string | number> extends BaseCollectionConfig<T, TKey, TSchema> {
|
|
29
21
|
/**
|
|
30
22
|
* The key to use for storing the collection data in localStorage/sessionStorage
|
|
31
23
|
*/
|
|
@@ -40,31 +32,6 @@ export interface LocalStorageCollectionConfig<TExplicit = unknown, TSchema exten
|
|
|
40
32
|
* Can be any object that implements addEventListener/removeEventListener for storage events
|
|
41
33
|
*/
|
|
42
34
|
storageEventApi?: StorageEventApi;
|
|
43
|
-
/**
|
|
44
|
-
* Collection identifier (defaults to "local-collection:{storageKey}" if not provided)
|
|
45
|
-
*/
|
|
46
|
-
id?: string;
|
|
47
|
-
schema?: TSchema;
|
|
48
|
-
getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`getKey`];
|
|
49
|
-
sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`sync`];
|
|
50
|
-
/**
|
|
51
|
-
* Optional asynchronous handler function called before an insert operation
|
|
52
|
-
* @param params Object containing transaction and collection information
|
|
53
|
-
* @returns Promise resolving to any value
|
|
54
|
-
*/
|
|
55
|
-
onInsert?: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
56
|
-
/**
|
|
57
|
-
* Optional asynchronous handler function called before an update operation
|
|
58
|
-
* @param params Object containing transaction and collection information
|
|
59
|
-
* @returns Promise resolving to any value
|
|
60
|
-
*/
|
|
61
|
-
onUpdate?: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
62
|
-
/**
|
|
63
|
-
* Optional asynchronous handler function called before a delete operation
|
|
64
|
-
* @param params Object containing transaction and collection information
|
|
65
|
-
* @returns Promise resolving to any value
|
|
66
|
-
*/
|
|
67
|
-
onDelete?: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
68
35
|
}
|
|
69
36
|
/**
|
|
70
37
|
* Type for the clear utility function
|
|
@@ -124,7 +91,17 @@ export interface LocalStorageCollectionUtils extends UtilsRecord {
|
|
|
124
91
|
* })
|
|
125
92
|
* )
|
|
126
93
|
*/
|
|
127
|
-
export declare function localStorageCollectionOptions<
|
|
94
|
+
export declare function localStorageCollectionOptions<T extends StandardSchemaV1, TKey extends string | number = string | number>(config: LocalStorageCollectionConfig<InferSchemaOutput<T>, T, TKey> & {
|
|
95
|
+
schema: T;
|
|
96
|
+
}): CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
97
|
+
id: string;
|
|
98
|
+
utils: LocalStorageCollectionUtils;
|
|
99
|
+
schema: T;
|
|
100
|
+
};
|
|
101
|
+
export declare function localStorageCollectionOptions<T extends object, TKey extends string | number = string | number>(config: LocalStorageCollectionConfig<T, never, TKey> & {
|
|
102
|
+
schema?: never;
|
|
103
|
+
}): CollectionConfig<T, TKey> & {
|
|
128
104
|
id: string;
|
|
129
105
|
utils: LocalStorageCollectionUtils;
|
|
106
|
+
schema?: never;
|
|
130
107
|
};
|