@intlayer/config 7.1.0 → 7.1.1-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/alias.cjs.map +1 -1
- package/dist/cjs/configFile/getConfiguration.cjs +3 -3
- package/dist/cjs/configFile/getConfiguration.cjs.map +1 -1
- package/dist/cjs/index.cjs +6 -6
- package/dist/cjs/loadExternalFile/loadExternalFile.cjs +1 -1
- package/dist/cjs/utils/cache.cjs +16 -15
- package/dist/cjs/utils/cache.cjs.map +1 -1
- package/dist/cjs/utils/cacheDisk.cjs +162 -0
- package/dist/cjs/utils/cacheDisk.cjs.map +1 -0
- package/dist/cjs/utils/cacheMemory.cjs +269 -0
- package/dist/cjs/utils/cacheMemory.cjs.map +1 -0
- package/dist/cjs/utils/checkVersionsConsistency.cjs +68 -0
- package/dist/cjs/utils/checkVersionsConsistency.cjs.map +1 -0
- package/dist/cjs/utils/getPackageJsonPath.cjs +3 -3
- package/dist/cjs/utils/getPackageJsonPath.cjs.map +1 -1
- package/dist/esm/alias.mjs.map +1 -1
- package/dist/esm/configFile/getConfiguration.mjs +3 -3
- package/dist/esm/configFile/getConfiguration.mjs.map +1 -1
- package/dist/esm/index.mjs +4 -2
- package/dist/esm/loadExternalFile/loadExternalFile.mjs +1 -1
- package/dist/esm/utils/cache.mjs +14 -14
- package/dist/esm/utils/cache.mjs.map +1 -1
- package/dist/esm/utils/cacheDisk.mjs +160 -0
- package/dist/esm/utils/cacheDisk.mjs.map +1 -0
- package/dist/esm/utils/cacheMemory.mjs +262 -0
- package/dist/esm/utils/cacheMemory.mjs.map +1 -0
- package/dist/esm/utils/checkVersionsConsistency.mjs +66 -0
- package/dist/esm/utils/checkVersionsConsistency.mjs.map +1 -0
- package/dist/esm/utils/getPackageJsonPath.mjs +3 -3
- package/dist/esm/utils/getPackageJsonPath.mjs.map +1 -1
- package/dist/types/alias.d.ts +6 -6
- package/dist/types/alias.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/utils/cache.d.ts +3 -4
- package/dist/types/utils/cache.d.ts.map +1 -1
- package/dist/types/utils/cacheDisk.d.ts +35 -0
- package/dist/types/utils/cacheDisk.d.ts.map +1 -0
- package/dist/types/utils/cacheMemory.d.ts +20 -0
- package/dist/types/utils/cacheMemory.d.ts.map +1 -0
- package/dist/types/utils/checkVersionsConsistency.d.ts +7 -0
- package/dist/types/utils/checkVersionsConsistency.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.mjs","names":["items: string[]","entries: Array<[string, unknown]>","DEFAULTS: Required<Pick<LocalCacheOptions, 'compress'>>","value: unknown","configPackageJson.version"],"sources":["../../../src/utils/cache.ts"],"sourcesContent":["import { createHash, type Hash } from 'node:crypto';\nimport {\n mkdir,\n readFile,\n rename,\n rm,\n stat,\n unlink,\n writeFile,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { deserialize, serialize } from 'node:v8';\nimport { gunzipSync, gzipSync } from 'node:zlib';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport configPackageJson from '../../package.json' with { type: 'json' };\n\n/** ------------------------- Utilities ------------------------- **/\n\n/** Prefer a fast non-crypto hash if available, then fast crypto, then sha256. */\nconst pickHashAlgorithm = (): string => {\n try {\n // Node 20+ supports xxhash64 (very fast). We feature-detect at module load.\n createHash('xxhash64').update('test').digest();\n return 'xxhash64';\n } catch {}\n try {\n // sha1 is faster than sha256 and sufficient for cache keys.\n createHash('sha1').update('test').digest();\n return 'sha1';\n } catch {}\n\n return 'sha256';\n};\nconst HASH_ALGORITHM = pickHashAlgorithm();\n\n/** Base64url without padding for compact, file-system-safe ids. */\nconst toBase64Url = (buffer: Buffer): string =>\n buffer\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/g, '');\n\n/** Token helpers to minimize collisions while streaming to the hasher. */\nconst token = {\n start: (hasher: Hash, tag: string) => hasher.update(`<${tag}>`),\n sep: (hasher: Hash) => hasher.update('|'),\n end: (hasher: Hash, tag: string) => hasher.update(`</${tag}>`),\n str: (hasher: Hash, stringValue: string) => {\n // length prefix to avoid ambiguity: len#value\n hasher.update(`${stringValue.length}#`);\n hasher.update(stringValue);\n },\n num: (hasher: Hash, numberValue: number) =>\n hasher.update(\n Number.isNaN(numberValue)\n ? 'NaN'\n : numberValue === Infinity\n ? 'Inf'\n : numberValue === -Infinity\n ? '-Inf'\n : String(numberValue)\n ),\n big: (hasher: Hash, bigintValue: bigint) =>\n hasher.update(bigintValue.toString(10)),\n bool: (hasher: Hash, booleanValue: boolean) =>\n hasher.update(booleanValue ? '1' : '0'),\n};\n\n/** ------------------- Canonical, streaming hasher ------------------- **/\n\ntype Seen = WeakSet<object>;\n\n/**\n * Streams a canonical representation of `value` into `hasher` without\n * constructing large intermediate strings. Objects/Maps/Sets are normalized.\n */\nconst stableHashValue = (hasher: Hash, value: unknown, seen: Seen): void => {\n const valueType = typeof value;\n\n if (value === null) {\n token.start(hasher, 'null');\n token.end(hasher, 'null');\n return;\n }\n\n if (valueType === 'undefined') {\n token.start(hasher, 'undef');\n token.end(hasher, 'undef');\n return;\n }\n\n if (valueType === 'number') {\n token.start(hasher, 'num');\n token.num(hasher, value as number);\n token.end(hasher, 'num');\n return;\n }\n\n if (valueType === 'bigint') {\n token.start(hasher, 'big');\n token.big(hasher, value as bigint);\n token.end(hasher, 'big');\n return;\n }\n\n if (valueType === 'boolean') {\n token.start(hasher, 'bool');\n token.bool(hasher, value as boolean);\n token.end(hasher, 'bool');\n return;\n }\n\n if (valueType === 'string') {\n token.start(hasher, 'str');\n token.str(hasher, value as string);\n token.end(hasher, 'str');\n return;\n }\n\n if (valueType === 'symbol') {\n token.start(hasher, 'sym');\n token.str(hasher, String(value));\n token.end(hasher, 'sym');\n return;\n }\n\n if (valueType === 'function') {\n // Stable-ish fingerprint: name and arity (avoid source text).\n const functionValue = value as Function;\n token.start(hasher, 'fn');\n token.str(hasher, functionValue.name ?? '');\n token.sep(hasher);\n token.num(hasher, functionValue.length);\n token.end(hasher, 'fn');\n return;\n }\n\n // Arrays and typed arrays\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n token.start(hasher, 'arr');\n token.str(hasher, 'Circular');\n token.end(hasher, 'arr');\n return;\n }\n seen.add(value);\n token.start(hasher, 'arr');\n for (let i = 0; i < value.length; i++) {\n token.sep(hasher);\n stableHashValue(hasher, value[i], seen);\n }\n token.end(hasher, 'arr');\n seen.delete(value);\n return;\n }\n\n // Node/Builtins\n if (value instanceof Date) {\n token.start(hasher, 'date');\n token.str(hasher, (value as Date).toISOString());\n token.end(hasher, 'date');\n return;\n }\n\n if (value instanceof RegExp) {\n const regex = value as RegExp;\n token.start(hasher, 're');\n token.str(hasher, regex.source);\n token.sep(hasher);\n token.str(hasher, regex.flags);\n token.end(hasher, 're');\n return;\n }\n\n if (value instanceof Set) {\n const setValue = value as Set<unknown>;\n if (seen.has(setValue)) {\n token.start(hasher, 'set');\n token.str(hasher, 'Circular');\n token.end(hasher, 'set');\n return;\n }\n seen.add(setValue);\n // Normalize by item fingerprints (strings) to sort deterministically.\n const items: string[] = [];\n for (const v of setValue) items.push(stableStringify(v)); // small, bounded use of stringify\n items.sort();\n token.start(hasher, 'set');\n for (const item of items) {\n token.sep(hasher);\n token.str(hasher, item);\n }\n token.end(hasher, 'set');\n seen.delete(setValue);\n return;\n }\n\n if (value instanceof Map) {\n const mapObject = value as Map<unknown, unknown>;\n if (seen.has(mapObject)) {\n token.start(hasher, 'map');\n token.str(hasher, 'Circular');\n token.end(hasher, 'map');\n return;\n }\n seen.add(mapObject);\n // Normalize by sorted key fingerprints.\n const entries: Array<[string, unknown]> = [];\n for (const [k, v] of mapObject.entries())\n entries.push([stableStringify(k), v]);\n entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n token.start(hasher, 'map');\n for (const [keyFingerprint, entryValue] of entries) {\n token.sep(hasher);\n token.str(hasher, keyFingerprint);\n token.sep(hasher);\n stableHashValue(hasher, entryValue, seen);\n }\n token.end(hasher, 'map');\n seen.delete(mapObject);\n return;\n }\n\n // ArrayBuffer & typed arrays\n if (ArrayBuffer.isView(value)) {\n const view = value as ArrayBufferView;\n token.start(hasher, 'typed');\n token.str(hasher, Object.getPrototypeOf(view).constructor.name);\n token.sep(hasher);\n hasher.update(Buffer.from(view.buffer, view.byteOffset, view.byteLength));\n token.end(hasher, 'typed');\n return;\n }\n if (value instanceof ArrayBuffer) {\n const buffer = Buffer.from(value as ArrayBuffer);\n token.start(hasher, 'ab');\n hasher.update(buffer);\n token.end(hasher, 'ab');\n return;\n }\n\n // URL\n if (typeof URL !== 'undefined' && value instanceof URL) {\n token.start(hasher, 'url');\n token.str(hasher, (value as URL).toString());\n token.end(hasher, 'url');\n return;\n }\n\n // Errors\n if (value instanceof Error) {\n const errorValue = value as Error;\n token.start(hasher, 'err');\n token.str(hasher, errorValue.name || '');\n token.sep(hasher);\n token.str(hasher, errorValue.message || '');\n token.sep(hasher);\n token.str(hasher, errorValue.stack || '');\n token.end(hasher, 'err');\n return;\n }\n\n // Generic objects\n if (valueType === 'object') {\n const objectValue = value as Record<string, unknown>;\n if (seen.has(objectValue)) {\n token.start(hasher, 'obj');\n token.str(hasher, 'Circular');\n token.end(hasher, 'obj');\n return;\n }\n seen.add(objectValue);\n\n const keys = Object.keys(objectValue).sort();\n token.start(hasher, 'obj');\n for (const key of keys) {\n token.sep(hasher);\n token.str(hasher, key);\n token.sep(hasher);\n stableHashValue(hasher, (objectValue as any)[key], seen);\n }\n token.end(hasher, 'obj');\n\n seen.delete(objectValue);\n return;\n }\n\n // Fallback\n token.start(hasher, 'other');\n token.str(hasher, String(value));\n token.end(hasher, 'other');\n};\n\n/** Public stringify kept for convenience / debugging (now faster & broader). */\nexport const stableStringify = (\n value: unknown,\n _stack = new WeakSet<object>()\n): string => {\n const hasher = createHash(HASH_ALGORITHM);\n stableHashValue(hasher, value, _stack);\n return toBase64Url(hasher.digest());\n};\n\n/** Compute a compact, stable id for arbitrary key tuples. */\nconst computeKeyId = (keyParts: unknown[]): string => {\n const h = createHash(HASH_ALGORITHM);\n token.start(h, 'keys');\n for (let i = 0; i < keyParts.length; i++) {\n token.sep(h);\n stableHashValue(h, keyParts[i], new WeakSet());\n }\n token.end(h, 'keys');\n return toBase64Url(h.digest());\n};\n\n/** ------------------------- In-memory cache ------------------------- **/\n\ntype CacheKey = unknown;\nconst cacheMap = new Map<string, any>();\n\nexport const getCache = <T>(...key: CacheKey[]): T | undefined => {\n return cacheMap.get(computeKeyId(key));\n};\n\ntype CacheSetArgs<T> = [...keys: CacheKey[], value: T];\n\nexport const setCache = <T>(...args: CacheSetArgs<T>): void => {\n const value = args[args.length - 1] as T;\n const key = args.slice(0, -1) as CacheKey[];\n cacheMap.set(computeKeyId(key), value);\n};\n\nexport const clearCache = (idOrKey: string): void => {\n // Accept either our computed id or a legacy string id the caller already computed.\n cacheMap.delete(idOrKey);\n};\n\nexport const clearAllCache = (): void => {\n cacheMap.clear();\n};\n\nexport const cache = {\n get: getCache,\n set: setCache,\n clear: clearCache,\n};\n\n/** ------------------------- Persistence layer ------------------------- **/\n\ntype LocalCacheOptions = {\n /** Preferred new option name */\n persistent?: boolean;\n /** Time-to-live in ms; if expired, disk entry is ignored. */\n ttlMs?: number;\n /** Max age in ms based on stored creation timestamp; invalidates on exceed. */\n maxTimeMs?: number;\n /** Optional namespace to separate different logical caches. */\n namespace?: string;\n /** Gzip values on disk (on by default for blobs > 1KB). */\n compress?: boolean;\n};\n\nconst DEFAULTS: Required<Pick<LocalCacheOptions, 'compress'>> = {\n compress: true,\n};\n\nconst ensureDir = async (dir: string) => {\n await mkdir(dir, { recursive: true });\n};\n\nconst atomicWriteFile = async (file: string, data: Buffer) => {\n const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await writeFile(tmp, data);\n await rename(tmp, file);\n};\n\nconst shouldUseCompression = (buf: Buffer, force?: boolean) =>\n force === true || (force !== false && buf.byteLength > 1024);\n\n/** Derive on-disk path from config dir + namespace + key id. */\nconst cachePath = (cacheDir: string, id: string, ns?: string) =>\n join(cacheDir, ns ? join(ns, id) : id);\n\n/** ------------------------- Local cache facade ------------------------- **/\n\nexport const localCache = (\n intlayerConfig: IntlayerConfig,\n keys: CacheKey[],\n options?: LocalCacheOptions\n) => {\n const { cacheDir } = intlayerConfig.content;\n const buildCacheEnabled = intlayerConfig.build.cache ?? true;\n const persistent =\n options?.persistent === true ||\n (typeof options?.persistent === 'undefined' && buildCacheEnabled);\n\n const { compress, ttlMs, maxTimeMs, namespace } = {\n ...DEFAULTS,\n ...options,\n };\n\n // single stable id for this key tuple (works for both memory & disk)\n const id = computeKeyId(keys);\n const filePath = cachePath(cacheDir, id, namespace);\n\n const readFromDisk = async (): Promise<unknown | undefined> => {\n try {\n const statValue = await stat(filePath).catch(() => undefined);\n\n if (!statValue) return undefined;\n\n if (typeof ttlMs === 'number' && ttlMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > ttlMs) return undefined;\n }\n let raw = await readFile(filePath);\n // header: 1 byte flag (0x00 raw, 0x01 gzip)\n const flag = raw[0];\n\n raw = raw.subarray(1);\n\n const payload = flag === 0x01 ? gunzipSync(raw) : raw;\n const deserialized = deserialize(payload) as unknown;\n\n let value: unknown;\n const maybeObj = deserialized as Record<string, unknown> | null;\n const isEnvelope =\n !!maybeObj &&\n typeof maybeObj === 'object' &&\n typeof (maybeObj as any).v === 'string' &&\n typeof (maybeObj as any).ts === 'number' &&\n Object.hasOwn(maybeObj, 'd');\n\n if (isEnvelope) {\n const entry = maybeObj as { v: string; ts: number; d: unknown };\n\n if (entry.v !== configPackageJson.version) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - entry.ts;\n if (age > maxTimeMs) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n }\n\n value = entry.d;\n } else {\n // Backward compatibility: old entries had raw serialized value.\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > maxTimeMs) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n }\n value = deserialized;\n }\n\n // hydrate memory cache as well\n cacheMap.set(id, value);\n return value;\n } catch {\n return undefined;\n }\n };\n\n const writeToDisk = async (value: unknown) => {\n try {\n await ensureDir(dirname(filePath));\n const envelope = {\n v: configPackageJson.version,\n ts: Date.now(),\n d: value,\n } as const;\n const payload = Buffer.from(serialize(envelope));\n\n const gz = shouldUseCompression(payload, compress)\n ? gzipSync(payload)\n : payload;\n\n // prepend a 1-byte header indicating compression\n const buf = Buffer.concat([\n Buffer.from([gz === payload ? 0x00 : 0x01]),\n gz,\n ]);\n\n await atomicWriteFile(filePath, buf);\n } catch {\n // swallow disk errors for cache writes\n }\n };\n\n return {\n /** In-memory first, then disk (if enabled), otherwise undefined. */\n get: async <T>(): Promise<T | undefined> => {\n const mem = cacheMap.get(id);\n\n if (mem !== undefined) return mem as T;\n\n if (persistent && buildCacheEnabled) {\n return (await readFromDisk()) as T | undefined;\n }\n return undefined;\n },\n /** Sets in-memory (always) and persists to disk if enabled. */\n set: async (value: unknown): Promise<void> => {\n cacheMap.set(id, value);\n\n if (persistent && buildCacheEnabled) {\n await writeToDisk(value);\n }\n },\n /** Clears only this entry from memory and disk. */\n clear: async (): Promise<void> => {\n cacheMap.delete(id);\n\n try {\n await unlink(filePath);\n } catch {}\n },\n /** Clears ALL cached entries (memory Map and entire cacheDir namespace if persistent). */\n clearAll: async (): Promise<void> => {\n clearAllCache();\n if (persistent && buildCacheEnabled) {\n // remove only the current namespace (if provided), else the root dir\n const base = namespace ? join(cacheDir, namespace) : cacheDir;\n\n try {\n await rm(base, { recursive: true, force: true });\n } catch {}\n\n try {\n await mkdir(base, { recursive: true });\n } catch {}\n }\n },\n /** Expose the computed id (useful if you want to key other structures). */\n isValid: async (): Promise<boolean> => {\n const mem = cacheMap.get(id);\n if (mem !== undefined) return true;\n\n // If persistence is disabled or build cache disabled, only memory can be valid\n if (!persistent || !buildCacheEnabled) return false;\n\n try {\n const statValue = await stat(filePath).catch(() => undefined);\n if (!statValue) return false;\n\n if (typeof ttlMs === 'number' && ttlMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > ttlMs) return false;\n }\n\n let raw = await readFile(filePath);\n const flag = raw[0];\n raw = raw.subarray(1);\n const payload = flag === 0x01 ? gunzipSync(raw) : raw;\n const deserialized = deserialize(payload) as unknown;\n\n const maybeObj = deserialized as Record<string, unknown> | null;\n const isEnvelope =\n !!maybeObj &&\n typeof maybeObj === 'object' &&\n typeof (maybeObj as any).v === 'string' &&\n typeof (maybeObj as any).ts === 'number' &&\n Object.hasOwn(maybeObj, 'd');\n\n if (isEnvelope) {\n const entry = maybeObj as { v: string; ts: number; d: unknown };\n if (entry.v !== configPackageJson.version) return false;\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - entry.ts;\n if (age > maxTimeMs) return false;\n }\n return true;\n }\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > maxTimeMs) return false;\n }\n return true;\n } catch {\n return false;\n }\n },\n /** Expose the computed id (useful if you want to key other structures). */\n id,\n /** Expose the absolute file path for debugging. */\n filePath,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache.mjs","names":["items: string[]","entries: Array<[string, unknown]>","DEFAULTS: Required<Pick<LocalCacheOptions, 'compress'>>","value: unknown","configPackageJson","envelope: CacheEnvelope"],"sources":["../../../src/utils/cache.ts"],"sourcesContent":["import { createHash, type Hash } from 'node:crypto';\nimport {\n mkdir,\n readFile,\n rename,\n rm,\n stat,\n unlink,\n writeFile,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { deserialize, serialize } from 'node:v8';\nimport { gunzipSync, gzipSync } from 'node:zlib';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport configPackageJson from '@intlayer/types/package.json' with {\n type: 'json',\n};\n\n/** ------------------------- Utilities ------------------------- **/\n\n/** Prefer a fast non-crypto hash if available, then fast crypto, then sha256. */\nconst pickHashAlgorithm = (): string => {\n try {\n // Node 20+ supports xxhash64 (very fast). We feature-detect at module load.\n createHash('xxhash64').update('test').digest();\n return 'xxhash64';\n } catch {}\n try {\n // sha1 is faster than sha256 and sufficient for cache keys.\n createHash('sha1').update('test').digest();\n return 'sha1';\n } catch {}\n\n return 'sha256';\n};\nconst HASH_ALGORITHM = pickHashAlgorithm();\n\n/** Base64url without padding for compact, file-system-safe ids. */\nconst toBase64Url = (buffer: Buffer): string =>\n buffer\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/g, '');\n\n/** Token helpers to minimize collisions while streaming to the hasher. */\nconst token = {\n start: (hasher: Hash, tag: string) => hasher.update(`<${tag}>`),\n sep: (hasher: Hash) => hasher.update('|'),\n end: (hasher: Hash, tag: string) => hasher.update(`</${tag}>`),\n str: (hasher: Hash, stringValue: string) => {\n // length prefix to avoid ambiguity: len#value\n hasher.update(`${stringValue.length}#`);\n hasher.update(stringValue);\n },\n num: (hasher: Hash, numberValue: number) =>\n hasher.update(\n Number.isNaN(numberValue)\n ? 'NaN'\n : numberValue === Infinity\n ? 'Inf'\n : numberValue === -Infinity\n ? '-Inf'\n : String(numberValue)\n ),\n big: (hasher: Hash, bigintValue: bigint) =>\n hasher.update(bigintValue.toString(10)),\n bool: (hasher: Hash, booleanValue: boolean) =>\n hasher.update(booleanValue ? '1' : '0'),\n};\n\n/** ------------------- Canonical, streaming hasher ------------------- **/\n\ntype Seen = WeakSet<object>;\n\n/**\n * Streams a canonical representation of `value` into `hasher` without\n * constructing large intermediate strings. Objects/Maps/Sets are normalized.\n */\nconst stableHashValue = (hasher: Hash, value: unknown, seen: Seen): void => {\n const valueType = typeof value;\n\n if (value === null) {\n token.start(hasher, 'null');\n token.end(hasher, 'null');\n return;\n }\n\n if (valueType === 'undefined') {\n token.start(hasher, 'undef');\n token.end(hasher, 'undef');\n return;\n }\n\n if (valueType === 'number') {\n token.start(hasher, 'num');\n token.num(hasher, value as number);\n token.end(hasher, 'num');\n return;\n }\n\n if (valueType === 'bigint') {\n token.start(hasher, 'big');\n token.big(hasher, value as bigint);\n token.end(hasher, 'big');\n return;\n }\n\n if (valueType === 'boolean') {\n token.start(hasher, 'bool');\n token.bool(hasher, value as boolean);\n token.end(hasher, 'bool');\n return;\n }\n\n if (valueType === 'string') {\n token.start(hasher, 'str');\n token.str(hasher, value as string);\n token.end(hasher, 'str');\n return;\n }\n\n if (valueType === 'symbol') {\n token.start(hasher, 'sym');\n token.str(hasher, String(value));\n token.end(hasher, 'sym');\n return;\n }\n\n if (valueType === 'function') {\n // Stable-ish fingerprint: name and arity (avoid source text).\n const functionValue = value as Function;\n token.start(hasher, 'fn');\n token.str(hasher, functionValue.name ?? '');\n token.sep(hasher);\n token.num(hasher, functionValue.length);\n token.end(hasher, 'fn');\n return;\n }\n\n // Arrays and typed arrays\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n token.start(hasher, 'arr');\n token.str(hasher, 'Circular');\n token.end(hasher, 'arr');\n return;\n }\n seen.add(value);\n token.start(hasher, 'arr');\n for (let i = 0; i < value.length; i++) {\n token.sep(hasher);\n stableHashValue(hasher, value[i], seen);\n }\n token.end(hasher, 'arr');\n seen.delete(value);\n return;\n }\n\n // Node/Builtins\n if (value instanceof Date) {\n token.start(hasher, 'date');\n token.str(hasher, (value as Date).toISOString());\n token.end(hasher, 'date');\n return;\n }\n\n if (value instanceof RegExp) {\n const regex = value as RegExp;\n token.start(hasher, 're');\n token.str(hasher, regex.source);\n token.sep(hasher);\n token.str(hasher, regex.flags);\n token.end(hasher, 're');\n return;\n }\n\n if (value instanceof Set) {\n const setValue = value as Set<unknown>;\n if (seen.has(setValue)) {\n token.start(hasher, 'set');\n token.str(hasher, 'Circular');\n token.end(hasher, 'set');\n return;\n }\n seen.add(setValue);\n // Normalize by item fingerprints (strings) to sort deterministically.\n const items: string[] = [];\n for (const v of setValue) items.push(stableStringify(v)); // small, bounded use of stringify\n items.sort();\n token.start(hasher, 'set');\n for (const item of items) {\n token.sep(hasher);\n token.str(hasher, item);\n }\n token.end(hasher, 'set');\n seen.delete(setValue);\n return;\n }\n\n if (value instanceof Map) {\n const mapObject = value as Map<unknown, unknown>;\n if (seen.has(mapObject)) {\n token.start(hasher, 'map');\n token.str(hasher, 'Circular');\n token.end(hasher, 'map');\n return;\n }\n seen.add(mapObject);\n // Normalize by sorted key fingerprints.\n const entries: Array<[string, unknown]> = [];\n for (const [k, v] of mapObject.entries())\n entries.push([stableStringify(k), v]);\n entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n token.start(hasher, 'map');\n for (const [keyFingerprint, entryValue] of entries) {\n token.sep(hasher);\n token.str(hasher, keyFingerprint);\n token.sep(hasher);\n stableHashValue(hasher, entryValue, seen);\n }\n token.end(hasher, 'map');\n seen.delete(mapObject);\n return;\n }\n\n // ArrayBuffer & typed arrays\n if (ArrayBuffer.isView(value)) {\n const view = value as ArrayBufferView;\n token.start(hasher, 'typed');\n token.str(hasher, Object.getPrototypeOf(view).constructor.name);\n token.sep(hasher);\n hasher.update(Buffer.from(view.buffer, view.byteOffset, view.byteLength));\n token.end(hasher, 'typed');\n return;\n }\n if (value instanceof ArrayBuffer) {\n const buffer = Buffer.from(value as ArrayBuffer);\n token.start(hasher, 'ab');\n hasher.update(buffer);\n token.end(hasher, 'ab');\n return;\n }\n\n // URL\n if (typeof URL !== 'undefined' && value instanceof URL) {\n token.start(hasher, 'url');\n token.str(hasher, (value as URL).toString());\n token.end(hasher, 'url');\n return;\n }\n\n // Errors\n if (value instanceof Error) {\n const errorValue = value as Error;\n token.start(hasher, 'err');\n token.str(hasher, errorValue.name || '');\n token.sep(hasher);\n token.str(hasher, errorValue.message || '');\n token.sep(hasher);\n token.str(hasher, errorValue.stack || '');\n token.end(hasher, 'err');\n return;\n }\n\n // Generic objects\n if (valueType === 'object') {\n const objectValue = value as Record<string, unknown>;\n if (seen.has(objectValue)) {\n token.start(hasher, 'obj');\n token.str(hasher, 'Circular');\n token.end(hasher, 'obj');\n return;\n }\n seen.add(objectValue);\n\n const keys = Object.keys(objectValue).sort();\n token.start(hasher, 'obj');\n for (const key of keys) {\n token.sep(hasher);\n token.str(hasher, key);\n token.sep(hasher);\n stableHashValue(hasher, (objectValue as any)[key], seen);\n }\n token.end(hasher, 'obj');\n\n seen.delete(objectValue);\n return;\n }\n\n // Fallback\n token.start(hasher, 'other');\n token.str(hasher, String(value));\n token.end(hasher, 'other');\n};\n\n/** Public stringify kept for convenience / debugging (now faster & broader). */\nexport const stableStringify = (\n value: unknown,\n _stack = new WeakSet<object>()\n): string => {\n const hasher = createHash(HASH_ALGORITHM);\n stableHashValue(hasher, value, _stack);\n return toBase64Url(hasher.digest());\n};\n\n/** Compute a compact, stable id for arbitrary key tuples. */\nconst computeKeyId = (keyParts: unknown[]): string => {\n const h = createHash(HASH_ALGORITHM);\n token.start(h, 'keys');\n for (let i = 0; i < keyParts.length; i++) {\n token.sep(h);\n stableHashValue(h, keyParts[i], new WeakSet());\n }\n token.end(h, 'keys');\n return toBase64Url(h.digest());\n};\n\n/** ------------------------- In-memory cache ------------------------- **/\n\ntype CacheKey = unknown;\nconst cacheMap = new Map<string, any>();\n\nexport const getCache = <T>(...key: CacheKey[]): T | undefined => {\n return cacheMap.get(computeKeyId(key));\n};\n\ntype CacheSetArgs<T> = [...keys: CacheKey[], value: T];\n\nexport const setCache = <T>(...args: CacheSetArgs<T>): void => {\n const value = args[args.length - 1] as T;\n const key = args.slice(0, -1) as CacheKey[];\n cacheMap.set(computeKeyId(key), value);\n};\n\nexport const clearCache = (idOrKey: string): void => {\n // Accept either our computed id or a legacy string id the caller already computed.\n cacheMap.delete(idOrKey);\n};\n\nexport const clearAllCache = (): void => {\n cacheMap.clear();\n};\n\nexport const memoryCache = {\n get: getCache,\n set: setCache,\n clear: clearCache,\n};\n\n/** ------------------------- Persistence layer ------------------------- **/\n\n/** Cache envelope structure stored on disk */\ntype CacheEnvelope = {\n /** Version of the config package (for cache invalidation) */\n version: string;\n /** Timestamp when the cache entry was created (in milliseconds) */\n timestamp: number;\n /** Data payload (the actual cached value) */\n data: unknown;\n};\n\ntype LocalCacheOptions = {\n /** Preferred new option name */\n persistent?: boolean;\n /** Time-to-live in ms; if expired, disk entry is ignored. */\n ttlMs?: number;\n /** Max age in ms based on stored creation timestamp; invalidates on exceed. */\n maxTimeMs?: number;\n /** Optional namespace to separate different logical caches. */\n namespace?: string;\n /** Gzip values on disk (on by default for blobs > 1KB). */\n compress?: boolean;\n};\n\nconst DEFAULTS: Required<Pick<LocalCacheOptions, 'compress'>> = {\n compress: true,\n};\n\nconst ensureDir = async (dir: string) => {\n await mkdir(dir, { recursive: true });\n};\n\nconst atomicWriteFile = async (file: string, data: Buffer) => {\n const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await writeFile(tmp, data);\n await rename(tmp, file);\n};\n\nconst shouldUseCompression = (buf: Buffer, force?: boolean) =>\n force === true || (force !== false && buf.byteLength > 1024);\n\n/** Derive on-disk path from config dir + namespace + key id. */\nconst cachePath = (cacheDir: string, id: string, ns?: string) =>\n join(cacheDir, ns ? join(ns, id) : id);\n\n/** ------------------------- Local cache facade ------------------------- **/\n\nexport const diskCache = (\n intlayerConfig: IntlayerConfig,\n keys: CacheKey[],\n options?: LocalCacheOptions\n) => {\n const { cacheDir } = intlayerConfig.content;\n const buildCacheEnabled = intlayerConfig.build.cache ?? true;\n const persistent =\n options?.persistent === true ||\n (typeof options?.persistent === 'undefined' && buildCacheEnabled);\n\n const { compress, ttlMs, maxTimeMs, namespace } = {\n ...DEFAULTS,\n ...options,\n };\n\n // single stable id for this key tuple (works for both memory & disk)\n const id = computeKeyId(keys);\n const filePath = cachePath(cacheDir, id, namespace);\n\n const readFromDisk = async (): Promise<unknown | undefined> => {\n try {\n const statValue = await stat(filePath).catch(() => undefined);\n\n if (!statValue) return undefined;\n\n if (typeof ttlMs === 'number' && ttlMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > ttlMs) return undefined;\n }\n let raw = await readFile(filePath);\n // header: 1 byte flag (0x00 raw, 0x01 gzip)\n const flag = raw[0];\n\n raw = raw.subarray(1);\n\n const payload = flag === 0x01 ? gunzipSync(raw) : raw;\n const deserialized = deserialize(payload) as unknown;\n\n let value: unknown;\n const maybeObj = deserialized as Record<string, unknown> | null;\n const isEnvelope =\n !!maybeObj &&\n typeof maybeObj === 'object' &&\n typeof (maybeObj as any).version === 'string' &&\n typeof (maybeObj as any).timestamp === 'number' &&\n Object.hasOwn(maybeObj, 'data');\n\n if (isEnvelope) {\n const entry = maybeObj as CacheEnvelope;\n\n if (entry.version !== configPackageJson.version) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - entry.timestamp;\n if (age > maxTimeMs) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n }\n\n value = entry.data;\n } else {\n // Backward compatibility: old entries had raw serialized value.\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > maxTimeMs) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n }\n value = deserialized;\n }\n\n // hydrate memory cache as well\n cacheMap.set(id, value);\n return value;\n } catch {\n return undefined;\n }\n };\n\n const writeToDisk = async (value: unknown) => {\n try {\n await ensureDir(dirname(filePath));\n const envelope: CacheEnvelope = {\n version: configPackageJson.version,\n timestamp: Date.now(),\n data: value,\n };\n const payload = Buffer.from(serialize(envelope));\n\n const gz = shouldUseCompression(payload, compress)\n ? gzipSync(payload)\n : payload;\n\n // prepend a 1-byte header indicating compression\n const buf = Buffer.concat([\n Buffer.from([gz === payload ? 0x00 : 0x01]),\n gz,\n ]);\n\n await atomicWriteFile(filePath, buf);\n } catch {\n // swallow disk errors for cache writes\n }\n };\n\n return {\n /** In-memory first, then disk (if enabled), otherwise undefined. */\n get: async <T>(): Promise<T | undefined> => {\n const mem = cacheMap.get(id);\n\n if (mem !== undefined) return mem as T;\n\n if (persistent && buildCacheEnabled) {\n return (await readFromDisk()) as T | undefined;\n }\n return undefined;\n },\n /** Sets in-memory (always) and persists to disk if enabled. */\n set: async (value: unknown): Promise<void> => {\n cacheMap.set(id, value);\n\n if (persistent && buildCacheEnabled) {\n await writeToDisk(value);\n }\n },\n /** Clears only this entry from memory and disk. */\n clear: async (): Promise<void> => {\n cacheMap.delete(id);\n\n try {\n await unlink(filePath);\n } catch {}\n },\n /** Clears ALL cached entries (memory Map and entire cacheDir namespace if persistent). */\n clearAll: async (): Promise<void> => {\n clearAllCache();\n if (persistent && buildCacheEnabled) {\n // remove only the current namespace (if provided), else the root dir\n const base = namespace ? join(cacheDir, namespace) : cacheDir;\n\n try {\n await rm(base, { recursive: true, force: true });\n } catch {}\n\n try {\n await mkdir(base, { recursive: true });\n } catch {}\n }\n },\n /** Expose the computed id (useful if you want to key other structures). */\n isValid: async (): Promise<boolean> => {\n const cachedValue = cacheMap.get(id);\n if (cachedValue !== undefined) return true;\n\n // If persistence is disabled or build cache disabled, only memory can be valid\n if (!persistent || !buildCacheEnabled) return false;\n\n try {\n const statValue = await stat(filePath).catch(() => undefined);\n if (!statValue) return false;\n\n if (typeof ttlMs === 'number' && ttlMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > ttlMs) return false;\n }\n\n let raw = await readFile(filePath);\n const flag = raw[0];\n raw = raw.subarray(1);\n const payload = flag === 0x01 ? gunzipSync(raw) : raw;\n const deserialized = deserialize(payload) as unknown;\n\n const maybeObj = deserialized as Record<string, unknown> | null;\n const isEnvelope =\n !!maybeObj &&\n typeof maybeObj === 'object' &&\n typeof maybeObj.version === 'string' &&\n typeof maybeObj.timestamp === 'number' &&\n Object.hasOwn(maybeObj, 'data');\n\n if (isEnvelope) {\n const entry = maybeObj as CacheEnvelope;\n if (entry.version !== configPackageJson.version) return false;\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - entry.timestamp;\n if (age > maxTimeMs) return false;\n }\n return true;\n }\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > maxTimeMs) return false;\n }\n return true;\n } catch {\n return false;\n }\n },\n /** Expose the computed id (useful if you want to key other structures). */\n id,\n /** Expose the absolute file path for debugging. */\n filePath,\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { clearAllCache, computeKeyId } from "./cacheMemory.mjs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import packageJson from "@intlayer/types/package.json" with { type: "json" };
|
|
4
|
+
import { mkdir, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { deserialize, serialize } from "node:v8";
|
|
6
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
7
|
+
|
|
8
|
+
//#region src/utils/cacheDisk.ts
|
|
9
|
+
const DEFAULTS = { compress: true };
|
|
10
|
+
const ensureDir = async (dir) => {
|
|
11
|
+
await mkdir(dir, { recursive: true });
|
|
12
|
+
};
|
|
13
|
+
const atomicWriteFile = async (file, data) => {
|
|
14
|
+
const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
15
|
+
await writeFile(tmp, data);
|
|
16
|
+
await rename(tmp, file);
|
|
17
|
+
};
|
|
18
|
+
const shouldUseCompression = (buf, force) => force === true || force !== false && buf.byteLength > 1024;
|
|
19
|
+
/** Derive on-disk path from config dir + namespace + key id. */
|
|
20
|
+
const cachePath = (cacheDir, id, ns) => join(cacheDir, ns ? join(ns, id) : id);
|
|
21
|
+
/** ------------------------- Local cache facade ------------------------- **/
|
|
22
|
+
const cacheMap = /* @__PURE__ */ new Map();
|
|
23
|
+
const cacheDisk = (intlayerConfig, keys, options) => {
|
|
24
|
+
const { cacheDir } = intlayerConfig.content;
|
|
25
|
+
const buildCacheEnabled = intlayerConfig.build.cache ?? true;
|
|
26
|
+
const persistent = options?.persistent === true || typeof options?.persistent === "undefined" && buildCacheEnabled;
|
|
27
|
+
const { compress, ttlMs, maxTimeMs, namespace } = {
|
|
28
|
+
...DEFAULTS,
|
|
29
|
+
...options
|
|
30
|
+
};
|
|
31
|
+
const id = computeKeyId(keys);
|
|
32
|
+
const filePath = cachePath(cacheDir, id, namespace);
|
|
33
|
+
const readFromDisk = async () => {
|
|
34
|
+
try {
|
|
35
|
+
const statValue = await stat(filePath).catch(() => void 0);
|
|
36
|
+
if (!statValue) return void 0;
|
|
37
|
+
if (typeof ttlMs === "number" && ttlMs > 0) {
|
|
38
|
+
if (Date.now() - statValue.mtimeMs > ttlMs) return void 0;
|
|
39
|
+
}
|
|
40
|
+
let raw = await readFile(filePath);
|
|
41
|
+
const flag = raw[0];
|
|
42
|
+
raw = raw.subarray(1);
|
|
43
|
+
const deserialized = deserialize(flag === 1 ? gunzipSync(raw) : raw);
|
|
44
|
+
let value;
|
|
45
|
+
const maybeObj = deserialized;
|
|
46
|
+
if (!!maybeObj && typeof maybeObj === "object" && typeof maybeObj.version === "string" && typeof maybeObj.timestamp === "number" && Object.hasOwn(maybeObj, "data")) {
|
|
47
|
+
const entry = maybeObj;
|
|
48
|
+
if (entry.version !== packageJson.version) {
|
|
49
|
+
try {
|
|
50
|
+
await unlink(filePath);
|
|
51
|
+
} catch {}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (typeof maxTimeMs === "number" && maxTimeMs > 0) {
|
|
55
|
+
if (Date.now() - entry.timestamp > maxTimeMs) {
|
|
56
|
+
try {
|
|
57
|
+
await unlink(filePath);
|
|
58
|
+
} catch {}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
value = entry.data;
|
|
63
|
+
} else {
|
|
64
|
+
if (typeof maxTimeMs === "number" && maxTimeMs > 0) {
|
|
65
|
+
if (Date.now() - statValue.mtimeMs > maxTimeMs) {
|
|
66
|
+
try {
|
|
67
|
+
await unlink(filePath);
|
|
68
|
+
} catch {}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
value = deserialized;
|
|
73
|
+
}
|
|
74
|
+
cacheMap.set(id, value);
|
|
75
|
+
return value;
|
|
76
|
+
} catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const writeToDisk = async (value) => {
|
|
81
|
+
try {
|
|
82
|
+
await ensureDir(dirname(filePath));
|
|
83
|
+
const envelope = {
|
|
84
|
+
version: packageJson.version,
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
data: value
|
|
87
|
+
};
|
|
88
|
+
const payload = Buffer.from(serialize(envelope));
|
|
89
|
+
const gz = shouldUseCompression(payload, compress) ? gzipSync(payload) : payload;
|
|
90
|
+
await atomicWriteFile(filePath, Buffer.concat([Buffer.from([gz === payload ? 0 : 1]), gz]));
|
|
91
|
+
} catch {}
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
get: async () => {
|
|
95
|
+
const mem = cacheMap.get(id);
|
|
96
|
+
if (mem !== void 0) return mem;
|
|
97
|
+
if (persistent && buildCacheEnabled) return await readFromDisk();
|
|
98
|
+
},
|
|
99
|
+
set: async (value) => {
|
|
100
|
+
cacheMap.set(id, value);
|
|
101
|
+
if (persistent && buildCacheEnabled) await writeToDisk(value);
|
|
102
|
+
},
|
|
103
|
+
clear: async () => {
|
|
104
|
+
cacheMap.delete(id);
|
|
105
|
+
try {
|
|
106
|
+
await unlink(filePath);
|
|
107
|
+
} catch {}
|
|
108
|
+
},
|
|
109
|
+
clearAll: async () => {
|
|
110
|
+
clearAllCache();
|
|
111
|
+
if (persistent && buildCacheEnabled) {
|
|
112
|
+
const base = namespace ? join(cacheDir, namespace) : cacheDir;
|
|
113
|
+
try {
|
|
114
|
+
await rm(base, {
|
|
115
|
+
recursive: true,
|
|
116
|
+
force: true
|
|
117
|
+
});
|
|
118
|
+
} catch {}
|
|
119
|
+
try {
|
|
120
|
+
await mkdir(base, { recursive: true });
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
isValid: async () => {
|
|
125
|
+
if (cacheMap.get(id) !== void 0) return true;
|
|
126
|
+
if (!persistent || !buildCacheEnabled) return false;
|
|
127
|
+
try {
|
|
128
|
+
const statValue = await stat(filePath).catch(() => void 0);
|
|
129
|
+
if (!statValue) return false;
|
|
130
|
+
if (typeof ttlMs === "number" && ttlMs > 0) {
|
|
131
|
+
if (Date.now() - statValue.mtimeMs > ttlMs) return false;
|
|
132
|
+
}
|
|
133
|
+
let raw = await readFile(filePath);
|
|
134
|
+
const flag = raw[0];
|
|
135
|
+
raw = raw.subarray(1);
|
|
136
|
+
const maybeObj = deserialize(flag === 1 ? gunzipSync(raw) : raw);
|
|
137
|
+
if (!!maybeObj && typeof maybeObj === "object" && typeof maybeObj.version === "string" && typeof maybeObj.timestamp === "number" && Object.hasOwn(maybeObj, "data")) {
|
|
138
|
+
const entry = maybeObj;
|
|
139
|
+
if (entry.version !== packageJson.version) return false;
|
|
140
|
+
if (typeof maxTimeMs === "number" && maxTimeMs > 0) {
|
|
141
|
+
if (Date.now() - entry.timestamp > maxTimeMs) return false;
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (typeof maxTimeMs === "number" && maxTimeMs > 0) {
|
|
146
|
+
if (Date.now() - statValue.mtimeMs > maxTimeMs) return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
id,
|
|
154
|
+
filePath
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
export { cacheDisk };
|
|
160
|
+
//# sourceMappingURL=cacheDisk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheDisk.mjs","names":["DEFAULTS: Required<Pick<LocalCacheOptions, 'compress'>>","value: unknown","configPackageJson","envelope: CacheEnvelope"],"sources":["../../../src/utils/cacheDisk.ts"],"sourcesContent":["import {\n mkdir,\n readFile,\n rename,\n rm,\n stat,\n unlink,\n writeFile,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { deserialize, serialize } from 'node:v8';\nimport { gunzipSync, gzipSync } from 'node:zlib';\nimport type { IntlayerConfig } from '@intlayer/types';\nimport configPackageJson from '@intlayer/types/package.json' with {\n type: 'json',\n};\nimport { type CacheKey, clearAllCache, computeKeyId } from './cacheMemory';\n\n/** ------------------------- Persistence layer ------------------------- **/\n\n/** Cache envelope structure stored on disk */\ntype CacheEnvelope = {\n /** Version of the config package (for cache invalidation) */\n version: string;\n /** Timestamp when the cache entry was created (in milliseconds) */\n timestamp: number;\n /** Data payload (the actual cached value) */\n data: unknown;\n};\n\ntype LocalCacheOptions = {\n /** Preferred new option name */\n persistent?: boolean;\n /** Time-to-live in ms; if expired, disk entry is ignored. */\n ttlMs?: number;\n /** Max age in ms based on stored creation timestamp; invalidates on exceed. */\n maxTimeMs?: number;\n /** Optional namespace to separate different logical caches. */\n namespace?: string;\n /** Gzip values on disk (on by default for blobs > 1KB). */\n compress?: boolean;\n};\n\nconst DEFAULTS: Required<Pick<LocalCacheOptions, 'compress'>> = {\n compress: true,\n};\n\nconst ensureDir = async (dir: string) => {\n await mkdir(dir, { recursive: true });\n};\n\nconst atomicWriteFile = async (file: string, data: Buffer) => {\n const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;\n await writeFile(tmp, data);\n await rename(tmp, file);\n};\n\nconst shouldUseCompression = (buf: Buffer, force?: boolean) =>\n force === true || (force !== false && buf.byteLength > 1024);\n\n/** Derive on-disk path from config dir + namespace + key id. */\nconst cachePath = (cacheDir: string, id: string, ns?: string) =>\n join(cacheDir, ns ? join(ns, id) : id);\n\n/** ------------------------- Local cache facade ------------------------- **/\n\nconst cacheMap = new Map<string, any>();\n\nexport const cacheDisk = (\n intlayerConfig: IntlayerConfig,\n keys: CacheKey[],\n options?: LocalCacheOptions\n) => {\n const { cacheDir } = intlayerConfig.content;\n const buildCacheEnabled = intlayerConfig.build.cache ?? true;\n const persistent =\n options?.persistent === true ||\n (typeof options?.persistent === 'undefined' && buildCacheEnabled);\n\n const { compress, ttlMs, maxTimeMs, namespace } = {\n ...DEFAULTS,\n ...options,\n };\n\n // single stable id for this key tuple (works for both memory & disk)\n const id = computeKeyId(keys);\n const filePath = cachePath(cacheDir, id, namespace);\n\n const readFromDisk = async (): Promise<unknown | undefined> => {\n try {\n const statValue = await stat(filePath).catch(() => undefined);\n\n if (!statValue) return undefined;\n\n if (typeof ttlMs === 'number' && ttlMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > ttlMs) return undefined;\n }\n let raw = await readFile(filePath);\n // header: 1 byte flag (0x00 raw, 0x01 gzip)\n const flag = raw[0];\n\n raw = raw.subarray(1);\n\n const payload = flag === 0x01 ? gunzipSync(raw) : raw;\n const deserialized = deserialize(payload) as unknown;\n\n let value: unknown;\n const maybeObj = deserialized as Record<string, unknown> | null;\n const isEnvelope =\n !!maybeObj &&\n typeof maybeObj === 'object' &&\n typeof (maybeObj as any).version === 'string' &&\n typeof (maybeObj as any).timestamp === 'number' &&\n Object.hasOwn(maybeObj, 'data');\n\n if (isEnvelope) {\n const entry = maybeObj as CacheEnvelope;\n\n if (entry.version !== configPackageJson.version) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - entry.timestamp;\n if (age > maxTimeMs) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n }\n\n value = entry.data;\n } else {\n // Backward compatibility: old entries had raw serialized value.\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > maxTimeMs) {\n try {\n await unlink(filePath);\n } catch {}\n return undefined;\n }\n }\n value = deserialized;\n }\n\n // hydrate memory cache as well\n cacheMap.set(id, value);\n return value;\n } catch {\n return undefined;\n }\n };\n\n const writeToDisk = async (value: unknown) => {\n try {\n await ensureDir(dirname(filePath));\n const envelope: CacheEnvelope = {\n version: configPackageJson.version,\n timestamp: Date.now(),\n data: value,\n };\n const payload = Buffer.from(serialize(envelope));\n\n const gz = shouldUseCompression(payload, compress)\n ? gzipSync(payload)\n : payload;\n\n // prepend a 1-byte header indicating compression\n const buf = Buffer.concat([\n Buffer.from([gz === payload ? 0x00 : 0x01]),\n gz,\n ]);\n\n await atomicWriteFile(filePath, buf);\n } catch {\n // swallow disk errors for cache writes\n }\n };\n\n return {\n /** In-memory first, then disk (if enabled), otherwise undefined. */\n get: async <T>(): Promise<T | undefined> => {\n const mem = cacheMap.get(id);\n\n if (mem !== undefined) return mem as T;\n\n if (persistent && buildCacheEnabled) {\n return (await readFromDisk()) as T | undefined;\n }\n return undefined;\n },\n /** Sets in-memory (always) and persists to disk if enabled. */\n set: async (value: unknown): Promise<void> => {\n cacheMap.set(id, value);\n\n if (persistent && buildCacheEnabled) {\n await writeToDisk(value);\n }\n },\n /** Clears only this entry from memory and disk. */\n clear: async (): Promise<void> => {\n cacheMap.delete(id);\n\n try {\n await unlink(filePath);\n } catch {}\n },\n /** Clears ALL cached entries (memory Map and entire cacheDir namespace if persistent). */\n clearAll: async (): Promise<void> => {\n clearAllCache();\n if (persistent && buildCacheEnabled) {\n // remove only the current namespace (if provided), else the root dir\n const base = namespace ? join(cacheDir, namespace) : cacheDir;\n\n try {\n await rm(base, { recursive: true, force: true });\n } catch {}\n\n try {\n await mkdir(base, { recursive: true });\n } catch {}\n }\n },\n /** Expose the computed id (useful if you want to key other structures). */\n isValid: async (): Promise<boolean> => {\n const cachedValue = cacheMap.get(id);\n if (cachedValue !== undefined) return true;\n\n // If persistence is disabled or build cache disabled, only memory can be valid\n if (!persistent || !buildCacheEnabled) return false;\n\n try {\n const statValue = await stat(filePath).catch(() => undefined);\n if (!statValue) return false;\n\n if (typeof ttlMs === 'number' && ttlMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > ttlMs) return false;\n }\n\n let raw = await readFile(filePath);\n const flag = raw[0];\n raw = raw.subarray(1);\n const payload = flag === 0x01 ? gunzipSync(raw) : raw;\n const deserialized = deserialize(payload) as unknown;\n\n const maybeObj = deserialized as Record<string, unknown> | null;\n const isEnvelope =\n !!maybeObj &&\n typeof maybeObj === 'object' &&\n typeof maybeObj.version === 'string' &&\n typeof maybeObj.timestamp === 'number' &&\n Object.hasOwn(maybeObj, 'data');\n\n if (isEnvelope) {\n const entry = maybeObj as CacheEnvelope;\n if (entry.version !== configPackageJson.version) return false;\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - entry.timestamp;\n if (age > maxTimeMs) return false;\n }\n return true;\n }\n\n if (typeof maxTimeMs === 'number' && maxTimeMs > 0) {\n const age = Date.now() - statValue.mtimeMs;\n if (age > maxTimeMs) return false;\n }\n return true;\n } catch {\n return false;\n }\n },\n /** Expose the computed id (useful if you want to key other structures). */\n id,\n /** Expose the absolute file path for debugging. */\n filePath,\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/cacheMemory.ts
|
|
4
|
+
/** ------------------------- Utilities ------------------------- **/
|
|
5
|
+
/** Prefer a fast non-crypto hash if available, then fast crypto, then sha256. */
|
|
6
|
+
const pickHashAlgorithm = () => {
|
|
7
|
+
try {
|
|
8
|
+
createHash("xxhash64").update("test").digest();
|
|
9
|
+
return "xxhash64";
|
|
10
|
+
} catch {}
|
|
11
|
+
try {
|
|
12
|
+
createHash("sha1").update("test").digest();
|
|
13
|
+
return "sha1";
|
|
14
|
+
} catch {}
|
|
15
|
+
return "sha256";
|
|
16
|
+
};
|
|
17
|
+
const HASH_ALGORITHM = pickHashAlgorithm();
|
|
18
|
+
/** Base64url without padding for compact, file-system-safe ids. */
|
|
19
|
+
const toBase64Url = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
20
|
+
/** Token helpers to minimize collisions while streaming to the hasher. */
|
|
21
|
+
const token = {
|
|
22
|
+
start: (hasher, tag) => hasher.update(`<${tag}>`),
|
|
23
|
+
sep: (hasher) => hasher.update("|"),
|
|
24
|
+
end: (hasher, tag) => hasher.update(`</${tag}>`),
|
|
25
|
+
str: (hasher, stringValue) => {
|
|
26
|
+
hasher.update(`${stringValue.length}#`);
|
|
27
|
+
hasher.update(stringValue);
|
|
28
|
+
},
|
|
29
|
+
num: (hasher, numberValue) => hasher.update(Number.isNaN(numberValue) ? "NaN" : numberValue === Infinity ? "Inf" : numberValue === -Infinity ? "-Inf" : String(numberValue)),
|
|
30
|
+
big: (hasher, bigintValue) => hasher.update(bigintValue.toString(10)),
|
|
31
|
+
bool: (hasher, booleanValue) => hasher.update(booleanValue ? "1" : "0")
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Streams a canonical representation of `value` into `hasher` without
|
|
35
|
+
* constructing large intermediate strings. Objects/Maps/Sets are normalized.
|
|
36
|
+
*/
|
|
37
|
+
const stableHashValue = (hasher, value, seen) => {
|
|
38
|
+
const valueType = typeof value;
|
|
39
|
+
if (value === null) {
|
|
40
|
+
token.start(hasher, "null");
|
|
41
|
+
token.end(hasher, "null");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (valueType === "undefined") {
|
|
45
|
+
token.start(hasher, "undef");
|
|
46
|
+
token.end(hasher, "undef");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (valueType === "number") {
|
|
50
|
+
token.start(hasher, "num");
|
|
51
|
+
token.num(hasher, value);
|
|
52
|
+
token.end(hasher, "num");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (valueType === "bigint") {
|
|
56
|
+
token.start(hasher, "big");
|
|
57
|
+
token.big(hasher, value);
|
|
58
|
+
token.end(hasher, "big");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (valueType === "boolean") {
|
|
62
|
+
token.start(hasher, "bool");
|
|
63
|
+
token.bool(hasher, value);
|
|
64
|
+
token.end(hasher, "bool");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (valueType === "string") {
|
|
68
|
+
token.start(hasher, "str");
|
|
69
|
+
token.str(hasher, value);
|
|
70
|
+
token.end(hasher, "str");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (valueType === "symbol") {
|
|
74
|
+
token.start(hasher, "sym");
|
|
75
|
+
token.str(hasher, String(value));
|
|
76
|
+
token.end(hasher, "sym");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (valueType === "function") {
|
|
80
|
+
const functionValue = value;
|
|
81
|
+
token.start(hasher, "fn");
|
|
82
|
+
token.str(hasher, functionValue.name ?? "");
|
|
83
|
+
token.sep(hasher);
|
|
84
|
+
token.num(hasher, functionValue.length);
|
|
85
|
+
token.end(hasher, "fn");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
if (seen.has(value)) {
|
|
90
|
+
token.start(hasher, "arr");
|
|
91
|
+
token.str(hasher, "Circular");
|
|
92
|
+
token.end(hasher, "arr");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
seen.add(value);
|
|
96
|
+
token.start(hasher, "arr");
|
|
97
|
+
for (let i = 0; i < value.length; i++) {
|
|
98
|
+
token.sep(hasher);
|
|
99
|
+
stableHashValue(hasher, value[i], seen);
|
|
100
|
+
}
|
|
101
|
+
token.end(hasher, "arr");
|
|
102
|
+
seen.delete(value);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (value instanceof Date) {
|
|
106
|
+
token.start(hasher, "date");
|
|
107
|
+
token.str(hasher, value.toISOString());
|
|
108
|
+
token.end(hasher, "date");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (value instanceof RegExp) {
|
|
112
|
+
const regex = value;
|
|
113
|
+
token.start(hasher, "re");
|
|
114
|
+
token.str(hasher, regex.source);
|
|
115
|
+
token.sep(hasher);
|
|
116
|
+
token.str(hasher, regex.flags);
|
|
117
|
+
token.end(hasher, "re");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (value instanceof Set) {
|
|
121
|
+
const setValue = value;
|
|
122
|
+
if (seen.has(setValue)) {
|
|
123
|
+
token.start(hasher, "set");
|
|
124
|
+
token.str(hasher, "Circular");
|
|
125
|
+
token.end(hasher, "set");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
seen.add(setValue);
|
|
129
|
+
const items = [];
|
|
130
|
+
for (const v of setValue) items.push(stableStringify(v));
|
|
131
|
+
items.sort();
|
|
132
|
+
token.start(hasher, "set");
|
|
133
|
+
for (const item of items) {
|
|
134
|
+
token.sep(hasher);
|
|
135
|
+
token.str(hasher, item);
|
|
136
|
+
}
|
|
137
|
+
token.end(hasher, "set");
|
|
138
|
+
seen.delete(setValue);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (value instanceof Map) {
|
|
142
|
+
const mapObject = value;
|
|
143
|
+
if (seen.has(mapObject)) {
|
|
144
|
+
token.start(hasher, "map");
|
|
145
|
+
token.str(hasher, "Circular");
|
|
146
|
+
token.end(hasher, "map");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
seen.add(mapObject);
|
|
150
|
+
const entries = [];
|
|
151
|
+
for (const [k, v] of mapObject.entries()) entries.push([stableStringify(k), v]);
|
|
152
|
+
entries.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
153
|
+
token.start(hasher, "map");
|
|
154
|
+
for (const [keyFingerprint, entryValue] of entries) {
|
|
155
|
+
token.sep(hasher);
|
|
156
|
+
token.str(hasher, keyFingerprint);
|
|
157
|
+
token.sep(hasher);
|
|
158
|
+
stableHashValue(hasher, entryValue, seen);
|
|
159
|
+
}
|
|
160
|
+
token.end(hasher, "map");
|
|
161
|
+
seen.delete(mapObject);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (ArrayBuffer.isView(value)) {
|
|
165
|
+
const view = value;
|
|
166
|
+
token.start(hasher, "typed");
|
|
167
|
+
token.str(hasher, Object.getPrototypeOf(view).constructor.name);
|
|
168
|
+
token.sep(hasher);
|
|
169
|
+
hasher.update(Buffer.from(view.buffer, view.byteOffset, view.byteLength));
|
|
170
|
+
token.end(hasher, "typed");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (value instanceof ArrayBuffer) {
|
|
174
|
+
const buffer = Buffer.from(value);
|
|
175
|
+
token.start(hasher, "ab");
|
|
176
|
+
hasher.update(buffer);
|
|
177
|
+
token.end(hasher, "ab");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (typeof URL !== "undefined" && value instanceof URL) {
|
|
181
|
+
token.start(hasher, "url");
|
|
182
|
+
token.str(hasher, value.toString());
|
|
183
|
+
token.end(hasher, "url");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (value instanceof Error) {
|
|
187
|
+
const errorValue = value;
|
|
188
|
+
token.start(hasher, "err");
|
|
189
|
+
token.str(hasher, errorValue.name || "");
|
|
190
|
+
token.sep(hasher);
|
|
191
|
+
token.str(hasher, errorValue.message || "");
|
|
192
|
+
token.sep(hasher);
|
|
193
|
+
token.str(hasher, errorValue.stack || "");
|
|
194
|
+
token.end(hasher, "err");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (valueType === "object") {
|
|
198
|
+
const objectValue = value;
|
|
199
|
+
if (seen.has(objectValue)) {
|
|
200
|
+
token.start(hasher, "obj");
|
|
201
|
+
token.str(hasher, "Circular");
|
|
202
|
+
token.end(hasher, "obj");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
seen.add(objectValue);
|
|
206
|
+
const keys = Object.keys(objectValue).sort();
|
|
207
|
+
token.start(hasher, "obj");
|
|
208
|
+
for (const key of keys) {
|
|
209
|
+
token.sep(hasher);
|
|
210
|
+
token.str(hasher, key);
|
|
211
|
+
token.sep(hasher);
|
|
212
|
+
stableHashValue(hasher, objectValue[key], seen);
|
|
213
|
+
}
|
|
214
|
+
token.end(hasher, "obj");
|
|
215
|
+
seen.delete(objectValue);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
token.start(hasher, "other");
|
|
219
|
+
token.str(hasher, String(value));
|
|
220
|
+
token.end(hasher, "other");
|
|
221
|
+
};
|
|
222
|
+
/** Public stringify kept for convenience / debugging (now faster & broader). */
|
|
223
|
+
const stableStringify = (value, _stack = /* @__PURE__ */ new WeakSet()) => {
|
|
224
|
+
const hasher = createHash(HASH_ALGORITHM);
|
|
225
|
+
stableHashValue(hasher, value, _stack);
|
|
226
|
+
return toBase64Url(hasher.digest());
|
|
227
|
+
};
|
|
228
|
+
/** Compute a compact, stable id for arbitrary key tuples. */
|
|
229
|
+
const computeKeyId = (keyParts) => {
|
|
230
|
+
const h = createHash(HASH_ALGORITHM);
|
|
231
|
+
token.start(h, "keys");
|
|
232
|
+
for (let i = 0; i < keyParts.length; i++) {
|
|
233
|
+
token.sep(h);
|
|
234
|
+
stableHashValue(h, keyParts[i], /* @__PURE__ */ new WeakSet());
|
|
235
|
+
}
|
|
236
|
+
token.end(h, "keys");
|
|
237
|
+
return toBase64Url(h.digest());
|
|
238
|
+
};
|
|
239
|
+
const cacheMap = /* @__PURE__ */ new Map();
|
|
240
|
+
const getCache = (...key) => {
|
|
241
|
+
return cacheMap.get(computeKeyId(key));
|
|
242
|
+
};
|
|
243
|
+
const setCache = (...args) => {
|
|
244
|
+
const value = args[args.length - 1];
|
|
245
|
+
const key = args.slice(0, -1);
|
|
246
|
+
cacheMap.set(computeKeyId(key), value);
|
|
247
|
+
};
|
|
248
|
+
const clearCache = (idOrKey) => {
|
|
249
|
+
cacheMap.delete(idOrKey);
|
|
250
|
+
};
|
|
251
|
+
const clearAllCache = () => {
|
|
252
|
+
cacheMap.clear();
|
|
253
|
+
};
|
|
254
|
+
const cacheMemory = {
|
|
255
|
+
get: getCache,
|
|
256
|
+
set: setCache,
|
|
257
|
+
clear: clearCache
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
export { cacheMemory, clearAllCache, clearCache, computeKeyId, getCache, setCache, stableStringify };
|
|
262
|
+
//# sourceMappingURL=cacheMemory.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheMemory.mjs","names":["items: string[]","entries: Array<[string, unknown]>"],"sources":["../../../src/utils/cacheMemory.ts"],"sourcesContent":["import { createHash, type Hash } from 'node:crypto';\n\n/** ------------------------- Utilities ------------------------- **/\n\n/** Prefer a fast non-crypto hash if available, then fast crypto, then sha256. */\nconst pickHashAlgorithm = (): string => {\n try {\n // Node 20+ supports xxhash64 (very fast). We feature-detect at module load.\n createHash('xxhash64').update('test').digest();\n return 'xxhash64';\n } catch {}\n try {\n // sha1 is faster than sha256 and sufficient for cache keys.\n createHash('sha1').update('test').digest();\n return 'sha1';\n } catch {}\n\n return 'sha256';\n};\nconst HASH_ALGORITHM = pickHashAlgorithm();\n\n/** Base64url without padding for compact, file-system-safe ids. */\nconst toBase64Url = (buffer: Buffer): string =>\n buffer\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/g, '');\n\n/** Token helpers to minimize collisions while streaming to the hasher. */\nconst token = {\n start: (hasher: Hash, tag: string) => hasher.update(`<${tag}>`),\n sep: (hasher: Hash) => hasher.update('|'),\n end: (hasher: Hash, tag: string) => hasher.update(`</${tag}>`),\n str: (hasher: Hash, stringValue: string) => {\n // length prefix to avoid ambiguity: len#value\n hasher.update(`${stringValue.length}#`);\n hasher.update(stringValue);\n },\n num: (hasher: Hash, numberValue: number) =>\n hasher.update(\n Number.isNaN(numberValue)\n ? 'NaN'\n : numberValue === Infinity\n ? 'Inf'\n : numberValue === -Infinity\n ? '-Inf'\n : String(numberValue)\n ),\n big: (hasher: Hash, bigintValue: bigint) =>\n hasher.update(bigintValue.toString(10)),\n bool: (hasher: Hash, booleanValue: boolean) =>\n hasher.update(booleanValue ? '1' : '0'),\n};\n\n/** ------------------- Canonical, streaming hasher ------------------- **/\n\ntype Seen = WeakSet<object>;\n\n/**\n * Streams a canonical representation of `value` into `hasher` without\n * constructing large intermediate strings. Objects/Maps/Sets are normalized.\n */\nconst stableHashValue = (hasher: Hash, value: unknown, seen: Seen): void => {\n const valueType = typeof value;\n\n if (value === null) {\n token.start(hasher, 'null');\n token.end(hasher, 'null');\n return;\n }\n\n if (valueType === 'undefined') {\n token.start(hasher, 'undef');\n token.end(hasher, 'undef');\n return;\n }\n\n if (valueType === 'number') {\n token.start(hasher, 'num');\n token.num(hasher, value as number);\n token.end(hasher, 'num');\n return;\n }\n\n if (valueType === 'bigint') {\n token.start(hasher, 'big');\n token.big(hasher, value as bigint);\n token.end(hasher, 'big');\n return;\n }\n\n if (valueType === 'boolean') {\n token.start(hasher, 'bool');\n token.bool(hasher, value as boolean);\n token.end(hasher, 'bool');\n return;\n }\n\n if (valueType === 'string') {\n token.start(hasher, 'str');\n token.str(hasher, value as string);\n token.end(hasher, 'str');\n return;\n }\n\n if (valueType === 'symbol') {\n token.start(hasher, 'sym');\n token.str(hasher, String(value));\n token.end(hasher, 'sym');\n return;\n }\n\n if (valueType === 'function') {\n // Stable-ish fingerprint: name and arity (avoid source text).\n const functionValue = value as Function;\n token.start(hasher, 'fn');\n token.str(hasher, functionValue.name ?? '');\n token.sep(hasher);\n token.num(hasher, functionValue.length);\n token.end(hasher, 'fn');\n return;\n }\n\n // Arrays and typed arrays\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n token.start(hasher, 'arr');\n token.str(hasher, 'Circular');\n token.end(hasher, 'arr');\n return;\n }\n seen.add(value);\n token.start(hasher, 'arr');\n for (let i = 0; i < value.length; i++) {\n token.sep(hasher);\n stableHashValue(hasher, value[i], seen);\n }\n token.end(hasher, 'arr');\n seen.delete(value);\n return;\n }\n\n // Node/Builtins\n if (value instanceof Date) {\n token.start(hasher, 'date');\n token.str(hasher, (value as Date).toISOString());\n token.end(hasher, 'date');\n return;\n }\n\n if (value instanceof RegExp) {\n const regex = value as RegExp;\n token.start(hasher, 're');\n token.str(hasher, regex.source);\n token.sep(hasher);\n token.str(hasher, regex.flags);\n token.end(hasher, 're');\n return;\n }\n\n if (value instanceof Set) {\n const setValue = value as Set<unknown>;\n if (seen.has(setValue)) {\n token.start(hasher, 'set');\n token.str(hasher, 'Circular');\n token.end(hasher, 'set');\n return;\n }\n seen.add(setValue);\n // Normalize by item fingerprints (strings) to sort deterministically.\n const items: string[] = [];\n for (const v of setValue) items.push(stableStringify(v)); // small, bounded use of stringify\n items.sort();\n token.start(hasher, 'set');\n for (const item of items) {\n token.sep(hasher);\n token.str(hasher, item);\n }\n token.end(hasher, 'set');\n seen.delete(setValue);\n return;\n }\n\n if (value instanceof Map) {\n const mapObject = value as Map<unknown, unknown>;\n if (seen.has(mapObject)) {\n token.start(hasher, 'map');\n token.str(hasher, 'Circular');\n token.end(hasher, 'map');\n return;\n }\n seen.add(mapObject);\n // Normalize by sorted key fingerprints.\n const entries: Array<[string, unknown]> = [];\n for (const [k, v] of mapObject.entries())\n entries.push([stableStringify(k), v]);\n entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));\n token.start(hasher, 'map');\n for (const [keyFingerprint, entryValue] of entries) {\n token.sep(hasher);\n token.str(hasher, keyFingerprint);\n token.sep(hasher);\n stableHashValue(hasher, entryValue, seen);\n }\n token.end(hasher, 'map');\n seen.delete(mapObject);\n return;\n }\n\n // ArrayBuffer & typed arrays\n if (ArrayBuffer.isView(value)) {\n const view = value as ArrayBufferView;\n token.start(hasher, 'typed');\n token.str(hasher, Object.getPrototypeOf(view).constructor.name);\n token.sep(hasher);\n hasher.update(Buffer.from(view.buffer, view.byteOffset, view.byteLength));\n token.end(hasher, 'typed');\n return;\n }\n if (value instanceof ArrayBuffer) {\n const buffer = Buffer.from(value as ArrayBuffer);\n token.start(hasher, 'ab');\n hasher.update(buffer);\n token.end(hasher, 'ab');\n return;\n }\n\n // URL\n if (typeof URL !== 'undefined' && value instanceof URL) {\n token.start(hasher, 'url');\n token.str(hasher, (value as URL).toString());\n token.end(hasher, 'url');\n return;\n }\n\n // Errors\n if (value instanceof Error) {\n const errorValue = value as Error;\n token.start(hasher, 'err');\n token.str(hasher, errorValue.name || '');\n token.sep(hasher);\n token.str(hasher, errorValue.message || '');\n token.sep(hasher);\n token.str(hasher, errorValue.stack || '');\n token.end(hasher, 'err');\n return;\n }\n\n // Generic objects\n if (valueType === 'object') {\n const objectValue = value as Record<string, unknown>;\n if (seen.has(objectValue)) {\n token.start(hasher, 'obj');\n token.str(hasher, 'Circular');\n token.end(hasher, 'obj');\n return;\n }\n seen.add(objectValue);\n\n const keys = Object.keys(objectValue).sort();\n token.start(hasher, 'obj');\n for (const key of keys) {\n token.sep(hasher);\n token.str(hasher, key);\n token.sep(hasher);\n stableHashValue(hasher, (objectValue as any)[key], seen);\n }\n token.end(hasher, 'obj');\n\n seen.delete(objectValue);\n return;\n }\n\n // Fallback\n token.start(hasher, 'other');\n token.str(hasher, String(value));\n token.end(hasher, 'other');\n};\n\n/** Public stringify kept for convenience / debugging (now faster & broader). */\nexport const stableStringify = (\n value: unknown,\n _stack = new WeakSet<object>()\n): string => {\n const hasher = createHash(HASH_ALGORITHM);\n stableHashValue(hasher, value, _stack);\n return toBase64Url(hasher.digest());\n};\n\n/** Compute a compact, stable id for arbitrary key tuples. */\nexport const computeKeyId = (keyParts: unknown[]): string => {\n const h = createHash(HASH_ALGORITHM);\n token.start(h, 'keys');\n for (let i = 0; i < keyParts.length; i++) {\n token.sep(h);\n stableHashValue(h, keyParts[i], new WeakSet());\n }\n token.end(h, 'keys');\n return toBase64Url(h.digest());\n};\n\n/** ------------------------- In-memory cache ------------------------- **/\n\nexport type CacheKey = unknown;\nconst cacheMap = new Map<string, any>();\n\nexport const getCache = <T>(...key: CacheKey[]): T | undefined => {\n return cacheMap.get(computeKeyId(key));\n};\n\ntype CacheSetArgs<T> = [...keys: CacheKey[], value: T];\n\nexport const setCache = <T>(...args: CacheSetArgs<T>): void => {\n const value = args[args.length - 1] as T;\n const key = args.slice(0, -1) as CacheKey[];\n cacheMap.set(computeKeyId(key), value);\n};\n\nexport const clearCache = (idOrKey: string): void => {\n // Accept either our computed id or a legacy string id the caller already computed.\n cacheMap.delete(idOrKey);\n};\n\nexport const clearAllCache = (): void => {\n cacheMap.clear();\n};\n\nexport const cacheMemory = {\n get: getCache,\n set: setCache,\n clear: clearCache,\n};\n"],"mappings":"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"}
|