9
9
import type {
10
10
CollectionConfig ,
11
11
DeleteMutationFnParams ,
12
+ InsertMutationFn ,
12
13
InsertMutationFnParams ,
14
+ ResolveInsertInput ,
13
15
ResolveType ,
14
16
SyncConfig ,
15
17
UpdateMutationFnParams ,
@@ -62,6 +64,7 @@ export interface LocalStorageCollectionConfig<
62
64
TExplicit = unknown ,
63
65
TSchema extends StandardSchemaV1 = never ,
64
66
TFallback extends object = Record < string , unknown > ,
67
+ TKey extends string | number = string | number ,
65
68
> {
66
69
/**
67
70
* The key to use for storing the collection data in localStorage/sessionStorage
@@ -85,16 +88,29 @@ export interface LocalStorageCollectionConfig<
85
88
*/
86
89
id ?: string
87
90
schema ?: TSchema
88
- getKey : CollectionConfig < ResolveType < TExplicit , TSchema , TFallback > > [ `getKey`]
89
- sync ?: CollectionConfig < ResolveType < TExplicit , TSchema , TFallback > > [ `sync`]
91
+ getKey : CollectionConfig <
92
+ ResolveType < TExplicit , TSchema , TFallback > ,
93
+ TKey ,
94
+ TSchema ,
95
+ ResolveInsertInput < TExplicit , TSchema , TFallback >
96
+ > [ `getKey`]
97
+ sync ?: CollectionConfig <
98
+ ResolveType < TExplicit , TSchema , TFallback > ,
99
+ TKey ,
100
+ TSchema ,
101
+ ResolveInsertInput < TExplicit , TSchema , TFallback >
102
+ > [ `sync`]
90
103
91
104
/**
92
105
* Optional asynchronous handler function called before an insert operation
93
106
* @param params Object containing transaction and collection information
94
107
* @returns Promise resolving to any value
95
108
*/
96
109
onInsert ?: (
97
- params : InsertMutationFnParams < ResolveType < TExplicit , TSchema , TFallback > >
110
+ params : InsertMutationFnParams <
111
+ ResolveInsertInput < TExplicit , TSchema , TFallback > ,
112
+ TKey
113
+ >
98
114
) => Promise < any >
99
115
100
116
/**
@@ -103,7 +119,10 @@ export interface LocalStorageCollectionConfig<
103
119
* @returns Promise resolving to any value
104
120
*/
105
121
onUpdate ?: (
106
- params : UpdateMutationFnParams < ResolveType < TExplicit , TSchema , TFallback > >
122
+ params : UpdateMutationFnParams <
123
+ ResolveType < TExplicit , TSchema , TFallback > ,
124
+ TKey
125
+ >
107
126
) => Promise < any >
108
127
109
128
/**
@@ -112,7 +131,10 @@ export interface LocalStorageCollectionConfig<
112
131
* @returns Promise resolving to any value
113
132
*/
114
133
onDelete ?: (
115
- params : DeleteMutationFnParams < ResolveType < TExplicit , TSchema , TFallback > >
134
+ params : DeleteMutationFnParams <
135
+ ResolveType < TExplicit , TSchema , TFallback > ,
136
+ TKey
137
+ >
116
138
) => Promise < any >
117
139
}
118
140
@@ -206,13 +228,23 @@ export function localStorageCollectionOptions<
206
228
TExplicit = unknown ,
207
229
TSchema extends StandardSchemaV1 = never ,
208
230
TFallback extends object = Record < string , unknown > ,
231
+ TKey extends string | number = string | number ,
209
232
> (
210
- config : LocalStorageCollectionConfig < TExplicit , TSchema , TFallback >
211
- ) : Omit < CollectionConfig < ResolveType < TExplicit , TSchema , TFallback > > , `id`> & {
233
+ config : LocalStorageCollectionConfig < TExplicit , TSchema , TFallback , TKey >
234
+ ) : Omit <
235
+ CollectionConfig <
236
+ ResolveType < TExplicit , TSchema , TFallback > ,
237
+ TKey ,
238
+ TSchema ,
239
+ ResolveInsertInput < TExplicit , TSchema , TFallback >
240
+ > ,
241
+ `id`
242
+ > & {
212
243
id : string
213
244
utils : LocalStorageCollectionUtils
214
245
} {
215
- type ResolvedType = ResolveType < TExplicit , TSchema , TFallback >
246
+ type TItem = ResolveType < TExplicit , TSchema , TFallback >
247
+ type TInsertInput = ResolveInsertInput < TExplicit , TSchema , TFallback >
216
248
217
249
// Validate required parameters
218
250
if ( ! config . storageKey ) {
@@ -237,14 +269,14 @@ export function localStorageCollectionOptions<
237
269
}
238
270
239
271
// Track the last known state to detect changes
240
- const lastKnownData = new Map < string | number , StoredItem < ResolvedType > > ( )
272
+ const lastKnownData = new Map < string | number , StoredItem < TItem > > ( )
241
273
242
274
// Create the sync configuration
243
- const sync = createLocalStorageSync < ResolvedType > (
275
+ const sync = createLocalStorageSync < TItem , TKey > (
244
276
config . storageKey ,
245
277
storage ,
246
278
storageEventApi ,
247
- config . getKey ,
279
+ config . getKey as ( item : TItem ) => TKey ,
248
280
lastKnownData
249
281
)
250
282
@@ -263,11 +295,11 @@ export function localStorageCollectionOptions<
263
295
* @param dataMap - Map of items with version tracking to save to storage
264
296
*/
265
297
const saveToStorage = (
266
- dataMap : Map < string | number , StoredItem < ResolvedType > >
298
+ dataMap : Map < string | number , StoredItem < TItem > >
267
299
) : void => {
268
300
try {
269
301
// Convert Map to object format for storage
270
- const objectData : Record < string , StoredItem < ResolvedType > > = { }
302
+ const objectData : Record < string , StoredItem < TItem > > = { }
271
303
dataMap . forEach ( ( storedItem , key ) => {
272
304
objectData [ String ( key ) ] = storedItem
273
305
} )
@@ -303,7 +335,7 @@ export function localStorageCollectionOptions<
303
335
* Wraps the user's onInsert handler to also save changes to localStorage
304
336
*/
305
337
const wrappedOnInsert = async (
306
- params : InsertMutationFnParams < ResolvedType >
338
+ params : InsertMutationFnParams < TItem , TKey >
307
339
) => {
308
340
// Validate that all values in the transaction can be JSON serialized
309
341
params . transaction . mutations . forEach ( ( mutation ) => {
@@ -313,20 +345,20 @@ export function localStorageCollectionOptions<
313
345
// Call the user handler BEFORE persisting changes (if provided)
314
346
let handlerResult : any = { }
315
347
if ( config . onInsert ) {
316
- handlerResult = ( await config . onInsert ( params ) ) ?? { }
348
+ handlerResult =
349
+ ( await ( config . onInsert as InsertMutationFn < TInsertInput , TKey > ) (
350
+ params as unknown as InsertMutationFnParams < TInsertInput , TKey >
351
+ ) ) ?? { }
317
352
}
318
353
319
354
// Always persist to storage
320
355
// Load current data from storage
321
- const currentData = loadFromStorage < ResolvedType > (
322
- config . storageKey ,
323
- storage
324
- )
356
+ const currentData = loadFromStorage < TItem > ( config . storageKey , storage )
325
357
326
358
// Add new items with version keys
327
359
params . transaction . mutations . forEach ( ( mutation ) => {
328
- const key = config . getKey ( mutation . modified )
329
- const storedItem : StoredItem < ResolvedType > = {
360
+ const key = ( config . getKey as ( item : TItem ) => TKey ) ( mutation . modified )
361
+ const storedItem : StoredItem < TItem > = {
330
362
versionKey : generateUuid ( ) ,
331
363
data : mutation . modified ,
332
364
}
@@ -343,7 +375,7 @@ export function localStorageCollectionOptions<
343
375
}
344
376
345
377
const wrappedOnUpdate = async (
346
- params : UpdateMutationFnParams < ResolvedType >
378
+ params : UpdateMutationFnParams < TItem , TKey >
347
379
) => {
348
380
// Validate that all values in the transaction can be JSON serialized
349
381
params . transaction . mutations . forEach ( ( mutation ) => {
@@ -358,15 +390,12 @@ export function localStorageCollectionOptions<
358
390
359
391
// Always persist to storage
360
392
// Load current data from storage
361
- const currentData = loadFromStorage < ResolvedType > (
362
- config . storageKey ,
363
- storage
364
- )
393
+ const currentData = loadFromStorage < TItem > ( config . storageKey , storage )
365
394
366
395
// Update items with new version keys
367
396
params . transaction . mutations . forEach ( ( mutation ) => {
368
- const key = config . getKey ( mutation . modified )
369
- const storedItem : StoredItem < ResolvedType > = {
397
+ const key = ( config . getKey as ( item : TItem ) => TKey ) ( mutation . modified )
398
+ const storedItem : StoredItem < TItem > = {
370
399
versionKey : generateUuid ( ) ,
371
400
data : mutation . modified ,
372
401
}
@@ -383,7 +412,7 @@ export function localStorageCollectionOptions<
383
412
}
384
413
385
414
const wrappedOnDelete = async (
386
- params : DeleteMutationFnParams < ResolvedType >
415
+ params : DeleteMutationFnParams < TItem , TKey >
387
416
) => {
388
417
// Call the user handler BEFORE persisting changes (if provided)
389
418
let handlerResult : any = { }
@@ -393,15 +422,14 @@ export function localStorageCollectionOptions<
393
422
394
423
// Always persist to storage
395
424
// Load current data from storage
396
- const currentData = loadFromStorage < ResolvedType > (
397
- config . storageKey ,
398
- storage
399
- )
425
+ const currentData = loadFromStorage < TItem > ( config . storageKey , storage )
400
426
401
427
// Remove items
402
428
params . transaction . mutations . forEach ( ( mutation ) => {
403
429
// For delete operations, mutation.original contains the full object
404
- const key = config . getKey ( mutation . original as ResolvedType )
430
+ const key = ( config . getKey as ( item : TItem ) => TKey ) (
431
+ mutation . original as TItem
432
+ )
405
433
currentData . delete ( key )
406
434
} )
407
435
@@ -433,7 +461,10 @@ export function localStorageCollectionOptions<
433
461
...restConfig ,
434
462
id : collectionId ,
435
463
sync,
436
- onInsert : wrappedOnInsert ,
464
+ onInsert : wrappedOnInsert as unknown as InsertMutationFn <
465
+ TInsertInput ,
466
+ TKey
467
+ > ,
437
468
onUpdate : wrappedOnUpdate ,
438
469
onDelete : wrappedOnDelete ,
439
470
utils : {
@@ -506,14 +537,17 @@ function loadFromStorage<T extends object>(
506
537
* @param lastKnownData - Map tracking the last known state for change detection
507
538
* @returns Sync configuration with manual trigger capability
508
539
*/
509
- function createLocalStorageSync < T extends object > (
540
+ function createLocalStorageSync <
541
+ T extends object ,
542
+ TKey extends string | number = string | number ,
543
+ > (
510
544
storageKey : string ,
511
545
storage : StorageApi ,
512
546
storageEventApi : StorageEventApi ,
513
- _getKey : ( item : T ) => string | number ,
547
+ _getKey : ( item : T ) => TKey ,
514
548
lastKnownData : Map < string | number , StoredItem < T > >
515
- ) : SyncConfig < T > & { manualTrigger ?: ( ) => void } {
516
- let syncParams : Parameters < SyncConfig < T > [ `sync`] > [ 0 ] | null = null
549
+ ) : SyncConfig < T , TKey > & { manualTrigger ?: ( ) => void } {
550
+ let syncParams : Parameters < SyncConfig < T , TKey > [ `sync`] > [ 0 ] | null = null
517
551
518
552
/**
519
553
* Compare two Maps to find differences using version keys
@@ -588,8 +622,8 @@ function createLocalStorageSync<T extends object>(
588
622
}
589
623
}
590
624
591
- const syncConfig : SyncConfig < T > & { manualTrigger ?: ( ) => void } = {
592
- sync : ( params : Parameters < SyncConfig < T > [ `sync`] > [ 0 ] ) => {
625
+ const syncConfig : SyncConfig < T , TKey > & { manualTrigger ?: ( ) => void } = {
626
+ sync : ( params : Parameters < SyncConfig < T , TKey > [ `sync`] > [ 0 ] ) => {
593
627
const { begin, write, commit, markReady } = params
594
628
595
629
// Store sync params for later use
0 commit comments