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
JavaScript
/*! idmp v3.4.4 | (c) github/haozi | MIT */
;
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;