UNPKG

@intlayer/config

Version:

Retrieve Intlayer configurations and manage environment variables for both server-side and client-side environments.

426 lines (424 loc) 13.3 kB
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs'); const require_package = require('../package.cjs'); let node_path = require("node:path"); node_path = require_rolldown_runtime.__toESM(node_path); let node_crypto = require("node:crypto"); node_crypto = require_rolldown_runtime.__toESM(node_crypto); let node_fs_promises = require("node:fs/promises"); node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises); let node_v8 = require("node:v8"); node_v8 = require_rolldown_runtime.__toESM(node_v8); let node_zlib = require("node:zlib"); node_zlib = require_rolldown_runtime.__toESM(node_zlib); //#region src/utils/cache.ts /** ------------------------- Utilities ------------------------- **/ /** Prefer a fast non-crypto hash if available, then fast crypto, then sha256. */ const pickHashAlgorithm = () => { try { (0, node_crypto.createHash)("xxhash64").update("test").digest(); return "xxhash64"; } catch {} try { (0, node_crypto.createHash)("sha1").update("test").digest(); return "sha1"; } catch {} return "sha256"; }; const HASH_ALGORITHM = pickHashAlgorithm(); /** Base64url without padding for compact, file-system-safe ids. */ const toBase64Url = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); /** Token helpers to minimize collisions while streaming to the hasher. */ const token = { start: (hasher, tag) => hasher.update(`<${tag}>`), sep: (hasher) => hasher.update("|"), end: (hasher, tag) => hasher.update(`</${tag}>`), str: (hasher, stringValue) => { hasher.update(`${stringValue.length}#`); hasher.update(stringValue); }, num: (hasher, numberValue) => hasher.update(Number.isNaN(numberValue) ? "NaN" : numberValue === Infinity ? "Inf" : numberValue === -Infinity ? "-Inf" : String(numberValue)), big: (hasher, bigintValue) => hasher.update(bigintValue.toString(10)), bool: (hasher, booleanValue) => hasher.update(booleanValue ? "1" : "0") }; /** * Streams a canonical representation of `value` into `hasher` without * constructing large intermediate strings. Objects/Maps/Sets are normalized. */ const stableHashValue = (hasher, value, seen) => { const valueType = typeof value; if (value === null) { token.start(hasher, "null"); token.end(hasher, "null"); return; } if (valueType === "undefined") { token.start(hasher, "undef"); token.end(hasher, "undef"); return; } if (valueType === "number") { token.start(hasher, "num"); token.num(hasher, value); token.end(hasher, "num"); return; } if (valueType === "bigint") { token.start(hasher, "big"); token.big(hasher, value); token.end(hasher, "big"); return; } if (valueType === "boolean") { token.start(hasher, "bool"); token.bool(hasher, value); token.end(hasher, "bool"); return; } if (valueType === "string") { token.start(hasher, "str"); token.str(hasher, value); token.end(hasher, "str"); return; } if (valueType === "symbol") { token.start(hasher, "sym"); token.str(hasher, String(value)); token.end(hasher, "sym"); return; } if (valueType === "function") { const functionValue = value; token.start(hasher, "fn"); token.str(hasher, functionValue.name ?? ""); token.sep(hasher); token.num(hasher, functionValue.length); token.end(hasher, "fn"); return; } if (Array.isArray(value)) { if (seen.has(value)) { token.start(hasher, "arr"); token.str(hasher, "Circular"); token.end(hasher, "arr"); return; } seen.add(value); token.start(hasher, "arr"); for (let i = 0; i < value.length; i++) { token.sep(hasher); stableHashValue(hasher, value[i], seen); } token.end(hasher, "arr"); seen.delete(value); return; } if (value instanceof Date) { token.start(hasher, "date"); token.str(hasher, value.toISOString()); token.end(hasher, "date"); return; } if (value instanceof RegExp) { const regex = value; token.start(hasher, "re"); token.str(hasher, regex.source); token.sep(hasher); token.str(hasher, regex.flags); token.end(hasher, "re"); return; } if (value instanceof Set) { const setValue = value; if (seen.has(setValue)) { token.start(hasher, "set"); token.str(hasher, "Circular"); token.end(hasher, "set"); return; } seen.add(setValue); const items = []; for (const v of setValue) items.push(stableStringify(v)); items.sort(); token.start(hasher, "set"); for (const item of items) { token.sep(hasher); token.str(hasher, item); } token.end(hasher, "set"); seen.delete(setValue); return; } if (value instanceof Map) { const mapObject = value; if (seen.has(mapObject)) { token.start(hasher, "map"); token.str(hasher, "Circular"); token.end(hasher, "map"); return; } seen.add(mapObject); const entries = []; for (const [k, v] of mapObject.entries()) entries.push([stableStringify(k), v]); entries.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0); token.start(hasher, "map"); for (const [keyFingerprint, entryValue] of entries) { token.sep(hasher); token.str(hasher, keyFingerprint); token.sep(hasher); stableHashValue(hasher, entryValue, seen); } token.end(hasher, "map"); seen.delete(mapObject); return; } if (ArrayBuffer.isView(value)) { const view = value; token.start(hasher, "typed"); token.str(hasher, Object.getPrototypeOf(view).constructor.name); token.sep(hasher); hasher.update(Buffer.from(view.buffer, view.byteOffset, view.byteLength)); token.end(hasher, "typed"); return; } if (value instanceof ArrayBuffer) { const buffer = Buffer.from(value); token.start(hasher, "ab"); hasher.update(buffer); token.end(hasher, "ab"); return; } if (typeof URL !== "undefined" && value instanceof URL) { token.start(hasher, "url"); token.str(hasher, value.toString()); token.end(hasher, "url"); return; } if (value instanceof Error) { const errorValue = value; token.start(hasher, "err"); token.str(hasher, errorValue.name || ""); token.sep(hasher); token.str(hasher, errorValue.message || ""); token.sep(hasher); token.str(hasher, errorValue.stack || ""); token.end(hasher, "err"); return; } if (valueType === "object") { const objectValue = value; if (seen.has(objectValue)) { token.start(hasher, "obj"); token.str(hasher, "Circular"); token.end(hasher, "obj"); return; } seen.add(objectValue); const keys = Object.keys(objectValue).sort(); token.start(hasher, "obj"); for (const key of keys) { token.sep(hasher); token.str(hasher, key); token.sep(hasher); stableHashValue(hasher, objectValue[key], seen); } token.end(hasher, "obj"); seen.delete(objectValue); return; } token.start(hasher, "other"); token.str(hasher, String(value)); token.end(hasher, "other"); }; /** Public stringify kept for convenience / debugging (now faster & broader). */ const stableStringify = (value, _stack = /* @__PURE__ */ new WeakSet()) => { const hasher = (0, node_crypto.createHash)(HASH_ALGORITHM); stableHashValue(hasher, value, _stack); return toBase64Url(hasher.digest()); }; /** Compute a compact, stable id for arbitrary key tuples. */ const computeKeyId = (keyParts) => { const h = (0, node_crypto.createHash)(HASH_ALGORITHM); token.start(h, "keys"); for (let i = 0; i < keyParts.length; i++) { token.sep(h); stableHashValue(h, keyParts[i], /* @__PURE__ */ new WeakSet()); } token.end(h, "keys"); return toBase64Url(h.digest()); }; const cacheMap = /* @__PURE__ */ new Map(); const getCache = (...key) => { return cacheMap.get(computeKeyId(key)); }; const setCache = (...args) => { const value = args[args.length - 1]; const key = args.slice(0, -1); cacheMap.set(computeKeyId(key), value); }; const clearCache = (idOrKey) => { cacheMap.delete(idOrKey); }; const clearAllCache = () => { cacheMap.clear(); }; const cache = { get: getCache, set: setCache, clear: clearCache }; const DEFAULTS = { compress: true }; const ensureDir = async (dir) => { await (0, node_fs_promises.mkdir)(dir, { recursive: true }); }; const atomicWriteFile = async (file, data) => { const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`; await (0, node_fs_promises.writeFile)(tmp, data); await (0, node_fs_promises.rename)(tmp, file); }; const shouldUseCompression = (buf, force) => force === true || force !== false && buf.byteLength > 1024; /** Derive on-disk path from config dir + namespace + key id. */ const cachePath = (cacheDir, id, ns) => (0, node_path.join)(cacheDir, ns ? (0, node_path.join)(ns, id) : id); /** ------------------------- Local cache facade ------------------------- **/ const localCache = (intlayerConfig, keys, options) => { const { cacheDir } = intlayerConfig.content; const buildCacheEnabled = intlayerConfig.build.cache ?? true; const persistent = options?.persistent === true || typeof options?.persistent === "undefined" && buildCacheEnabled; const { compress, ttlMs, maxTimeMs, namespace } = { ...DEFAULTS, ...options }; const id = computeKeyId(keys); const filePath = cachePath(cacheDir, id, namespace); const readFromDisk = async () => { try { const statValue = await (0, node_fs_promises.stat)(filePath).catch(() => void 0); if (!statValue) return void 0; if (typeof ttlMs === "number" && ttlMs > 0) { if (Date.now() - statValue.mtimeMs > ttlMs) return void 0; } let raw = await (0, node_fs_promises.readFile)(filePath); const flag = raw[0]; raw = raw.subarray(1); const deserialized = (0, node_v8.deserialize)(flag === 1 ? (0, node_zlib.gunzipSync)(raw) : raw); let value; const maybeObj = deserialized; if (!!maybeObj && typeof maybeObj === "object" && typeof maybeObj.v === "string" && typeof maybeObj.ts === "number" && Object.hasOwn(maybeObj, "d")) { const entry = maybeObj; if (entry.v !== require_package.version) { try { await (0, node_fs_promises.unlink)(filePath); } catch {} return; } if (typeof maxTimeMs === "number" && maxTimeMs > 0) { if (Date.now() - entry.ts > maxTimeMs) { try { await (0, node_fs_promises.unlink)(filePath); } catch {} return; } } value = entry.d; } else { if (typeof maxTimeMs === "number" && maxTimeMs > 0) { if (Date.now() - statValue.mtimeMs > maxTimeMs) { try { await (0, node_fs_promises.unlink)(filePath); } catch {} return; } } value = deserialized; } cacheMap.set(id, value); return value; } catch { return; } }; const writeToDisk = async (value) => { try { await ensureDir((0, node_path.dirname)(filePath)); const envelope = { v: require_package.version, ts: Date.now(), d: value }; const payload = Buffer.from((0, node_v8.serialize)(envelope)); const gz = shouldUseCompression(payload, compress) ? (0, node_zlib.gzipSync)(payload) : payload; await atomicWriteFile(filePath, Buffer.concat([Buffer.from([gz === payload ? 0 : 1]), gz])); } catch {} }; return { get: async () => { const mem = cacheMap.get(id); if (mem !== void 0) return mem; if (persistent && buildCacheEnabled) return await readFromDisk(); }, set: async (value) => { cacheMap.set(id, value); if (persistent && buildCacheEnabled) await writeToDisk(value); }, clear: async () => { cacheMap.delete(id); try { await (0, node_fs_promises.unlink)(filePath); } catch {} }, clearAll: async () => { clearAllCache(); if (persistent && buildCacheEnabled) { const base = namespace ? (0, node_path.join)(cacheDir, namespace) : cacheDir; try { await (0, node_fs_promises.rm)(base, { recursive: true, force: true }); } catch {} try { await (0, node_fs_promises.mkdir)(base, { recursive: true }); } catch {} } }, isValid: async () => { if (cacheMap.get(id) !== void 0) return true; if (!persistent || !buildCacheEnabled) return false; try { const statValue = await (0, node_fs_promises.stat)(filePath).catch(() => void 0); if (!statValue) return false; if (typeof ttlMs === "number" && ttlMs > 0) { if (Date.now() - statValue.mtimeMs > ttlMs) return false; } let raw = await (0, node_fs_promises.readFile)(filePath); const flag = raw[0]; raw = raw.subarray(1); const maybeObj = (0, node_v8.deserialize)(flag === 1 ? (0, node_zlib.gunzipSync)(raw) : raw); if (!!maybeObj && typeof maybeObj === "object" && typeof maybeObj.v === "string" && typeof maybeObj.ts === "number" && Object.hasOwn(maybeObj, "d")) { const entry = maybeObj; if (entry.v !== require_package.version) return false; if (typeof maxTimeMs === "number" && maxTimeMs > 0) { if (Date.now() - entry.ts > maxTimeMs) return false; } return true; } if (typeof maxTimeMs === "number" && maxTimeMs > 0) { if (Date.now() - statValue.mtimeMs > maxTimeMs) return false; } return true; } catch { return false; } }, id, filePath }; }; //#endregion exports.cache = cache; exports.clearAllCache = clearAllCache; exports.clearCache = clearCache; exports.getCache = getCache; exports.localCache = localCache; exports.setCache = setCache; exports.stableStringify = stableStringify; //# sourceMappingURL=cache.cjs.map