@@ -44,6 +44,11 @@ interface StoredItem<T> {
4444 data : T
4545}
4646
47+ export interface Parser {
48+ parse : ( data : string ) => unknown
49+ stringify : ( data : unknown ) => string
50+ }
51+
4752/**
4853 * Configuration interface for localStorage collection options
4954 * @template T - The type of items in the collection
@@ -71,6 +76,12 @@ export interface LocalStorageCollectionConfig<
7176 * Can be any object that implements addEventListener/removeEventListener for storage events
7277 */
7378 storageEventApi ?: StorageEventApi
79+
80+ /**
81+ * Parser to use for serializing and deserializing data to and from storage
82+ * Defaults to JSON
83+ */
84+ parser ?: Parser
7485}
7586
7687/**
@@ -113,13 +124,18 @@ export interface LocalStorageCollectionUtils extends UtilsRecord {
113124
114125/**
115126 * Validates that a value can be JSON serialized
127+ * @param parser - The parser to use for serialization
116128 * @param value - The value to validate for JSON serialization
117129 * @param operation - The operation type being performed (for error messages)
118130 * @throws Error if the value cannot be JSON serialized
119131 */
120- function validateJsonSerializable ( value : any , operation : string ) : void {
132+ function validateJsonSerializable (
133+ parser : Parser ,
134+ value : any ,
135+ operation : string
136+ ) : void {
121137 try {
122- JSON . stringify ( value )
138+ parser . stringify ( value )
123139 } catch ( error ) {
124140 throw new SerializationError (
125141 operation ,
@@ -314,6 +330,9 @@ export function localStorageCollectionOptions(
314330 ( typeof window !== `undefined` ? window : null ) ||
315331 createNoOpStorageEventApi ( )
316332
333+ // Default to JSON parser if no parser is provided
334+ const parser = config . parser || JSON
335+
317336 // Track the last known state to detect changes
318337 const lastKnownData = new Map < string | number , StoredItem < any > > ( )
319338
@@ -322,6 +341,7 @@ export function localStorageCollectionOptions(
322341 config . storageKey ,
323342 storage ,
324343 storageEventApi ,
344+ parser ,
325345 config . getKey ,
326346 lastKnownData
327347 )
@@ -349,7 +369,7 @@ export function localStorageCollectionOptions(
349369 dataMap . forEach ( ( storedItem , key ) => {
350370 objectData [ String ( key ) ] = storedItem
351371 } )
352- const serialized = JSON . stringify ( objectData )
372+ const serialized = parser . stringify ( objectData )
353373 storage . setItem ( config . storageKey , serialized )
354374 } catch ( error ) {
355375 console . error (
@@ -383,7 +403,7 @@ export function localStorageCollectionOptions(
383403 const wrappedOnInsert = async ( params : InsertMutationFnParams < any > ) => {
384404 // Validate that all values in the transaction can be JSON serialized
385405 params . transaction . mutations . forEach ( ( mutation ) => {
386- validateJsonSerializable ( mutation . modified , `insert` )
406+ validateJsonSerializable ( parser , mutation . modified , `insert` )
387407 } )
388408
389409 // Call the user handler BEFORE persisting changes (if provided)
@@ -394,7 +414,7 @@ export function localStorageCollectionOptions(
394414
395415 // Always persist to storage
396416 // Load current data from storage
397- const currentData = loadFromStorage < any > ( config . storageKey , storage )
417+ const currentData = loadFromStorage < any > ( config . storageKey , storage , parser )
398418
399419 // Add new items with version keys
400420 params . transaction . mutations . forEach ( ( mutation ) => {
@@ -418,7 +438,7 @@ export function localStorageCollectionOptions(
418438 const wrappedOnUpdate = async ( params : UpdateMutationFnParams < any > ) => {
419439 // Validate that all values in the transaction can be JSON serialized
420440 params . transaction . mutations . forEach ( ( mutation ) => {
421- validateJsonSerializable ( mutation . modified , `update` )
441+ validateJsonSerializable ( parser , mutation . modified , `update` )
422442 } )
423443
424444 // Call the user handler BEFORE persisting changes (if provided)
@@ -429,7 +449,7 @@ export function localStorageCollectionOptions(
429449
430450 // Always persist to storage
431451 // Load current data from storage
432- const currentData = loadFromStorage < any > ( config . storageKey , storage )
452+ const currentData = loadFromStorage < any > ( config . storageKey , storage , parser )
433453
434454 // Update items with new version keys
435455 params . transaction . mutations . forEach ( ( mutation ) => {
@@ -459,7 +479,7 @@ export function localStorageCollectionOptions(
459479
460480 // Always persist to storage
461481 // Load current data from storage
462- const currentData = loadFromStorage < any > ( config . storageKey , storage )
482+ const currentData = loadFromStorage < any > ( config . storageKey , storage , parser )
463483
464484 // Remove items
465485 params . transaction . mutations . forEach ( ( mutation ) => {
@@ -518,18 +538,19 @@ export function localStorageCollectionOptions(
518538 switch ( mutation . type ) {
519539 case `insert` :
520540 case `update` :
521- validateJsonSerializable ( mutation . modified , mutation . type )
541+ validateJsonSerializable ( parser , mutation . modified , mutation . type )
522542 break
523543 case `delete` :
524- validateJsonSerializable ( mutation . original , mutation . type )
544+ validateJsonSerializable ( parser , mutation . original , mutation . type )
525545 break
526546 }
527547 }
528548
529549 // Load current data from storage
530550 const currentData = loadFromStorage < Record < string , unknown > > (
531551 config . storageKey ,
532- storage
552+ storage ,
553+ parser
533554 )
534555
535556 // Apply each mutation
@@ -579,21 +600,23 @@ export function localStorageCollectionOptions(
579600
580601/**
581602 * Load data from storage and return as a Map
603+ * @param parser - The parser to use for deserializing the data
582604 * @param storageKey - The key used to store data in the storage API
583605 * @param storage - The storage API to load from (localStorage, sessionStorage, etc.)
584606 * @returns Map of stored items with version tracking, or empty Map if loading fails
585607 */
586608function loadFromStorage < T extends object > (
587609 storageKey : string ,
588- storage : StorageApi
610+ storage : StorageApi ,
611+ parser : Parser
589612) : Map < string | number , StoredItem < T > > {
590613 try {
591614 const rawData = storage . getItem ( storageKey )
592615 if ( ! rawData ) {
593616 return new Map ( )
594617 }
595618
596- const parsed = JSON . parse ( rawData )
619+ const parsed = parser . parse ( rawData )
597620 const dataMap = new Map < string | number , StoredItem < T > > ( )
598621
599622 // Handle object format where keys map to StoredItem values
@@ -644,6 +667,7 @@ function createLocalStorageSync<T extends object>(
644667 storageKey : string ,
645668 storage : StorageApi ,
646669 storageEventApi : StorageEventApi ,
670+ parser : Parser ,
647671 _getKey : ( item : T ) => string | number ,
648672 lastKnownData : Map < string | number , StoredItem < T > >
649673) : SyncConfig < T > & {
@@ -704,7 +728,7 @@ function createLocalStorageSync<T extends object>(
704728 const { begin, write, commit } = syncParams
705729
706730 // Load the new data
707- const newData = loadFromStorage < T > ( storageKey , storage )
731+ const newData = loadFromStorage < T > ( storageKey , storage , parser )
708732
709733 // Find the specific changes
710734 const changes = findChanges ( lastKnownData , newData )
@@ -713,7 +737,7 @@ function createLocalStorageSync<T extends object>(
713737 begin ( )
714738 changes . forEach ( ( { type, value } ) => {
715739 if ( value ) {
716- validateJsonSerializable ( value , type )
740+ validateJsonSerializable ( parser , value , type )
717741 write ( { type, value } )
718742 }
719743 } )
@@ -739,11 +763,11 @@ function createLocalStorageSync<T extends object>(
739763 collection = params . collection
740764
741765 // Initial load
742- const initialData = loadFromStorage < T > ( storageKey , storage )
766+ const initialData = loadFromStorage < T > ( storageKey , storage , parser )
743767 if ( initialData . size > 0 ) {
744768 begin ( )
745769 initialData . forEach ( ( storedItem ) => {
746- validateJsonSerializable ( storedItem . data , `load` )
770+ validateJsonSerializable ( parser , storedItem . data , `load` )
747771 write ( { type : `insert` , value : storedItem . data } )
748772 } )
749773 commit ( )
0 commit comments