@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 49.7 kB
Source Map (JSON)
{"version":3,"file":"local-storage.cjs","sources":["../../src/local-storage.ts"],"sourcesContent":["import { safeRandomUUID } from './utils/uuid'\nimport { deepEquals } from './utils'\nimport { withCollectionConfigFactory } from './client.js'\nimport { collectionOptionsClaim } from './collection-options.js'\nimport { registerTransactionCommitWork } from './transaction-commit-work.js'\nimport {\n InvalidStorageDataFormatError,\n InvalidStorageObjectFormatError,\n LocalStorageCollectionError,\n SerializationError,\n StorageKeyRequiredError,\n} from './errors'\nimport { codedMessage, codedWarning, devBuild } from './error-message.js'\nimport type {\n BaseCollectionConfig,\n CollectionConfig,\n DeleteMutationFnParams,\n InferSchemaOutput,\n InsertMutationFnParams,\n PendingMutation,\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// Browser storage events do not reach other Collections in the writing tab.\n// A sync run owns one listener for writes through the same Storage object/key.\nconst sameTabListeners = new WeakMap<StorageApi, Map<string, Set<() => void>>>()\n\nfunction subscribeToSameTabWrites(\n storage: StorageApi,\n key: string,\n refresh: () => void,\n): () => void {\n let byKey = sameTabListeners.get(storage)\n if (!byKey) {\n byKey = new Map()\n sameTabListeners.set(storage, byKey)\n }\n let listeners = byKey.get(key)\n if (!listeners) {\n listeners = new Set()\n byKey.set(key, listeners)\n }\n listeners.add(refresh)\n return () => {\n listeners.delete(refresh)\n if (listeners.size === 0) byKey.delete(key)\n if (byKey.size === 0) sameTabListeners.delete(storage)\n }\n}\n\nfunction publishSameTabWrite(\n storage: StorageApi,\n key: string,\n writer?: { listener: () => void; refresh: () => void },\n): void {\n for (const refresh of [...(sameTabListeners.get(storage)?.get(key) ?? [])]) {\n try {\n if (refresh === writer?.listener) writer.refresh()\n else refresh()\n } catch (error) {\n // A peer's parser or sync listener cannot revoke the writer's durable\n // write or prevent another peer from observing it.\n console.warn(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `[LocalStorageCollection] Error refreshing a same-tab peer for storage key \"${key}\":`\n : codedWarning(232, { storageKey: key }),\n error,\n )\n }\n }\n}\n\n/**\n * Internal storage format that includes version tracking\n */\ninterface StoredItem<T> {\n versionKey: string\n data: T\n}\n\nexport interface Parser {\n parse: (data: string) => unknown\n stringify: (data: unknown) => string\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 * Parser to use for serializing and deserializing data to and from storage\n * Defaults to JSON\n */\n parser?: Parser\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 * Accepts this Collection's manual mutations in write order and persists\n * them to localStorage. Call it inside the transaction's mutationFn. The\n * transaction's persistence receipt waits for this work even if the caller\n * does not await the returned Promise. Await it when later mutationFn work\n * depends on the storage write.\n *\n * @param transaction - The transaction containing mutations to accept\n * @example\n * const localSettings = createCollection(localStorageCollectionOptions({...}))\n *\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Make API call first\n * await api.save(...)\n * // Then persist local-storage mutations after success\n * await localSettings.utils.acceptMutations(transaction)\n * }\n * })\n */\n acceptMutations: (transaction: {\n mutations: Array<PendingMutation<Record<string, unknown>>>\n }) => Promise<void>\n}\n\n/**\n * Validates that a value can be JSON serialized\n * @param parser - The parser to use for serialization\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(\n parser: Parser,\n value: any,\n operation: string,\n): void {\n try {\n parser.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 safeRandomUUID()\n}\n\n/**\n * Encodes a key (string or number) into a storage-safe string format.\n * This prevents collisions between numeric and string keys by prefixing with type information.\n *\n * Examples:\n * - number 1 → \"n:1\"\n * - string \"1\" → \"s:1\"\n * - string \"n:1\" → \"s:n:1\"\n *\n * @param key - The key to encode (string or number)\n * @returns Type-prefixed string that is safe for storage\n */\nfunction encodeStorageKey(key: string | number): string {\n if (typeof key === `number`) {\n return `n:${key}`\n }\n return `s:${key}`\n}\n\n/**\n * Decodes a storage key back to its original form.\n * This is the inverse of encodeStorageKey.\n *\n * @param encodedKey - The encoded key from storage\n * @returns The original key (string or number)\n */\nfunction decodeStorageKey(encodedKey: string): string | number {\n if (encodedKey.startsWith(`n:`)) {\n return Number(encodedKey.slice(2))\n }\n if (encodedKey.startsWith(`s:`)) {\n return encodedKey.slice(2)\n }\n // Fallback for legacy data without encoding\n return encodedKey\n}\n\nfunction sameStorageKey(\n left: string | number,\n right: string | number,\n): boolean {\n return left === right || (Number.isNaN(left) && Number.isNaN(right))\n}\n\n/**\n * Creates an in-memory storage implementation that mimics the StorageApi interface\n * Used as a fallback when localStorage is not available (e.g., server-side rendering)\n * @returns An object implementing the StorageApi interface using an in-memory Map\n */\nfunction createInMemoryStorage(): StorageApi {\n const storage = new Map<string, string>()\n\n return {\n getItem(key: string): string | null {\n return storage.get(key) ?? null\n },\n setItem(key: string, value: string): void {\n storage.set(key, value)\n },\n removeItem(key: string): void {\n storage.delete(key)\n },\n }\n}\n\n/**\n * Creates a no-op storage event API for environments without window (e.g., server-side)\n * This provides the required interface but doesn't actually listen to any events\n * since cross-tab synchronization is not possible in server environments\n * @returns An object implementing the StorageEventApi interface with no-op methods\n */\nfunction createNoOpStorageEventApi(): StorageEventApi {\n return {\n addEventListener: () => {\n // No-op: cannot listen to storage events without window\n },\n removeEventListener: () => {\n // No-op: cannot remove listeners without window\n },\n }\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. Active\n * Collections sharing this Storage object and storage key also synchronize\n * writes in the same tab, without waiting for a browser event.\n * Create fresh options for each direct `createCollection()` call. One options\n * object contains state owned by one Collection and cannot be reused.\n *\n * **Fallback Behavior:**\n *\n * When localStorage is not available (e.g., in server-side rendering environments),\n * this function automatically falls back to an in-memory storage implementation.\n * This prevents errors during module initialization and allows the collection to\n * work in any environment, though data will not persist across page reloads or\n * be shared across tabs when using the in-memory fallback.\n *\n * **Using with Manual Transactions:**\n *\n * For manual transactions, call `utils.acceptMutations()` in your transaction's `mutationFn`\n * to persist changes made during `tx.mutate()`. The transaction receipt waits for this work even\n * when the call is not awaited. Await it when later mutationFn work depends on the storage write.\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, getStorageSize, and acceptMutations\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 * @example\n * // Using with manual transactions\n * const localSettings = createCollection(\n * localStorageCollectionOptions({\n * storageKey: 'user-settings',\n * getKey: (item) => item.id,\n * })\n * )\n *\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Use settings data in API call\n * const settingsMutations = transaction.mutations.filter(m => m.collection === localSettings)\n * await api.updateUserProfile({ settings: settingsMutations[0]?.modified })\n *\n * // Persist local-storage mutations after API success\n * await localSettings.utils.acceptMutations(transaction)\n * }\n * })\n *\n * tx.mutate(() => {\n * localSettings.insert({ id: 'theme', value: 'dark' })\n * apiCollection.insert({ id: 2, data: 'profile data' })\n * })\n *\n * await tx.commit()\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<\n InferSchemaOutput<T>,\n TKey,\n T,\n LocalStorageCollectionUtils\n> & {\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, never, LocalStorageCollectionUtils> & {\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<\n CollectionConfig<any, string | number, any, LocalStorageCollectionUtils>,\n `id`\n> & {\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 // Fall back to in-memory storage if localStorage is not available (e.g., server-side rendering)\n const storage =\n config.storage ||\n (typeof window !== `undefined` ? window.localStorage : null) ||\n createInMemoryStorage()\n\n // Default to window for storage events if not provided\n // Fall back to no-op storage event API if window is not available (e.g., server-side rendering)\n const storageEventApi =\n config.storageEventApi ||\n (typeof window !== `undefined` ? window : null) ||\n createNoOpStorageEventApi()\n\n // Default to JSON parser if no parser is provided\n const parser = config.parser || JSON\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 parser,\n config.getKey,\n lastKnownData,\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 mutations: Array<PendingMutation<Record<string, unknown>>>,\n ): {\n serialized: string\n persisted: Map<string | number, StoredItem<any>>\n } => {\n try {\n // Convert Map to object format for storage\n const objectData: Record<string, StoredItem<any>> = {}\n dataMap.forEach((storedItem, key) => {\n objectData[encodeStorageKey(key)] = storedItem\n })\n const serialized = parser.stringify(objectData)\n // A custom parser may normalize rows while serializing. Validate and\n // retain the exact snapshot before setItem makes the write durable.\n const persisted =\n parser === JSON\n ? dataMap\n : readFromStorage(\n config.storageKey,\n storage,\n parser,\n config.getKey,\n serialized,\n )\n for (const mutation of mutations) {\n const storedItem = persisted.get(mutation.key)\n if (mutation.type === `delete` ? storedItem : !storedItem) {\n throw new InvalidStorageDataFormatError(\n config.storageKey,\n encodeStorageKey(mutation.key),\n )\n }\n }\n storage.setItem(config.storageKey, serialized)\n return { serialized, persisted }\n } catch (error) {\n console.error(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `[LocalStorageCollection] Error saving data to storage key \"${config.storageKey}\":`\n : codedMessage(218, { storageKey: config.storageKey }),\n error,\n )\n throw error\n }\n }\n\n /**\n * Removes the stored snapshot and publishes removal of accepted synced rows.\n * Pending optimistic mutations can still settle afterward.\n */\n const clearStorage: ClearStorageFn = (): void => {\n storage.removeItem(config.storageKey)\n publishSameTabWrite(storage, 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 const persistMutations = (\n mutations: Array<PendingMutation<Record<string, unknown>>>,\n ): void => {\n // A peer may have written after our last storage event. Start from the\n // current durable snapshot so this write preserves untouched peer rows.\n const staged = readFromStorage(\n config.storageKey,\n storage,\n parser,\n config.getKey,\n )\n for (const mutation of mutations) {\n if (mutation.type === `delete`) staged.delete(mutation.key)\n else {\n if (parser === JSON) {\n const restored = JSON.parse(JSON.stringify(mutation.modified))\n if (!sameStorageKey(config.getKey(restored), mutation.key)) {\n throw new InvalidStorageDataFormatError(\n config.storageKey,\n encodeStorageKey(mutation.key),\n )\n }\n }\n staged.set(mutation.key, {\n versionKey: generateUuid(),\n data: mutation.modified,\n })\n }\n }\n const { serialized: savedRaw, persisted } = saveToStorage(staged, mutations)\n try {\n for (const mutation of mutations) {\n const storedItem = persisted.get(mutation.key)\n if (storedItem) lastKnownData.set(mutation.key, storedItem)\n else lastKnownData.delete(mutation.key)\n }\n sync.confirmOperationsSync(mutations, persisted)\n } finally {\n // Storage has already accepted this snapshot. A local confirmation\n // error must not suppress publication to other active Collections.\n const writerRefresh = sync.manualTrigger\n publishSameTabWrite(\n storage,\n config.storageKey,\n writerRefresh\n ? {\n listener: writerRefresh,\n refresh: () =>\n writerRefresh(\n storage.getItem(config.storageKey) === savedRaw\n ? persisted\n : undefined,\n ),\n }\n : undefined,\n )\n }\n }\n\n // Reserve automatic and manual writes in acceptance order. A rejected\n // handler releases only its storage slot after its predecessor, while its\n // own transaction receipt can reject immediately.\n let writeTail: Promise<void> | undefined\n const reserveWrite = () => {\n const previous = writeTail\n let release!: () => void\n const slot = new Promise<void>((resolve) => {\n release = resolve\n })\n writeTail = slot\n const retire = () => {\n release()\n if (writeTail === slot) writeTail = undefined\n }\n return { previous, retire }\n }\n\n const persistAutomatic = (\n mutations: Array<PendingMutation<Record<string, unknown>>>,\n handler?: () => unknown | Promise<unknown>,\n ): Promise<unknown> => {\n const { previous, retire } = reserveWrite()\n\n // Native Storage writes synchronously. With no handler or earlier write,\n // preserve that direct-mutation return boundary.\n if (!handler && previous === undefined) {\n try {\n persistMutations(mutations)\n return Promise.resolve({})\n } catch (error) {\n return Promise.reject(error)\n } finally {\n retire()\n }\n }\n\n let result: unknown\n try {\n result = handler?.()\n } catch (error) {\n if (previous) void previous.then(retire)\n else retire()\n return Promise.reject(error)\n }\n return Promise.resolve(result).then(\n async (value) => {\n await previous\n try {\n persistMutations(mutations)\n return value ?? {}\n } finally {\n retire()\n }\n },\n (error: unknown) => {\n // Rejection belongs to this transaction immediately. Only its empty\n // storage slot must wait for the earlier accepted write to retire.\n if (previous) void previous.then(retire)\n else retire()\n throw error\n },\n )\n }\n\n const persistManual = (\n mutations: Array<PendingMutation<Record<string, unknown>>>,\n ): Promise<void> => {\n const { previous, retire } = reserveWrite()\n if (previous === undefined) {\n try {\n persistMutations(mutations)\n return Promise.resolve()\n } finally {\n retire()\n }\n }\n return previous.then(() => {\n try {\n persistMutations(mutations)\n } finally {\n retire()\n }\n })\n }\n\n const wrappedOnInsert = (params: InsertMutationFnParams<any>) => {\n params.transaction.mutations.forEach((mutation) => {\n validateJsonSerializable(parser, mutation.modified, `insert`)\n })\n return persistAutomatic(\n params.transaction.mutations,\n config.onInsert ? () => config.onInsert!(params) : undefined,\n )\n }\n\n const wrappedOnUpdate = (params: UpdateMutationFnParams<any>) => {\n params.transaction.mutations.forEach((mutation) => {\n validateJsonSerializable(parser, mutation.modified, `update`)\n })\n return persistAutomatic(\n params.transaction.mutations,\n config.onUpdate ? () => config.onUpdate!(params) : undefined,\n )\n }\n\n const wrappedOnDelete = (params: DeleteMutationFnParams<any>) =>\n persistAutomatic(\n params.transaction.mutations,\n config.onDelete ? () => config.onDelete!(params) : undefined,\n )\n\n // Extract standard Collection config properties\n // Remove localStorage-specific properties so they don't leak into the CollectionConfig\n const {\n storageKey: _storageKey,\n storage: _storage,\n storageEventApi: _storageEventApi,\n parser: _parser,\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 /**\n * Accepts this Collection's manual mutations in write order and resolves\n * after their storage write. A mutationFn receipt tracks this work even\n * when the caller does not await the returned Promise.\n */\n const persistAcceptedMutations = async (\n transaction: {\n mutations: Array<PendingMutation<Record<string, unknown>>>\n },\n owner: PendingMutation<Record<string, unknown>>[`collection`] | undefined,\n ): Promise<void> => {\n const collectionMutations = transaction.mutations.filter(\n (mutation) => mutation.collection === owner,\n )\n\n if (collectionMutations.length === 0) {\n return Promise.resolve()\n }\n\n // Validate all mutations can be serialized before modifying storage\n for (const mutation of collectionMutations) {\n switch (mutation.type) {\n case `insert`:\n case `update`:\n validateJsonSerializable(parser, mutation.modified, mutation.type)\n break\n case `delete`:\n validateJsonSerializable(parser, mutation.original, mutation.type)\n break\n }\n }\n\n await persistManual(collectionMutations)\n }\n\n const materializedAcceptors = new WeakSet<\n LocalStorageCollectionUtils[`acceptMutations`]\n >()\n const acceptMutations = (transaction: {\n mutations: Array<PendingMutation<Record<string, unknown>>>\n }): Promise<void> => {\n // A DbClient materializes module-level options into a fresh Collection.\n // Route acceptance to that Collection's utility so its automatic and\n // manual writes reserve slots in the same queue.\n const candidates = new Set(\n transaction.mutations\n .filter((mutation) => mutation.collection.id === collectionId)\n .map((mutation) => mutation.collection),\n )\n const owned = [...candidates].filter((candidate) => {\n const candidateAccept = (candidate.utils as LocalStorageCollectionUtils)\n .acceptMutations\n return (\n candidateAccept === acceptMutations ||\n materializedAcceptors.has(candidateAccept)\n )\n })\n if (owned.length > 1 || (candidates.size > 0 && owned.length === 0)) {\n const work = Promise.reject(\n new LocalStorageCollectionError(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `LocalStorage manual acceptance belongs to a different Collection.`\n : codedMessage(231),\n ),\n )\n registerTransactionCommitWork(transaction, work)\n return work\n }\n const owner = owned[0]\n const ownerAccept = owner\n ? (owner.utils as LocalStorageCollectionUtils).acceptMutations\n : undefined\n if (ownerAccept && ownerAccept !== acceptMutations)\n return ownerAccept(transaction)\n const work = persistAcceptedMutations(transaction, owner)\n registerTransactionCommitWork(transaction, work)\n return work\n }\n\n const options = {\n ...restConfig,\n id: collectionId,\n sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n clearStorage,\n getStorageSize,\n acceptMutations,\n },\n }\n\n let claimed = false\n Object.defineProperty(options, collectionOptionsClaim, {\n enumerable: true,\n value: () => {\n if (claimed)\n throw new LocalStorageCollectionError(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `LocalStorage options can create only one Collection. Create fresh options for each Collection.`\n : codedMessage(230),\n )\n claimed = true\n },\n })\n\n return withCollectionConfigFactory(options, () => {\n const materialized = localStorageCollectionOptions({\n ...config,\n id: collectionId,\n }) as unknown as typeof options\n materializedAcceptors.add(materialized.utils.acceptMutations)\n return materialized\n })\n}\n\n/**\n * Read data from storage without treating a failed read as an empty snapshot.\n * @param parser - The parser to use for deserializing the data\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\n */\nfunction readFromStorage<T extends object>(\n storageKey: string,\n storage: StorageApi,\n parser: Parser,\n getKey: (item: T) => string | number,\n knownRaw?: string,\n): Map<string | number, StoredItem<T>> {\n const rawData = knownRaw ?? storage.getItem(storageKey)\n if (rawData === null) {\n return new Map()\n }\n\n const parsed = parser.parse(rawData)\n const dataMap = new Map<string | number, StoredItem<T>>()\n\n // Handle object format where keys map to StoredItem values\n if (typeof parsed === `object` && parsed !== null && !Array.isArray(parsed)) {\n Object.entries(parsed).forEach(([encodedKey, 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 typeof value.versionKey === `string` &&\n `data` in value &&\n value.data !== null &&\n typeof value.data === `object`\n ) {\n const storedItem = value as StoredItem<T>\n const decodedKey = decodeStorageKey(encodedKey)\n if (\n !sameStorageKey(getKey(storedItem.data), decodedKey) ||\n dataMap.has(decodedKey)\n ) {\n throw new InvalidStorageDataFormatError(storageKey, encodedKey)\n }\n dataMap.set(decodedKey, storedItem)\n } else {\n throw new InvalidStorageDataFormatError(storageKey, encodedKey)\n }\n })\n } else {\n throw new InvalidStorageObjectFormatError(storageKey)\n }\n\n return dataMap\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 parser: Parser,\n getKey: (item: T) => string | number,\n lastKnownData: Map<string | number, StoredItem<T>>,\n): SyncConfig<T> & {\n manualTrigger?: (knownSnapshot?: Map<string | number, StoredItem<T>>) => void\n collection: any\n confirmOperationsSync: (\n mutations: Array<any>,\n persisted: Map<string | number, StoredItem<T>>,\n ) => void\n} {\n let syncParams: Parameters<SyncConfig<T>[`sync`]>[0] | null = null\n let collection: any = 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 compareContent: boolean,\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 (\n oldStoredItem.versionKey !== newStoredItem.versionKey ||\n (compareContent &&\n !deepEquals(oldStoredItem.data, newStoredItem.data) &&\n // A default-JSON writer may retain a native Date while the stored\n // row contains its ISO string. A peer's unrelated write must not\n // publish that representation change as an update.\n (parser !== JSON ||\n JSON.stringify(oldStoredItem.data) !==\n JSON.stringify(newStoredItem.data)))\n ) {\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 knownSnapshot?: Map<string | number, StoredItem<T>>,\n ) => {\n if (!syncParams) return\n\n const { begin, write, commit } = syncParams\n\n // Load the new data\n let newData: Map<string | number, StoredItem<T>>\n try {\n newData =\n knownSnapshot ?? readFromStorage<T>(storageKey, storage, parser, getKey)\n } catch (error) {\n console.warn(\n devBuild() && process.env.NODE_ENV !== `production`\n ? `[LocalStorageCollection] Error loading data from storage key \"${storageKey}\":`\n : codedWarning(219, { storageKey }),\n error,\n )\n return\n }\n\n // Find the specific changes\n // The default-JSON writer deliberately retains native authored values.\n // Peers and storage events must compare content even under the same token.\n const changes = findChanges(\n lastKnownData,\n newData,\n knownSnapshot === undefined || parser !== JSON,\n )\n\n if (changes.length > 0) {\n // Reject an invalid snapshot before opening a sync transaction. This\n // leaves the previous mirror available for a later valid refresh.\n changes.forEach(({ type, value }) => {\n if (value) validateJsonSerializable(parser, value, type)\n })\n begin()\n changes.forEach(({ type, value }) => {\n if (value) {\n write({ type, value })\n }\n })\n // A commit can synchronously publish to a subscriber that writes again.\n // The nested refresh must compare against this snapshot, not its parent.\n lastKnownData.clear()\n newData.forEach((storedItem, key) => {\n lastKnownData.set(key, storedItem)\n })\n commit()\n }\n }\n\n const syncConfig: SyncConfig<T> & {\n manualTrigger?: (\n knownSnapshot?: Map<string | number, StoredItem<T>>,\n ) => void\n } = {\n rowUpdateMode: `full`,\n sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n const { begin, write, commit, markReady } = params\n\n // A failed restore cannot establish an empty authoritative snapshot.\n // Read before retaining this sync run's callbacks so startup failure\n // leaves no stale adapter state.\n const initialData = readFromStorage<T>(\n storageKey,\n storage,\n parser,\n getKey,\n )\n initialData.forEach((storedItem) => {\n validateJsonSerializable(parser, storedItem.data, `load`)\n })\n syncParams = params\n collection = params.collection\n // Establish the mirror and same-tab listener before restore publishes\n // callbacks, which can synchronously write through an already-ready peer.\n lastKnownData.clear()\n initialData.forEach((storedItem, key) => {\n lastKnownData.set(key, storedItem)\n })\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 const unsubscribeSameTab = subscribeToSameTabWrites(\n storage,\n storageKey,\n processStorageChanges,\n )\n try {\n if (initialData.size > 0) {\n begin()\n initialData.forEach((storedItem) => {\n write({ type: `insert`, value: storedItem.data })\n })\n commit()\n }\n // Mark collection as ready after initial load, then listen for\n // browser events from other tabs.\n markReady()\n storageEventApi.addEventListener(`storage`, handleStorageEvent)\n } catch (error) {\n unsubscribeSameTab()\n if (syncParams === params) {\n syncParams = null\n }\n throw error\n }\n return {\n cleanup: () => {\n storageEventApi.removeEventListener(`storage`, handleStorageEvent)\n unsubscribeSameTab()\n if (syncParams === params) {\n syncParams = null\n }\n },\n }\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 /**\n * Confirms mutations by writing them through the sync interface\n * This moves mutations from optimistic to synced state\n * @param mutations - Array of mutation objects to confirm\n */\n const confirmOperationsSync = (\n mutations: Array<any>,\n persisted: Map<string | number, StoredItem<T>>,\n ) => {\n if (!syncParams) {\n // Sync not initialized yet, mutations will be handled on next sync\n return\n }\n\n const { begin, write, commit } = syncParams\n\n // Write the mutations through sync to confirm them\n begin()\n mutations.forEach((mutation: any) => {\n write({\n // A peer may have inserted the same key while this local mutation\n // was pending. Full-row update confirms the accepted stored value in\n // either case without a duplicate insert.\n type: mutation.type === `delete` ? `delete` : `update`,\n value:\n mutation.type === `delete`\n ? mutation.original\n : persisted.get(mutation.key)!.data,\n })\n })\n commit()\n }\n\n return {\n ...syncConfig,\n get collection() {\n return collection\n },\n confirmOperationsSync,\n }\n}\n"],"names":["devBuild","codedWarning","SerializationError","safeRandomUUID","StorageKeyRequiredError","InvalidStorageDataFormatError","codedMessage","work","LocalStorageCollectionError","registerTransactionCommitWork","collectionOptionsClaim","withCollectionConfigFactory","InvalidStorageObjectFormatError","deepEquals"],"mappings":"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"}