{"version":3,"file":"lockfile.mjs","names":[],"sources":["../../src/cache/paths.ts","../../src/cache/storage.ts","../../src/cache/lockfile.ts"],"sourcesContent":["import { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst APP_NAME = \"registries\";\n\n/**\n * Resolve the cache directory using the current platform convention.\n *\n * @returns {string} The absolute cache directory path.\n */\nexport function getCacheDir(): string {\n  // Explicit override always wins\n  const envOverride = process.env[\"REGISTRIES_CACHE_DIR\"];\n  if (envOverride) return envOverride;\n\n  const os = platform();\n\n  if (os === \"darwin\") {\n    return join(homedir(), \"Library\", \"Caches\", APP_NAME);\n  }\n\n  if (os === \"win32\") {\n    const localAppData = process.env[\"LOCALAPPDATA\"];\n    if (localAppData) return join(localAppData, APP_NAME, \"cache\");\n    return join(homedir(), \"AppData\", \"Local\", APP_NAME, \"cache\");\n  }\n\n  // Linux / FreeBSD / other — XDG Base Directory\n  const xdgCache = process.env[\"XDG_CACHE_HOME\"];\n  if (xdgCache) return join(xdgCache, APP_NAME);\n  return join(homedir(), \".cache\", APP_NAME);\n}\n","import { createStorage, prefixStorage } from \"unstorage\";\nimport fsDriver from \"unstorage/drivers/fs\";\nimport type { Storage } from \"unstorage\";\nimport { getCacheDir } from \"./paths.ts\";\nimport { hash } from \"node:crypto\";\n\nlet _storage: Storage | undefined;\n\n/**\n * Configure the storage backend before the first cache operation.\n *\n * @param storage - Storage backend to use.\n */\nexport function configureStorage(storage: Storage): void {\n  _storage = storage;\n}\n\n/**\n * Get or create the shared storage backend.\n *\n * @returns {Storage} The configured or filesystem-backed storage.\n */\nexport function getStorage(): Storage {\n  _storage ??= createStorage({\n    driver: fsDriver({ base: getCacheDir() }),\n  });\n  return _storage;\n}\n\n/**\n * Get storage scoped to one registry ecosystem.\n *\n * @param ecosystem - Registry ecosystem key.\n * @returns {Storage} Namespaced storage for the ecosystem.\n */\nexport function getEcosystemStorage(ecosystem: string): Storage {\n  return prefixStorage(getStorage(), ecosystem);\n}\n\n/**\n * Compute a SHA-256 integrity value for JSON-serializable data.\n *\n * @param value - Value to hash.\n * @returns {string} The prefixed hexadecimal digest.\n */\nexport function computeIntegrity(value: unknown): string {\n  return `sha256-${hash(\"sha256\", JSON.stringify(value), \"hex\")}`;\n}\n\n/** Dispose the storage instance. Call on process exit. */\nexport async function disposeStorage(): Promise<void> {\n  if (_storage) {\n    await _storage.dispose();\n    _storage = undefined;\n  }\n}\n\n/** Clear all cached data. */\nexport async function clearStorage(): Promise<void> {\n  await getStorage().clear();\n}\n","import type { Storage } from \"unstorage\";\nimport { getStorage } from \"./storage.ts\";\n\nconst LOCKFILE_VERSION = 1;\nconst LOCKFILE_KEY = \"__lockfile__\";\n\n/** Default TTL per data type (seconds). */\nexport const DEFAULT_TTL = {\n  package: 3600, // 1 hour — package metadata changes rarely\n  versions: 1800, // 30 min — new versions published more often\n  dependencies: 86400, // 24 hours — deps of a specific version never change\n  maintainers: 86400, // 24 hours\n} as const;\n\nexport type EntryType = keyof typeof DEFAULT_TTL;\n\n/** A single cached entry tracked in the lockfile. */\nexport interface LockfileEntry {\n  /** PURL-style key: 'npm:lodash', 'cargo:serde' */\n  key: string;\n  /** What kind of data: package, versions, dependencies, maintainers */\n  type: EntryType;\n  /** Unix timestamp (ms) when fetched */\n  fetchedAt: number;\n  /** TTL in seconds */\n  ttl: number;\n  /** Latest version at time of caching (for package entries) */\n  latestVersion?: string;\n  /** Content hash (sha256 of JSON-serialized value) for integrity */\n  integrity: string;\n}\n\n/** The full lockfile structure. */\nexport interface Lockfile {\n  version: number;\n  entries: Record<string, LockfileEntry>;\n}\n\nfunction emptyLockfile(): Lockfile {\n  return { version: LOCKFILE_VERSION, entries: {} };\n}\n\n/**\n * Read the lockfile, falling back to an empty valid structure.\n *\n * @param storage - Optional storage backend.\n * @returns {Promise<Lockfile>} The stored or empty lockfile.\n */\nexport async function readLockfile(storage?: Storage): Promise<Lockfile> {\n  const s = storage ?? getStorage();\n  try {\n    const data = await s.getItem<Lockfile>(LOCKFILE_KEY);\n    if (!data || data.version !== LOCKFILE_VERSION) return emptyLockfile();\n    return data;\n  } catch {\n    return emptyLockfile();\n  }\n}\n\n/**\n * Persist the lockfile.\n *\n * @param lockfile - Lockfile to persist.\n * @param storage - Optional storage backend.\n */\nexport async function writeLockfile(lockfile: Lockfile, storage?: Storage): Promise<void> {\n  const s = storage ?? getStorage();\n  await s.setItem(LOCKFILE_KEY, lockfile);\n}\n\n/**\n * Build a stable cache key.\n *\n * @param ecosystem - Registry ecosystem.\n * @param name - Package name.\n * @param type - Cached data type.\n * @param version - Optional package version.\n * @returns {string} The cache key.\n */\nexport function cacheKey(\n  ecosystem: string,\n  name: string,\n  type: EntryType,\n  version?: string,\n): string {\n  const base = `${ecosystem}:${name}:${type}`;\n  return version ? `${base}:${version}` : base;\n}\n\n/**\n * Check whether a lockfile entry is fresh.\n *\n * @param entry - Entry to inspect.\n * @returns {boolean} Whether the entry is still valid.\n */\nexport function isFresh(entry: LockfileEntry): boolean {\n  const expiresAt = entry.fetchedAt + entry.ttl * 1000;\n  return Date.now() < expiresAt;\n}\n\n/**\n * Read a fresh lockfile entry.\n *\n * @param lockfile - Lockfile to inspect.\n * @param key - Cache key.\n * @returns {LockfileEntry | null} The fresh entry, or null.\n */\nexport function getFreshEntry(lockfile: Lockfile, key: string): LockfileEntry | null {\n  const entry = lockfile.entries[key];\n  if (!entry) return null;\n  if (!isFresh(entry)) return null;\n  return entry;\n}\n\n/**\n * Upsert an in-memory lockfile entry without persisting it.\n *\n * @param lockfile - Lockfile to mutate.\n * @param entry - Entry to store.\n */\nexport function setEntry(lockfile: Lockfile, entry: LockfileEntry): void {\n  lockfile.entries[entry.key] = entry;\n}\n\n/**\n * Remove an in-memory lockfile entry.\n *\n * @param lockfile - Lockfile to mutate.\n * @param key - Cache key to remove.\n */\nexport function removeEntry(lockfile: Lockfile, key: string): void {\n  delete lockfile.entries[key];\n}\n\n/**\n * Remove every stale in-memory entry.\n *\n * @param lockfile - Lockfile to prune.\n * @returns {number} The number of removed entries.\n */\nexport function pruneStale(lockfile: Lockfile): number {\n  let removed = 0;\n  for (const key of Object.keys(lockfile.entries)) {\n    if (!isFresh(lockfile.entries[key]!)) {\n      delete lockfile.entries[key];\n      removed++;\n    }\n  }\n  return removed;\n}\n"],"mappings":";;;;;AAGA,MAAM,WAAW;;;;;;AAOjB,SAAgB,cAAsB;CAEpC,MAAM,cAAc,QAAQ,IAAI;AAChC,KAAI,YAAa,QAAO;CAExB,MAAM,KAAK,UAAU;AAErB,KAAI,OAAO,SACT,QAAO,KAAK,SAAS,EAAE,WAAW,UAAU,SAAS;AAGvD,KAAI,OAAO,SAAS;EAClB,MAAM,eAAe,QAAQ,IAAI;AACjC,MAAI,aAAc,QAAO,KAAK,cAAc,UAAU,QAAQ;AAC9D,SAAO,KAAK,SAAS,EAAE,WAAW,SAAS,UAAU,QAAQ;;CAI/D,MAAM,WAAW,QAAQ,IAAI;AAC7B,KAAI,SAAU,QAAO,KAAK,UAAU,SAAS;AAC7C,QAAO,KAAK,SAAS,EAAE,UAAU,SAAS;;ACxB5C,IAAI;;;;;;AAOJ,SAAgB,iBAAiB,SAAwB;AACvD,YAAW;;;;;;;AAQb,SAAgB,aAAsB;AACpC,cAAa,cAAc,EACzB,QAAQ,SAAS,EAAE,MAAM,aAAa,EAAE,CAAC,EAC1C,CAAC;AACF,QAAO;;;;;;;;AAST,SAAgB,oBAAoB,WAA4B;AAC9D,QAAO,cAAc,YAAY,EAAE,UAAU;;;;;;;;AAS/C,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,UAAU,KAAK,UAAU,KAAK,UAAU,MAAM,EAAE,MAAM;;;AAI/D,eAAsB,iBAAgC;AACpD,KAAI,UAAU;AACZ,QAAM,SAAS,SAAS;AACxB,aAAW,KAAA;;;;AAKf,eAAsB,eAA8B;AAClD,OAAM,YAAY,CAAC,OAAO;;ACxD5B,MAAM,mBAAmB;AACzB,MAAM,eAAe;;AAGrB,MAAa,cAAc;CACzB,SAAS;CACT,UAAU;CACV,cAAc;CACd,aAAa;CACd;AA0BD,SAAS,gBAA0B;AACjC,QAAO;EAAE,SAAS;EAAkB,SAAS,EAAA;EAAI;;;;;;;;AASnD,eAAsB,aAAa,SAAsC;CACvE,MAAM,IAAI,WAAW,YAAY;AACjC,KAAI;EACF,MAAM,OAAO,MAAM,EAAE,QAAkB,aAAa;AACpD,MAAI,CAAC,QAAQ,KAAK,YAAY,iBAAkB,QAAO,eAAe;AACtE,SAAO;SACD;AACN,SAAO,eAAe;;;;;;;;;AAU1B,eAAsB,cAAc,UAAoB,SAAkC;AAExF,QADU,WAAW,YAAY,EACzB,QAAQ,cAAc,SAAS;;;;;;;;;;;AAYzC,SAAgB,SACd,WACA,MACA,MACA,SACQ;CACR,MAAM,OAAO,GAAG,UAAU,GAAG,KAAK,GAAG;AACrC,QAAO,UAAU,GAAG,KAAK,GAAG,YAAY;;;;;;;;AAS1C,SAAgB,QAAQ,OAA+B;CACrD,MAAM,YAAY,MAAM,YAAY,MAAM,MAAM;AAChD,QAAO,KAAK,KAAK,GAAG;;;;;;;;;AAUtB,SAAgB,cAAc,UAAoB,KAAmC;CACnF,MAAM,QAAQ,SAAS,QAAQ;AAC/B,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,QAAO;;;;;;;;AAST,SAAgB,SAAS,UAAoB,OAA4B;AACvE,UAAS,QAAQ,MAAM,OAAO;;;;;;;;AAShC,SAAgB,YAAY,UAAoB,KAAmB;AACjE,QAAO,SAAS,QAAQ;;;;;;;;AAS1B,SAAgB,WAAW,UAA4B;CACrD,IAAI,UAAU;AACd,MAAK,MAAM,OAAO,OAAO,KAAK,SAAS,QAAQ,CAC7C,KAAI,CAAC,QAAQ,SAAS,QAAQ,KAAM,EAAE;AACpC,SAAO,SAAS,QAAQ;AACxB;;AAGJ,QAAO"}