UNPKG

idmp

Version:

A lightweight TypeScript library for deduplicating and caching async function calls with automatic retries, designed for idempotent network requests in React and Node.js.

392 lines (391 loc) 9.44 kB
/*! idmp v3.4.4 | (c) github/haozi | MIT */ "use strict"; var __pow = Math.pow; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const DEFAULT_MAX_AGE = 3e3; const _7days = 6048e5; const noop = () => { }; const UNDEFINED = void 0; const $timeout = setTimeout; const getMax = (a, b) => { return a > b ? a : b; }; const getMin = (a, b) => { return a < b ? a : b; }; const defineReactive = (obj, key, value) => { readonly(value); Object.defineProperty(obj, key, { get: () => value, set: (newValue) => { const msg = `[idmp error] The data is read-only, set ${key.toString()}=${JSON.stringify( newValue )} is not allow`; console.error(`%c ${msg}`, "font-weight: lighter; color: red"); throw new Error(msg); } }); }; const readonly = (obj) => { if (obj == null || typeof obj !== "object") return obj; const protoType = Object.prototype.toString.call(obj); if (!["[object Object]", "[object Array]"].includes(protoType)) return obj; const isImmerDraft = (obj2) => !!obj2[Symbol.for("immer-state")]; if (isImmerDraft(obj)) return obj; Object.keys(obj).forEach((key) => { var _a; const configurable = (_a = Object.getOwnPropertyDescriptor(obj, key)) == null ? void 0 : _a.configurable; if (configurable === UNDEFINED || configurable === true) { defineReactive(obj, key, obj[key]); } }); return obj; }; const getRange = (maxAge) => { if (maxAge < 0) return 0; if (maxAge > _7days) return _7days; return maxAge; }; let _globalStore = {}; const getOptions = (options) => { const { maxRetry = 30, maxAge: paramMaxAge = DEFAULT_MAX_AGE, minRetryDelay = 50, maxRetryDelay = 5e3, onBeforeRetry = noop, signal } = options || {}; const maxAge = getRange(paramMaxAge); return { maxRetry, maxAge, minRetryDelay, maxRetryDelay, onBeforeRetry, f: paramMaxAge === 1 / 0, // Infinity signal }; }; const flush = (globalKey) => { if (!globalKey) return; delete _globalStore[globalKey]; }; const flushAll = () => { _globalStore = {}; }; const idmp = (globalKey, promiseFunc, options) => { if (process.env.NODE_ENV !== "production") { options = readonly(options); } if (!globalKey) { return promiseFunc(); } const { maxRetry, minRetryDelay, maxRetryDelay, maxAge, onBeforeRetry, f: isFiniteParamMaxAge, signal } = getOptions(options); _globalStore[globalKey] = _globalStore[globalKey] || [ 0, // [K.retryCount]: number 0, // [K.status]: Status [] // [K.pendingList]: Array<any> ]; const cache = _globalStore[globalKey]; let callStackLocation = ""; const printLogs = (...msg) => { if (typeof window === "undefined") return; try { if (localStorage.idmp_debug === "false") return; } catch (e) { } if (console.groupCollapsed) { console.groupCollapsed(...msg); console.log("globalKey:", globalKey); console.log("callStackLocation:", callStackLocation); console.log("data:", cache[ 3 /* resolvedData */ ]); console.groupEnd(); } else { console.log(...msg); } }; const reset = () => { cache[ 1 /* status */ ] = 0; cache[ 3 /* resolvedData */ ] = cache[ 4 /* rejectionError */ ] = UNDEFINED; }; const doResolves = () => { const len = cache[ 2 /* pendingList */ ].length; for (let i = 0; i < len; ++i) { cache[ 2 /* pendingList */ ][i][0](cache[ 3 /* resolvedData */ ]); if (process.env.NODE_ENV !== "production") { if (i === 0) { printLogs( `%c[idmp debug] ${globalKey == null ? void 0 : globalKey.toString()} from origin`, "font-weight: lighter" ); } else { printLogs( `%c[idmp debug] ${globalKey == null ? void 0 : globalKey.toString()} from cache`, "color: gray; font-weight: lighter" ); } } } cache[ 2 /* pendingList */ ] = []; if (!isFiniteParamMaxAge) { $timeout(() => { flush(globalKey); }, maxAge); } }; const doRejects = () => { const len = cache[ 2 /* pendingList */ ].length; let maxLen; maxLen = len - maxRetry; if (maxLen < 0 || !isFinite(len)) { maxLen = getMax(1, cache[ 2 /* pendingList */ ].length - 3); } for (let i = 0; i < maxLen; ++i) { cache[ 2 /* pendingList */ ][i][1](cache[ 4 /* rejectionError */ ]); } flush(globalKey); }; const executePromise = () => new Promise((resolve, reject) => { !cache[ 5 /* cachedPromiseFunc */ ] && (cache[ 5 /* cachedPromiseFunc */ ] = promiseFunc); if (process.env.NODE_ENV !== "production") { try { if (cache[ 0 /* retryCount */ ] === 0) { throw new Error(); } } catch (err) { const getCodeLine = (stack, offset = 0) => { if (typeof globalKey === "symbol") return ""; try { let arr = stack.split("\n").filter((o) => o.includes(":")); let idx = Infinity; $0: for (let key of [ "idmp/src/index.ts", "idmp/", "idmp\\", "idmp" ]) { let _idx = arr.length - 1; $1: for (; _idx >= 0; --_idx) { if (arr[_idx].indexOf(key) > -1) { idx = _idx; break $0; } } } const line = arr[idx + offset + 1] || ""; if (line.includes("idmp")) return line; return ""; } catch (e) { return ""; } }; callStackLocation = getCodeLine(err.stack, 1).split(" ").pop() || ""; !cache[ 6 /* _originalErrorStack */ ] && (cache[ 6 /* _originalErrorStack */ ] = err.stack); if (cache[ 6 /* _originalErrorStack */ ] !== err.stack) { const line1 = getCodeLine(cache[ 6 /* _originalErrorStack */ ]); const line2 = getCodeLine(err.stack); if (line1 && line2 && line1 !== line2) { console.error( `[idmp warn] the same key \`${globalKey.toString()}\` may be used multiple times in different places (It may be a misjudgment and can be ignored): see https://github.com/ha0z1/idmp?tab=readme-ov-file#implementation ${[ `1.${line1} ${cache[ 6 /* _originalErrorStack */ ]}`, "------------", `2.${line2} ${err.stack}` ].join("\n")}` ); } } } } if (cache[ 3 /* resolvedData */ ]) { if (process.env.NODE_ENV !== "production") { printLogs( `%c[idmp debug] \`${globalKey == null ? void 0 : globalKey.toString()}\` from cache`, "color: gray;font-weight: lighter" ); } resolve(cache[ 3 /* resolvedData */ ]); return; } if (signal) { if (signal.aborted) return; signal.addEventListener("abort", () => { reset(); cache[ 4 /* rejectionError */ ] = new DOMException( signal.reason, "AbortError" ); doRejects(); }); } if (cache[ 1 /* status */ ] === 0) { cache[ 1 /* status */ ] = 1; cache[ 2 /* pendingList */ ].push([resolve, reject]); cache[ 5 /* cachedPromiseFunc */ ]().then((data) => { if (process.env.NODE_ENV !== "production") { cache[ 3 /* resolvedData */ ] = readonly(data); } else { cache[ 3 /* resolvedData */ ] = data; } doResolves(); cache[ 1 /* status */ ] = 4; }).catch((err) => { cache[ 1 /* status */ ] = 3; cache[ 4 /* rejectionError */ ] = err; ++cache[ 0 /* retryCount */ ]; if (cache[ 0 /* retryCount */ ] > maxRetry) { doRejects(); } else { onBeforeRetry(err, { globalKey, retryCount: cache[ 0 /* retryCount */ ] }); reset(); const delay = getMin( maxRetryDelay, minRetryDelay * __pow(2, cache[ 0 /* retryCount */ ] - 1) ); $timeout(executePromise, delay); } }); } else if (cache[ 1 /* status */ ] === 1) { cache[ 2 /* pendingList */ ].push([resolve, reject]); } }); return executePromise(); }; idmp.flush = flush; idmp.flushAll = flushAll; exports.default = idmp; exports.getOptions = getOptions; exports.idmp = idmp;