UNPKG

@rb2bv/cache-handler

Version:
518 lines (511 loc) 12.6 kB
"use strict"; 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 __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; 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); // ../../node_modules/cluster-key-slot/lib/index.js var require_lib = __commonJS({ "../../node_modules/cluster-key-slot/lib/index.js"(exports2, module2) { "use strict"; var lookup = [ 0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290, 45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 17044, 29431, 25302, 37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314, 13379, 1056, 5121, 25830, 29895, 17572, 21637, 42346, 46411, 34088, 38153, 58862, 62927, 50604, 54669, 13907, 9842, 5649, 1584, 30423, 26358, 22165, 18100, 46939, 42874, 38681, 34616, 63455, 59390, 55197, 51132, 18628, 22757, 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789, 59790, 63919, 35144, 39273, 43274, 47403, 23285, 19156, 31415, 27286, 6769, 2640, 14899, 10770, 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879, 19684, 23749, 11298, 15363, 3168, 7233, 60846, 64911, 52716, 56781, 44330, 48395, 36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439, 61374, 57309, 53244, 48923, 44858, 40793, 36728, 37256, 33193, 45514, 41451, 53516, 49453, 61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742, 24679, 33721, 37784, 41979, 46042, 49981, 54044, 58239, 62302, 689, 4752, 8947, 13010, 16949, 21012, 25207, 29270, 46570, 42443, 38312, 34185, 62830, 58703, 54572, 50445, 13538, 9411, 5280, 1153, 29798, 25671, 21540, 17413, 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939, 14066, 1681, 5808, 26199, 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368, 35305, 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093, 56156, 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254, 2801, 6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424, 36297, 31782, 27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342, 53085, 57212, 44955, 49082, 36825, 40952, 28183, 32310, 20053, 24180, 11923, 16050, 3793, 7920 ]; var toUTF8Array = function toUTF8Array2(str) { var char; var i = 0; var p = 0; var utf8 = []; var len = str.length; for (; i < len; i++) { char = str.charCodeAt(i); if (char < 128) { utf8[p++] = char; } else if (char < 2048) { utf8[p++] = char >> 6 | 192; utf8[p++] = char & 63 | 128; } else if ((char & 64512) === 55296 && i + 1 < str.length && (str.charCodeAt(i + 1) & 64512) === 56320) { char = 65536 + ((char & 1023) << 10) + (str.charCodeAt(++i) & 1023); utf8[p++] = char >> 18 | 240; utf8[p++] = char >> 12 & 63 | 128; utf8[p++] = char >> 6 & 63 | 128; utf8[p++] = char & 63 | 128; } else { utf8[p++] = char >> 12 | 224; utf8[p++] = char >> 6 & 63 | 128; utf8[p++] = char & 63 | 128; } } return utf8; }; var generate = module2.exports = function generate2(str) { var char; var i = 0; var start = -1; var result = 0; var resultHash = 0; var utf8 = typeof str === "string" ? toUTF8Array(str) : str; var len = utf8.length; while (i < len) { char = utf8[i++]; if (start === -1) { if (char === 123) { start = i; } } else if (char !== 125) { resultHash = lookup[(char ^ resultHash >> 8) & 255] ^ resultHash << 8; } else if (i - 1 !== start) { return resultHash & 16383; } result = lookup[(char ^ result >> 8) & 255] ^ result << 8; } return result & 16383; }; module2.exports.generateMulti = function generateMulti(keys) { var i = 1; var len = keys.length; var base = generate(keys[0]); while (i < len) { if (generate(keys[i++]) !== base) return -1; } return base; }; } }); // src/handlers/experimental-redis-cluster.ts var experimental_redis_cluster_exports = {}; __export(experimental_redis_cluster_exports, { default: () => createHandler }); module.exports = __toCommonJS(experimental_redis_cluster_exports); var import_cluster_key_slot = __toESM(require_lib(), 1); // src/constants.ts var MAX_INT32 = 2 ** 31 - 1; var REVALIDATED_TAGS_KEY = "__revalidated_tags__"; // src/helpers/get-timeout-redis-command-options.ts var import_redis = require("redis"); function getTimeoutRedisCommandOptions(timeoutMs) { if (timeoutMs === 0) { return (0, import_redis.commandOptions)({}); } return (0, import_redis.commandOptions)({ signal: AbortSignal.timeout(timeoutMs) }); } // src/helpers/is-implicit-tag.ts var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_"; function isImplicitTag(tag) { return tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID); } // src/handlers/experimental-redis-cluster.ts function groupKeysBySlot(keys) { const slotKeysMap = /* @__PURE__ */ new Map(); for (const key of keys) { const slot = (0, import_cluster_key_slot.default)(key); const slotKeys = slotKeysMap.get(slot); if (slotKeys) { slotKeys.push(key); } else { slotKeysMap.set(slot, [key]); } } return slotKeysMap; } function createHandler({ cluster, keyPrefix = "", sharedTagsKey = "__sharedTags__", timeoutMs = 5e3, keyExpirationStrategy = "EXPIREAT", revalidateTagQuerySize = 100 }) { const revalidatedTagsKey = keyPrefix + REVALIDATED_TAGS_KEY; return { name: "experimental-redis-cluster", async get(key, { implicitTags }) { const result = await cluster.get(getTimeoutRedisCommandOptions(timeoutMs), keyPrefix + key); if (!result) { return null; } const cacheValue = JSON.parse(result); if (!cacheValue) { return null; } const combinedTags = /* @__PURE__ */ new Set([...cacheValue.tags, ...implicitTags]); if (combinedTags.size === 0) { return cacheValue; } const revalidationTimes = await cluster.hmGet( getTimeoutRedisCommandOptions(timeoutMs), revalidatedTagsKey, Array.from(combinedTags) ); for (const timeString of revalidationTimes) { if (timeString && Number.parseInt(timeString, 10) > cacheValue.lastModified) { await cluster.unlink(getTimeoutRedisCommandOptions(timeoutMs), keyPrefix + key); return null; } } return cacheValue; }, async set(key, cacheHandlerValue) { const options = getTimeoutRedisCommandOptions(timeoutMs); let setOperation; let expireOperation; switch (keyExpirationStrategy) { case "EXAT": { setOperation = cluster.set( options, keyPrefix + key, JSON.stringify(cacheHandlerValue), typeof cacheHandlerValue.lifespan?.expireAt === "number" ? { EXAT: cacheHandlerValue.lifespan.expireAt } : void 0 ); break; } case "EXPIREAT": { setOperation = cluster.set(options, keyPrefix + key, JSON.stringify(cacheHandlerValue)); expireOperation = cacheHandlerValue.lifespan ? cluster.expireAt(options, keyPrefix + key, cacheHandlerValue.lifespan.expireAt) : void 0; break; } default: { throw new Error(`Invalid keyExpirationStrategy: ${keyExpirationStrategy}`); } } const setTagsOperation = cacheHandlerValue.tags.length > 0 ? cluster.hSet(options, keyPrefix + sharedTagsKey, key, JSON.stringify(cacheHandlerValue.tags)) : void 0; await Promise.all([setOperation, expireOperation, setTagsOperation]); }, async revalidateTag(tag) { if (isImplicitTag(tag)) { await cluster.hSet(getTimeoutRedisCommandOptions(timeoutMs), revalidatedTagsKey, tag, Date.now()); } const tagsMap = /* @__PURE__ */ new Map(); let cursor = 0; const hScanOptions = { COUNT: revalidateTagQuerySize }; do { const remoteTagsPortion = await cluster.hScan( getTimeoutRedisCommandOptions(timeoutMs), keyPrefix + sharedTagsKey, cursor, hScanOptions ); for (const { field, value } of remoteTagsPortion.tuples) { tagsMap.set(field, JSON.parse(value)); } cursor = remoteTagsPortion.cursor; } while (cursor !== 0); const keysToDelete = []; const tagsToDelete = []; for (const [key, tags] of tagsMap) { if (tags.includes(tag)) { keysToDelete.push(keyPrefix + key); tagsToDelete.push(key); } } if (keysToDelete.length === 0) { return; } const slotKeysMap = groupKeysBySlot(keysToDelete); const unlinkPromises = []; for (const [slot, keys] of slotKeysMap) { const targetMasterNode = cluster.slots[slot]?.master; const client = await targetMasterNode?.client; if (keys.length === 0 || !client) { continue; } const unlinkPromisesForSlot = client.unlink(getTimeoutRedisCommandOptions(timeoutMs), keys); if (unlinkPromisesForSlot) { unlinkPromises.push(unlinkPromisesForSlot); } } const updateTagsOperation = cluster.hDel( { isolated: true, ...getTimeoutRedisCommandOptions(timeoutMs) }, keyPrefix + sharedTagsKey, tagsToDelete ); await Promise.allSettled([...unlinkPromises, updateTagsOperation]); }, async delete(key) { await cluster.unlink(getTimeoutRedisCommandOptions(timeoutMs), key); } }; }