{"version":3,"file":"useAsyncBatcher.cjs","names":["useDefaultPacerOptions","AsyncBatcher","shallow"],"sources":["../../src/async-batcher/useAsyncBatcher.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react'\nimport { AsyncBatcher } from '@tanstack/pacer/async-batcher'\nimport { shallow, useSelector } from '@tanstack/react-store'\nimport { useDefaultPacerOptions } from '../provider/PacerProvider'\nimport type {\n  AsyncBatcherOptions,\n  AsyncBatcherState,\n} from '@tanstack/pacer/async-batcher'\nimport type { Store } from '@tanstack/react-store'\nimport type { FunctionComponent, ReactNode } from 'react'\n\nexport interface ReactAsyncBatcherOptions<\n  TValue,\n  TSelected = {},\n> extends AsyncBatcherOptions<TValue> {\n  /**\n   * Optional callback invoked when the component unmounts. Receives the batcher instance.\n   * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), reset(), cancel(), add logging, etc.\n   */\n  onUnmount?: (batcher: ReactAsyncBatcher<TValue, TSelected>) => void\n}\n\nexport interface ReactAsyncBatcher<TValue, TSelected = {}> extends Omit<\n  AsyncBatcher<TValue>,\n  'store'\n> {\n  /**\n   * A React HOC (Higher Order Component) that allows you to subscribe to the batcher state.\n   *\n   * This is useful for opting into state re-renders for specific parts of the batcher state\n   * deep in your component tree without needing to pass a selector to the hook.\n   *\n   * @example\n   * <batcher.Subscribe selector={(state) => ({ size: state.size, isExecuting: state.isExecuting })}>\n   *   {({ size, isExecuting }) => (\n   *     <div>Batch: {size} items, {isExecuting ? 'Processing' : 'Ready'}</div>\n   *   )}\n   * </batcher.Subscribe>\n   */\n  Subscribe: <TSelected>(props: {\n    selector: (state: AsyncBatcherState<TValue>) => TSelected\n    children: ((state: TSelected) => ReactNode) | ReactNode\n  }) => ReturnType<FunctionComponent>\n  /**\n   * Reactive state that will be updated and re-rendered when the batcher state changes\n   *\n   * Use this instead of `batcher.store.state`\n   */\n  readonly state: Readonly<TSelected>\n  /**\n   * @deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state.\n   * The state on the store object is not reactive, as it has not been wrapped in a `useSelector` hook internally.\n   * Although, you can make the state reactive by using the `useSelector` in your own usage.\n   */\n  readonly store: Store<Readonly<AsyncBatcherState<TValue>>>\n}\n\n/**\n * A React hook that creates an `AsyncBatcher` instance for managing asynchronous batches of items.\n *\n * This is the async version of the useBatcher hook. Unlike the sync version, this async batcher:\n * - Handles promises and returns results from batch executions\n * - Provides error handling with configurable error behavior\n * - Tracks success, error, and settle counts separately\n * - Has state tracking for when batches are executing\n * - Returns the result of the batch function execution\n *\n * Features:\n * - Configurable batch size and wait time\n * - Custom batch processing logic via getShouldExecute\n * - Event callbacks for monitoring batch operations\n * - Error handling for failed batch operations\n * - Automatic or manual batch processing\n *\n * The batcher collects items and processes them in batches based on:\n * - Maximum batch size (number of items per batch)\n * - Time-based batching (process after X milliseconds)\n * - Custom batch processing logic via getShouldExecute\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and batcher instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n * - The error state can be checked using the underlying AsyncBatcher instance\n *\n * ## State Management and Selector\n *\n * The hook uses TanStack Store for reactive state management. You can subscribe to state changes\n * in two ways:\n *\n * **1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)**\n *\n * Use the `Subscribe` HOC to subscribe to state changes deep in your component tree without\n * needing to pass a selector to the hook. This is ideal when you want to subscribe to state\n * in child components.\n *\n * **2. Using the `selector` parameter (For hook-level subscriptions)**\n *\n * The `selector` parameter allows you to specify which state changes will trigger a re-render\n * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant\n * state changes occur.\n *\n * **By default, there will be no reactive state subscriptions** and you must opt-in to state\n * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary\n * re-renders and gives you full control over when your component updates.\n *\n * Available state properties:\n * - `errorCount`: Number of batch executions that have resulted in errors\n * - `failedItems`: Array of items that failed during batch processing\n * - `isEmpty`: Whether the batcher has no items to process\n * - `isExecuting`: Whether a batch is currently being processed asynchronously\n * - `isPending`: Whether the batcher is waiting for the timeout to trigger batch processing\n * - `isRunning`: Whether the batcher is active and will process items automatically\n * - `items`: Array of items currently queued for batch processing\n * - `lastResult`: The result from the most recent batch execution\n * - `settleCount`: Number of batch executions that have completed (success or error)\n * - `size`: Number of items currently in the batch queue\n * - `status`: Current processing status ('idle' | 'pending' | 'executing' | 'populated')\n * - `successCount`: Number of batch executions that have completed successfully\n * - `totalItemsProcessed`: Total number of items processed across all batches\n * - `totalItemsFailed`: Total number of items that have failed processing\n *\n * ## Unmount behavior\n *\n * By default, the hook cancels any pending batch and aborts any in-flight execution when the component unmounts.\n * Abort only cancels underlying operations (e.g. fetch) when the abort signal from `getAbortSignal()` is passed to them.\n * Use the `onUnmount` option to customize this. For example, to flush pending work instead:\n *\n * ```tsx\n * const batcher = useAsyncBatcher(fn, {\n *   maxSize: 10,\n *   wait: 2000,\n *   onUnmount: (b) => b.flush()\n * });\n * ```\n *\n * Note: For async utils, `flush()` returns a Promise and runs fire-and-forget in the cleanup.\n * If your batch function updates React state, those updates may run after the component has\n * unmounted, which can cause \"setState on unmounted component\" warnings. Guard your callbacks\n * accordingly when using onUnmount with flush.\n *\n * @example\n * ```tsx\n * // Basic async batcher for API requests - no reactive state subscriptions\n * const asyncBatcher = useAsyncBatcher(\n *   async (items) => {\n *     const results = await Promise.all(items.map(item => processItem(item)));\n *     return results;\n *   },\n *   { maxSize: 10, wait: 2000 }\n * );\n *\n * // Subscribe to state changes deep in component tree using Subscribe HOC\n * <asyncBatcher.Subscribe selector={(state) => ({ size: state.size, isExecuting: state.isExecuting })}>\n *   {({ size, isExecuting }) => (\n *     <div>Batch: {size} items, {isExecuting ? 'Processing' : 'Ready'}</div>\n *   )}\n * </asyncBatcher.Subscribe>\n *\n * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators)\n * const asyncBatcher = useAsyncBatcher(\n *   async (items) => {\n *     const results = await Promise.all(items.map(item => processItem(item)));\n *     return results;\n *   },\n *   { maxSize: 10, wait: 2000 },\n *   (state) => ({\n *     isExecuting: state.isExecuting,\n *     isPending: state.isPending,\n *     status: state.status\n *   })\n * );\n *\n * // Opt-in to re-render when results are available (optimized for data display)\n * const asyncBatcher = useAsyncBatcher(\n *   async (items) => {\n *     const results = await Promise.all(items.map(item => processItem(item)));\n *     return results;\n *   },\n *   { maxSize: 10, wait: 2000 },\n *   (state) => ({\n *     lastResult: state.lastResult,\n *     successCount: state.successCount,\n *     totalItemsProcessed: state.totalItemsProcessed\n *   })\n * );\n *\n * // Opt-in to re-render when error state changes (optimized for error handling)\n * const asyncBatcher = useAsyncBatcher(\n *   async (items) => {\n *     const results = await Promise.all(items.map(item => processItem(item)));\n *     return results;\n *   },\n *   {\n *     maxSize: 10,\n *     wait: 2000,\n *     onError: (error) => console.error('Batch processing failed:', error)\n *   },\n *   (state) => ({\n *     errorCount: state.errorCount,\n *     failedItems: state.failedItems,\n *     totalItemsFailed: state.totalItemsFailed\n *   })\n * );\n *\n * // Complete example with all callbacks\n * const asyncBatcher = useAsyncBatcher(\n *   async (items) => {\n *     const results = await Promise.all(items.map(item => processItem(item)));\n *     return results;\n *   },\n *   {\n *     maxSize: 10,\n *     wait: 2000,\n *     onSuccess: (result) => {\n *       console.log('Batch processed successfully:', result);\n *     },\n *     onError: (error) => {\n *       console.error('Batch processing failed:', error);\n *     }\n *   }\n * );\n *\n * // Add items to batch\n * asyncBatcher.addItem(newItem);\n *\n * // Manually execute batch\n * const result = await asyncBatcher.execute();\n *\n * // Access the selected state (will be empty object {} unless selector provided)\n * const { isExecuting, lastResult, size } = asyncBatcher.state;\n * ```\n */\nexport function useAsyncBatcher<TValue, TSelected = {}>(\n  fn: (items: Array<TValue>) => Promise<any>,\n  options: ReactAsyncBatcherOptions<TValue, TSelected> = {},\n  selector: (state: AsyncBatcherState<TValue>) => TSelected = () =>\n    ({}) as TSelected,\n): ReactAsyncBatcher<TValue, TSelected> {\n  const mergedOptions = {\n    ...useDefaultPacerOptions().asyncBatcher,\n    ...options,\n  } as ReactAsyncBatcherOptions<TValue, TSelected>\n  const [asyncBatcher] = useState(() => {\n    const asyncBatcherInstance = new AsyncBatcher<TValue>(\n      fn,\n      mergedOptions,\n    ) as unknown as ReactAsyncBatcher<TValue, TSelected>\n\n    /* eslint-disable-next-line @eslint-react/component-hook-factories -- Subscribe attached once in useState lazy init; stable per instance */\n    asyncBatcherInstance.Subscribe = function Subscribe<TSelected>(props: {\n      selector: (state: AsyncBatcherState<TValue>) => TSelected\n      children: ((state: TSelected) => ReactNode) | ReactNode\n    }) {\n      const selected = useSelector(asyncBatcherInstance.store, props.selector, {\n        compare: shallow,\n      })\n\n      return typeof props.children === 'function'\n        ? props.children(selected)\n        : props.children\n    }\n\n    return asyncBatcherInstance\n  })\n\n  asyncBatcher.fn = fn\n  asyncBatcher.setOptions(mergedOptions)\n\n  /* eslint-disable react-hooks/exhaustive-deps, @eslint-react/exhaustive-deps, react-compiler/react-compiler -- unmount cleanup only; empty deps keep teardown stable */\n  useEffect(() => {\n    return () => {\n      if (mergedOptions.onUnmount) {\n        mergedOptions.onUnmount(asyncBatcher)\n      } else {\n        asyncBatcher.cancel()\n        asyncBatcher.abort()\n      }\n    }\n  }, [])\n  /* eslint-enable react-hooks/exhaustive-deps, @eslint-react/exhaustive-deps, react-compiler/react-compiler */\n\n  const state = useSelector(asyncBatcher.store, selector, { compare: shallow })\n\n  return useMemo(\n    () =>\n      ({\n        ...asyncBatcher,\n        state,\n      }) as ReactAsyncBatcher<TValue, TSelected>, // omit `store` in favor of `state`\n    [asyncBatcher, state],\n  )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0OA,SAAgB,gBACd,IACA,UAAuD,EAAE,EACzD,kBACG,EAAE,GACiC;CACtC,MAAM,gBAAgB;EACpB,GAAGA,8CAAwB,CAAC;EAC5B,GAAG;EACJ;CACD,MAAM,CAAC,0CAA+B;EACpC,MAAM,uBAAuB,IAAIC,2CAC/B,IACA,cACD;AAGD,uBAAqB,YAAY,SAAS,UAAqB,OAG5D;GACD,MAAM,kDAAuB,qBAAqB,OAAO,MAAM,UAAU,EACvE,SAASC,+BACV,CAAC;AAEF,UAAO,OAAO,MAAM,aAAa,aAC7B,MAAM,SAAS,SAAS,GACxB,MAAM;;AAGZ,SAAO;GACP;AAEF,cAAa,KAAK;AAClB,cAAa,WAAW,cAAc;AAGtC,4BAAgB;AACd,eAAa;AACX,OAAI,cAAc,UAChB,eAAc,UAAU,aAAa;QAChC;AACL,iBAAa,QAAQ;AACrB,iBAAa,OAAO;;;IAGvB,EAAE,CAAC;CAGN,MAAM,+CAAoB,aAAa,OAAO,UAAU,EAAE,SAASA,+BAAS,CAAC;AAE7E,kCAEK;EACC,GAAG;EACH;EACD,GACH,CAAC,cAAc,MAAM,CACtB"}