UNPKG

react-p-queue

Version:

A React library for handling promise-based queue management with support for batch processing, throttling, and concurrency control using `p-queue`

316 lines (312 loc) 9.44 kB
"use client"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // index.tsx var index_exports = {}; __export(index_exports, { default: () => index_default, useQueue: () => useQueue, useQueueStore: () => useQueueStore }); module.exports = __toCommonJS(index_exports); var import_assert = __toESM(require("assert")); var import_react = require("react"); // batch.ts var BatchQueue = class { batchSize; throttleTime; resolver; identifier; buffer; timeout; /** * Creates an instance of BatchQueue. * * @param {Object} params - Configuration parameters for the batch queue. * @param {number} params.batchSize - The maximum size of the batch before triggering a flush. * @param {number} params.throttleTime - The maximum time (in milliseconds) to wait before triggering a flush. * @param {(batch: T[]) => Promise<R[]>} params.resolver - The function to call with the batch of tasks. * @param {keyof R} params.identifier - The property in the resolver result to match with the task. */ constructor({ batchSize, throttleTime, resolver, identifier }) { this.batchSize = batchSize; this.throttleTime = throttleTime; this.resolver = resolver; this.identifier = identifier; this.buffer = []; this.timeout = null; } /** * Adds a task to the batch queue. If the batch size is reached, it triggers an immediate flush. * Otherwise, it waits for the specified throttle time before flushing the remaining tasks. * * @param {T} task - The task to be added to the batch queue. * @returns {Promise<R>} A promise that resolves with the result of the task once handled. */ addTask(task) { return new Promise((resolve, reject) => { this.buffer.push({ task, resolve, reject }); if (this.buffer.length >= this.batchSize) { this.flush(); } else { if (!this.timeout) { this.timeout = setTimeout(() => this.flush(), this.throttleTime); } } }); } /** * Flushes the current batch of tasks. This will clear the buffer and call the resolver function * with the current batch. If a timeout was set, it will be cleared. * * @returns {void} */ flush() { if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; } if (this.buffer.length > 0) { const batch = this.buffer.splice(0, this.batchSize); this.processBatch(batch); } } /** * Processes a batch of tasks concurrently. * * @param {Array} batch - The batch of tasks to process. * @returns {void} */ async processBatch(batch) { try { const results = await this.resolver(batch.map((item) => item.task)); results.forEach((result) => { const matchedTask = batch.find( (item) => ( // @ts-expect-error item.task[this.identifier] === result[this.identifier] ) ); if (matchedTask) { matchedTask.resolve(result); } }); } catch (error) { batch.forEach((item) => item.reject(error)); } } }; var batch_default = BatchQueue; // index.tsx var import_jsx_runtime = require("react/jsx-runtime"); var PQueueResolverContext = (0, import_react.createContext)( null ); var PQueueContext = (0, import_react.createContext)(null); function initstate(config) { return { config, store: [], tasks: [] }; } var __noop = () => { }; var PQueueDispatchContext = (0, import_react.createContext)(__noop); function reducer(state, action) { switch (action.type) { case "add": { return { ...state, tasks: [...state.tasks, action.task] }; } case "start": { break; } case "result": { const { result } = action; const { error } = result; return { ...state, store: [...state.store, { ...result, ok: error ? true : false }] }; } case "clear": return { ...state, store: [], tasks: [] }; } return state; } function QueueProvider({ config, resolver, batch, throttle, queue, children }) { const [state, dispatch] = (0, import_react.useReducer)(reducer, initstate(config)); const pQueue = (0, import_react.useMemo)(() => queue, [queue]); const batchqueue = (0, import_react.useMemo)(() => { if (!batch) return null; return new batch_default({ batchSize: batch, throttleTime: throttle, identifier: config.identifier, resolver: async (tasks) => { var _a; const results = await resolver(...tasks); (_a = results.data) == null ? void 0 : _a.forEach((result, index) => { const task = tasks[index]; dispatch({ type: "result", result: { data: result, error: results.error } }); }); if (!results.data) { throw results.error || new Error("Resolver failed"); } return results.data; } }); }, [batch, resolver, throttle, config.identifier, dispatch]); (0, import_react.useEffect)(() => { if (!batchqueue) return; const originalFlush = batchqueue.flush.bind(batchqueue); batchqueue.flush = () => { pQueue.add(async () => { await originalFlush(); }); }; return () => { batchqueue.flush(); }; }, [batchqueue, pQueue]); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( PQueueResolverContext.Provider, { value: { resolver, queue: pQueue, batch: batchqueue }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PQueueContext.Provider, { value: state, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PQueueDispatchContext.Provider, { value: dispatch, children }) }) } ); } function __useDispatch() { const dispatch = (0, import_react.useContext)(PQueueDispatchContext); if (!dispatch) throw new Error("QueueProvider not provided"); return dispatch; } function __useResolver() { const context = (0, import_react.useContext)(PQueueResolverContext); if (!context) throw new Error("QueueProvider not provided"); return { batch: context.batch, resolver: context.resolver, queue: context.queue }; } function useQueueStore() { const state = (0, import_react.useContext)(PQueueContext); if (!state) throw new Error("QueueProvider not provided"); const { store } = state; return store; } function useQueue() { const dispatch = __useDispatch(); const { resolver, queue, batch } = __useResolver(); const state = (0, import_react.useContext)(PQueueContext); const store = useQueueStore(); if (!state) throw new Error("QueueProvider not provided"); const onAdd = (0, import_react.useCallback)( async (task) => { const existing = store.find((s) => { return s.data && // @ts-ignore s.data[state.config.identifier] === task[state.config.identifier]; }); if (existing) { return existing; } if (batch) { const res = await batch.addTask(task); if (res) { return { data: res, error: null }; } return { data: null, error: new Error("Resolver failed") }; } else { try { const res = await queue.add(() => resolver(task)); (0, import_assert.default)(res); dispatch({ type: "result", result: res }); return res; } catch (e) { return { data: null, error: e }; } } }, // eslint-disable-next-line react-hooks/exhaustive-deps [batch, dispatch, resolver, queue] ); const onClear = (0, import_react.useCallback)(() => { dispatch({ type: "clear" }); queue.clear(); }, [dispatch, queue]); return (0, import_react.useMemo)( () => ({ add: onAdd, clear: onClear }), [onAdd, onClear] ); } var index_default = QueueProvider; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { useQueue, useQueueStore });