@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
JavaScript
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