{"version":3,"file":"local-storage.cjs","sources":["../../src/local-storage.ts"],"sourcesContent":["import {\n  InvalidStorageDataFormatError,\n  InvalidStorageObjectFormatError,\n  SerializationError,\n  StorageKeyRequiredError,\n} from './errors'\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/**\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 mutations from a transaction that belong to this collection and persists them to localStorage.\n   * This should be called in your transaction's mutationFn to persist local-storage data.\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   *     localSettings.utils.acceptMutations(transaction)\n   *   }\n   * })\n   */\n  acceptMutations: (transaction: {\n    mutations: Array<PendingMutation<Record<string, unknown>>>\n  }) => 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 crypto.randomUUID()\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\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.\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, you must call `utils.acceptMutations()` in your transaction's `mutationFn`\n * to persist changes made during `tx.mutate()`. This is necessary because local-storage collections\n * don't participate in the standard mutation handler flow for manual transactions.\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 *     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  ): 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[encodeStorageKey(key)] = storedItem\n      })\n      const serialized = parser.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(parser, 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    // Use lastKnownData (in-memory cache) instead of reading from storage\n    // Add new items with version keys\n    params.transaction.mutations.forEach((mutation) => {\n      // Use the engine's pre-computed key for consistency\n      const storedItem: StoredItem<any> = {\n        versionKey: generateUuid(),\n        data: mutation.modified,\n      }\n      lastKnownData.set(mutation.key, storedItem)\n    })\n\n    // Save to storage\n    saveToStorage(lastKnownData)\n\n    // Confirm mutations through sync interface (moves from optimistic to synced state)\n    // without reloading from storage\n    sync.confirmOperationsSync(params.transaction.mutations)\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(parser, 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    // Use lastKnownData (in-memory cache) instead of reading from storage\n    // Update items with new version keys\n    params.transaction.mutations.forEach((mutation) => {\n      // Use the engine's pre-computed key for consistency\n      const storedItem: StoredItem<any> = {\n        versionKey: generateUuid(),\n        data: mutation.modified,\n      }\n      lastKnownData.set(mutation.key, storedItem)\n    })\n\n    // Save to storage\n    saveToStorage(lastKnownData)\n\n    // Confirm mutations through sync interface (moves from optimistic to synced state)\n    // without reloading from storage\n    sync.confirmOperationsSync(params.transaction.mutations)\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    // Use lastKnownData (in-memory cache) instead of reading from storage\n    // Remove items\n    params.transaction.mutations.forEach((mutation) => {\n      // Use the engine's pre-computed key for consistency\n      lastKnownData.delete(mutation.key)\n    })\n\n    // Save to storage\n    saveToStorage(lastKnownData)\n\n    // Confirm mutations through sync interface (moves from optimistic to synced state)\n    // without reloading from storage\n    sync.confirmOperationsSync(params.transaction.mutations)\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  /**\n   * Accepts mutations from a transaction that belong to this collection and persists them to storage\n   */\n  const acceptMutations = (transaction: {\n    mutations: Array<PendingMutation<Record<string, unknown>>>\n  }) => {\n    // Filter mutations that belong to this collection\n    // Use collection ID for filtering if collection reference isn't available yet\n    const collectionMutations = transaction.mutations.filter((m) => {\n      // Try to match by collection reference first\n      if (sync.collection && m.collection === sync.collection) {\n        return true\n      }\n      // Fall back to matching by collection ID\n      return m.collection.id === collectionId\n    })\n\n    if (collectionMutations.length === 0) {\n      return\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    // Use lastKnownData (in-memory cache) instead of reading from storage\n    // Apply each mutation\n    for (const mutation of collectionMutations) {\n      // Use the engine's pre-computed key to avoid key derivation issues\n      switch (mutation.type) {\n        case `insert`:\n        case `update`: {\n          const storedItem: StoredItem<Record<string, unknown>> = {\n            versionKey: generateUuid(),\n            data: mutation.modified,\n          }\n          lastKnownData.set(mutation.key, storedItem)\n          break\n        }\n        case `delete`: {\n          lastKnownData.delete(mutation.key)\n          break\n        }\n      }\n    }\n\n    // Save to storage\n    saveToStorage(lastKnownData)\n\n    // Confirm the mutations in the collection to move them from optimistic to synced state\n    // This writes them through the sync interface to make them \"synced\" instead of \"optimistic\"\n    sync.confirmOperationsSync(collectionMutations)\n  }\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      acceptMutations,\n    },\n  }\n}\n\n/**\n * Load data from storage and return as a Map\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, or empty Map if loading fails\n */\nfunction loadFromStorage<T extends object>(\n  storageKey: string,\n  storage: StorageApi,\n  parser: Parser,\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 = 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 (\n      typeof parsed === `object` &&\n      parsed !== null &&\n      !Array.isArray(parsed)\n    ) {\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          `data` in value\n        ) {\n          const storedItem = value as StoredItem<T>\n          const decodedKey = decodeStorageKey(encodedKey)\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  } 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  parser: Parser,\n  _getKey: (item: T) => string | number,\n  lastKnownData: Map<string | number, StoredItem<T>>,\n): SyncConfig<T> & {\n  manualTrigger?: () => void\n  collection: any\n  confirmOperationsSync: (mutations: Array<any>) => 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  ): 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, parser)\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(parser, 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> & {\n    manualTrigger?: () => void\n    collection: any\n  } = {\n    sync: (params: Parameters<SyncConfig<T>[`sync`]>[0]) => {\n      const { begin, write, commit, markReady } = params\n\n      // Store sync params and collection for later use\n      syncParams = params\n      collection = params.collection\n\n      // Initial load\n      const initialData = loadFromStorage<T>(storageKey, storage, parser)\n      if (initialData.size > 0) {\n        begin()\n        initialData.forEach((storedItem) => {\n          validateJsonSerializable(parser, 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    // Collection instance reference\n    collection,\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 = (mutations: Array<any>) => {\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        type: mutation.type,\n        value:\n          mutation.type === `delete` ? mutation.original : mutation.modified,\n      })\n    })\n    commit()\n  }\n\n  return {\n    ...syncConfig,\n    confirmOperationsSync,\n  }\n}\n"],"names":["SerializationError","StorageKeyRequiredError","InvalidStorageDataFormatError","InvalidStorageObjectFormatError"],"mappings":"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"}