{"version":3,"file":"useCollection.cjs","sources":["../../src/useCollection.ts"],"sourcesContent":["import { useSyncExternalStoreWithSelector } from \"use-sync-external-store/shim/with-selector.js\"\nimport {\n  Collection,\n  collectionsStore,\n  preloadCollection,\n} from \"@tanstack/optimistic\"\nimport type {\n  CollectionConfig,\n  SortedMap,\n  Transaction,\n} from \"@tanstack/optimistic\"\n\nexport { preloadCollection }\n\n// Cache for snapshots to prevent infinite loops\nlet snapshotCache: Map<\n  string,\n  { state: Map<unknown, unknown>; transactions: SortedMap<string, Transaction> }\n> | null = null\n\n/**\n * Hook that provides access to all collections\n *\n * @returns A Map of all collections with their states and transactions\n */\nexport function useCollections() {\n  return useSyncExternalStoreWithSelector(\n    (callback) => {\n      // Subscribe to the collections store for new collections\n      const storeUnsubscribe = collectionsStore.subscribe(() => {\n        snapshotCache = null // Invalidate cache when collections change\n        callback()\n      })\n\n      // Subscribe to all collections' derived states and transactions\n      const collectionUnsubscribes = Array.from(\n        collectionsStore.state.values()\n      ).map((collection) => {\n        const derivedStateUnsub = collection.derivedState.subscribe(() => {\n          snapshotCache = null // Invalidate cache when state changes\n          callback()\n        })\n\n        return derivedStateUnsub\n      })\n\n      return () => {\n        storeUnsubscribe()\n        collectionUnsubscribes.forEach((unsubscribe) => unsubscribe())\n      }\n    },\n    // Get snapshot of all collections and their transactions\n    () => {\n      if (snapshotCache) {\n        return snapshotCache\n      }\n\n      const snapshot = new Map<\n        string,\n        {\n          state: Map<unknown, unknown>\n          transactions: SortedMap<string, Transaction>\n        }\n      >()\n      for (const [id, collection] of collectionsStore.state) {\n        snapshot.set(id, {\n          state: collection.derivedState.state,\n          transactions: collection.transactions.state,\n        })\n      }\n      snapshotCache = snapshot\n      return snapshot\n    },\n    // Server snapshot (same as client for now)\n    () => {\n      if (snapshotCache) {\n        return snapshotCache\n      }\n\n      const snapshot = new Map<\n        string,\n        {\n          state: Map<unknown, unknown>\n          transactions: SortedMap<string, Transaction>\n        }\n      >()\n      for (const [id, collection] of collectionsStore.state) {\n        snapshot.set(id, {\n          state: collection.derivedState.state,\n          transactions: collection.transactions.state,\n        })\n      }\n      snapshotCache = snapshot\n      return snapshot\n    },\n    // Identity selector\n    (state) => state,\n    // Custom equality function for Maps\n    (a, b) => {\n      if (a === b) return true\n      if (!(a instanceof Map) || !(b instanceof Map)) return false\n      if (a.size !== b.size) return false\n      for (const [key, value] of a) {\n        const bValue = b.get(key)\n        if (!b.has(key)) return false\n        if (value instanceof Map) {\n          if (!(bValue instanceof Map)) return false\n          if (!shallow(value, bValue)) return false\n        } else if (!Object.is(value, bValue)) {\n          return false\n        }\n      }\n      return true\n    }\n  )\n}\n\n/**\n * Hook to use a specific collection with React\n *\n * @template T - Type of data in the collection\n * @template R - Return type of the selector function\n * @param config - Configuration for the collection\n * @param selector - TODO Optional selector function to transform the collection data\n * @returns Object containing collection data and CRUD operations\n */\n// Overload for when selector is not provided\n\nexport function useCollection<T extends object>(\n  config: CollectionConfig<T>\n): {\n  /**\n   * The collection data as a Map with keys as identifiers\n   */\n  state: Map<string, T>\n  /**\n   * The collection data as an Array of data\n   */\n  data: Array<T>\n  /**\n   * Updates an existing item in the collection\n   *\n   * @param item - The item to update (must exist in collection)\n   * @param configOrCallback - Update configuration or callback function\n   * @param maybeCallback - Callback function if config was provided\n   * @returns {Transaction} A Transaction object representing the update operation\n   * @throws {SchemaValidationError} If the updated data fails schema validation\n   * @throws {Error} If mutationFn is not provided in the collection config\n   * @example\n   * // Update a single item\n   * update(todo, (draft) => { draft.completed = true })\n   *\n   * // Update multiple items\n   * update([todo1, todo2], (drafts) => {\n   *   drafts.forEach(draft => { draft.completed = true })\n   * })\n   *\n   * // Update with metadata\n   * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n   */\n  update: Collection<T>[`update`]\n  /**\n   * Inserts a new item or items into the collection\n   *\n   * @param data - Single item or array of items to insert\n   * @param config - Optional configuration including key(s) and metadata\n   * @returns {Transaction} A Transaction object representing the insert operation\n   * @throws {SchemaValidationError} If the data fails schema validation\n   * @throws {Error} If more keys provided than items to insert\n   * @throws {Error} If mutationFn is not provided in the collection config\n   * @example\n   * // Insert a single item\n   * insert({ text: \"Buy groceries\", completed: false })\n   *\n   * // Insert multiple items\n   * insert([\n   *   { text: \"Buy groceries\", completed: false },\n   *   { text: \"Walk dog\", completed: false }\n   * ])\n   *\n   * // Insert with custom key\n   * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n   */\n  insert: Collection<T>[`insert`]\n  /**\n   * Deletes an item or items from the collection\n   *\n   * @param items - Item(s) to delete (must exist in collection) or their key(s)\n   * @param config - Optional configuration including metadata\n   * @returns {Transaction} A Transaction object representing the delete operation\n   * @throws {Error} If mutationFn is not provided in the collection config\n   * @example\n   * // Delete a single item\n   * delete(todo)\n   *\n   * // Delete multiple items\n   * delete([todo1, todo2])\n   *\n   * // Delete with metadata\n   * delete(todo, { metadata: { reason: \"completed\" } })\n   */\n  delete: Collection<T>[`delete`]\n}\n\n// Overload for when selector is provided\n// eslint-disable-next-line\nexport function useCollection<T extends object, R>(\n  config: CollectionConfig<T>,\n  selector: (d: Map<string, T>) => R\n): {\n  /**\n   * The collection data as a Map with keys as identifiers\n   */\n  state: Map<string, T>\n  /**\n   * The collection data as an Array of items\n   */\n  data: Array<T>\n  /**\n   * Updates an existing item in the collection\n   *\n   * @param item - The item to update (must exist in collection)\n   * @param configOrCallback - Update configuration or callback function\n   * @param maybeCallback - Callback function if config was provided\n   * @returns {Transaction} A Transaction object representing the update operation\n   * @throws {SchemaValidationError} If the updated data fails schema validation\n   * @throws {Error} If mutationFn is not provided in the collection config\n   * @example\n   * // Update a single item\n   * update(todo, (draft) => { draft.completed = true })\n   *\n   * // Update multiple items\n   * update([todo1, todo2], (drafts) => {\n   *   drafts.forEach(draft => { draft.completed = true })\n   * })\n   *\n   * // Update with metadata\n   * update(todo, { metadata: { reason: \"user update\" } }, (draft) => { draft.text = \"Updated text\" })\n   */\n  update: Collection<T>[`update`]\n  /**\n   * Inserts a new item or items into the collection\n   *\n   * @param data - Single item or array of items to insert\n   * @param config - Optional configuration including key(s) and metadata\n   * @returns {Transaction} A Transaction object representing the insert operation\n   * @throws {SchemaValidationError} If the data fails schema validation\n   * @throws {Error} If more keys provided than items to insert\n   * @throws {Error} If mutationFn is not provided in the collection config\n   * @example\n   * // Insert a single item\n   * insert({ text: \"Buy groceries\", completed: false })\n   *\n   * // Insert multiple items\n   * insert([\n   *   { text: \"Buy groceries\", completed: false },\n   *   { text: \"Walk dog\", completed: false }\n   * ])\n   *\n   * // Insert with custom key\n   * insert({ text: \"Buy groceries\" }, { key: \"grocery-task\" })\n   */\n  insert: Collection<T>[`insert`]\n  /**\n   * Deletes an item or items from the collection\n   *\n   * @param items - Item(s) to delete (must exist in collection) or their key(s)\n   * @param config - Optional configuration including metadata\n   * @returns {Transaction} A Transaction object representing the delete operation\n   * @throws {Error} If mutationFn is not provided in the collection config\n   * @example\n   * // Delete a single item\n   * delete(todo)\n   *\n   * // Delete multiple items\n   * delete([todo1, todo2])\n   *\n   * // Delete with metadata\n   * delete(todo, { metadata: { reason: \"completed\" } })\n   */\n  delete: Collection<T>[`delete`]\n}\n\n// Implementation\n// eslint-disable-next-line\nexport function useCollection<T extends object, R = any>(\n  config: CollectionConfig<T>,\n  selector?: (d: Map<string, T>) => R\n) {\n  if (selector) {\n    console.log(`selector support not yet implemented`, selector)\n  }\n  // Get or create collection instance\n  if (!collectionsStore.state.has(config.id)) {\n    // If collection doesn't exist yet, create it\n    // This will reuse any existing collection created by preloadCollection\n    collectionsStore.setState((prev) => {\n      const next = new Map(prev)\n      next.set(\n        config.id,\n        new Collection<T>({\n          id: config.id,\n          sync: config.sync,\n          schema: config.schema,\n        })\n      )\n      return next\n    })\n  }\n\n  const collection = collectionsStore.state.get(config.id)! as Collection<T>\n\n  // Use a single subscription to get all the data we need\n  const result = useSyncExternalStoreWithSelector<\n    Map<string, T>,\n    { state: Map<string, T>; data: Array<T> }\n  >(\n    collection.derivedState.subscribe,\n    () => collection.derivedState.state,\n    () => collection.derivedState.state,\n    (stateMap) => {\n      return {\n        state: stateMap,\n        // derivedState & derivedArray are recomputed at the same time.\n        data: collection.derivedArray.state,\n      }\n    },\n    (a, b) => {\n      // Custom equality function that checks each property\n      if (a === b) return true\n\n      // Check if state maps are equal\n      const stateEqual =\n        a.state.size === b.state.size &&\n        Array.from(a.state.keys()).every((key) =>\n          shallow(a.state.get(key), b.state.get(key))\n        )\n\n      // Check if data arrays are equal\n      const dataEqual =\n        a.data.length === b.data.length &&\n        a.data.every((datum, i) => shallow(datum, b.data[i]))\n\n      return stateEqual && dataEqual\n    }\n  )\n\n  const returnValue = {\n    state: result.state,\n    data: result.data,\n    insert: collection.insert.bind(collection),\n    update: collection.update.bind(collection),\n    delete: collection.delete.bind(collection),\n  }\n\n  return returnValue\n}\n\n/**\n * Performs a shallow comparison between two objects\n * Used for equality checking in React hooks\n *\n * @template T - Type of objects to compare\n * @param objA - First object\n * @param objB - Second object\n * @returns True if objects are shallowly equal, false otherwise\n */\nexport function shallow<T>(objA: T, objB: T) {\n  if (Object.is(objA, objB)) {\n    return true\n  }\n\n  if (\n    typeof objA !== `object` ||\n    objA === null ||\n    typeof objB !== `object` ||\n    objB === null\n  ) {\n    return false\n  }\n\n  if (objA instanceof Map && objB instanceof Map) {\n    if (objA.size !== objB.size) return false\n    for (const [k, v] of objA) {\n      if (!objB.has(k) || !Object.is(v, objB.get(k))) return false\n    }\n    return true\n  }\n\n  if (objA instanceof Set && objB instanceof Set) {\n    if (objA.size !== objB.size) return false\n    for (const v of objA) {\n      if (!objB.has(v)) return false\n    }\n    return true\n  }\n\n  const keysA = Object.keys(objA)\n  if (keysA.length !== Object.keys(objB).length) {\n    return false\n  }\n\n  for (const key of keysA) {\n    if (\n      !Object.prototype.hasOwnProperty.call(objB, key) ||\n      !Object.is(objA[key as keyof T], objB[key as keyof T])\n    ) {\n      return false\n    }\n  }\n  return true\n}\n"],"names":["useSyncExternalStoreWithSelector","collectionsStore","Collection"],"mappings":"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"}