{"version":3,"file":"manifest.cjs","sources":["../src/core/manifest.ts"],"sourcesContent":["/**\n * tex-packages.json — content-addressed manifest of TL files.\n *\n * Generated at engine build time by `scripts/build-manifest.ts` by walking the\n * built TDS tree. Distributed alongside the engine artifacts.\n *\n * The runtime uses this for three things:\n *   1. Decide which backend tier should serve a given TDS path.\n *   2. Verify bytes before they reach the engine. What that covers, exactly:\n *      the core bundle is checked as a whole against `coreBundleSha256` before\n *      it is unpacked (vfs/bundlefs.ts), and every file served out of the OPFS\n *      cache or fetched from the CDN is checked against its `files[].sha256`\n *      (withIntegrity + FetchFsOptions.verify in vfs/index.ts). Files inside\n *      an already-verified bundle are NOT re-hashed one by one, and a file the\n *      manifest does not list passes through unchecked — the manifest indexes\n *      the TL tree, it is not an allowlist for everything an app may mount. A\n *      backend the caller supplies itself (TauriFS, a custom `vfs` chain) is\n *      the caller's to verify; `withIntegrity()` is exported for that.\n *   3. Reverse-lookup which CTAN package a TDS file belongs to (used in error\n *      messages: \"missing geometry.sty — try downloading the 'geometry' package\").\n */\n\nexport type ManifestTier = 'core' | 'full' | 'cdn';\n\nexport interface ManifestEntry {\n  /** SHA-256 of the file's bytes, hex. */\n  sha256: string;\n  /** File size in bytes. */\n  size: number;\n  /** Which delivery tier this file ships in. */\n  tier: ManifestTier;\n  /** CTAN package the file belongs to (best-effort; null for orphans). */\n  package: string | null;\n}\n\nexport interface TexPackagesManifest {\n  /** Manifest format version. Bumped on schema changes. */\n  schema: 1;\n  /** Versioned build identifier, e.g. \"texlive-2026-r0\". */\n  version: string;\n  /** When the manifest was built (ISO 8601). */\n  generatedAt: string;\n  /** Brotli-compressed core-tier bundle. */\n  coreBundleUrl: string | null;\n  /** SHA-256 of the core bundle's compressed bytes, hex. Optional (older manifests). */\n  coreBundleSha256?: string | null;\n  /** Brotli-compressed full-tier bundle (downloaded once on first run). */\n  fullBundleUrl: string | null;\n  /** SHA-256 of the full bundle's compressed bytes, hex. Optional (older manifests). */\n  fullBundleSha256?: string | null;\n  /** CDN base URL for per-file long-tail fetches. */\n  cdnBaseUrl: string | null;\n  /** TDS-relative path → entry. */\n  files: Record<string, ManifestEntry>;\n}\n\nexport async function loadManifest(url: string): Promise<TexPackagesManifest> {\n  const r = await fetch(url, { cache: 'force-cache' });\n  if (!r.ok) {\n    throw new Error(`Failed to load manifest from ${url}: HTTP ${r.status}`);\n  }\n  const json = (await r.json()) as TexPackagesManifest;\n  if (json.schema !== 1) {\n    throw new Error(`Unsupported manifest schema: ${json.schema}`);\n  }\n  return json;\n}\n\nexport function tierOf(manifest: TexPackagesManifest, tdsPath: string): ManifestTier | null {\n  return manifest.files[tdsPath]?.tier ?? null;\n}\n\nexport function packageOf(manifest: TexPackagesManifest, tdsPath: string): string | null {\n  return manifest.files[tdsPath]?.package ?? null;\n}\n\nexport function expectedSha256(manifest: TexPackagesManifest, tdsPath: string): string | null {\n  return manifest.files[tdsPath]?.sha256 ?? null;\n}\n\n/** SHA-256 of `bytes` as lowercase hex. */\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n  const view = new Uint8Array(bytes.byteLength);\n  view.set(bytes);\n  const digest = await crypto.subtle.digest('SHA-256', view);\n  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Throw unless `bytes` hashes to `expected`. Used on whole blobs — the core\n * bundle, a CDN file — where a poisoned byte stream would otherwise be\n * executed as TeX macros or cached for every later session. Files served out\n * of a bundle we already verified are not re-hashed one by one.\n */\nexport async function assertSha256(\n  bytes: Uint8Array,\n  expected: string,\n  what: string,\n): Promise<void> {\n  const actual = await sha256Hex(bytes);\n  if (actual !== expected.toLowerCase()) {\n    throw new Error(\n      `texlive-wasm: integrity check failed for ${what}: expected sha256 ${expected}, got ${actual}`,\n    );\n  }\n}\n"],"names":[],"mappings":";;;;AAwDA,eAAsB,aAAa,GAAA,EAA2C;AAC5E,EAAA,MAAM,IAAI,MAAM,KAAA,CAAM,KAAK,EAAE,KAAA,EAAO,eAAe,CAAA;AACnD,EAAA,IAAI,CAAC,EAAE,EAAA,EAAI;AACT,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,GAAG,CAAA,OAAA,EAAU,CAAA,CAAE,MAAM,CAAA,CAAE,CAAA;AAAA,EACzE;AACA,EAAA,MAAM,IAAA,GAAQ,MAAM,CAAA,CAAE,IAAA,EAAK;AAC3B,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACrB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,IAAA,CAAK,MAAM,CAAA,CAAE,CAAA;AAAA,EAC/D;AACA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,MAAA,CAAO,UAA+B,OAAA,EAAsC;AAC1F,EAAA,OAAO,QAAA,CAAS,KAAA,CAAM,OAAO,CAAA,EAAG,IAAA,IAAQ,IAAA;AAC1C;AAEO,SAAS,SAAA,CAAU,UAA+B,OAAA,EAAgC;AACvF,EAAA,OAAO,QAAA,CAAS,KAAA,CAAM,OAAO,CAAA,EAAG,OAAA,IAAW,IAAA;AAC7C;AAEO,SAAS,cAAA,CAAe,UAA+B,OAAA,EAAgC;AAC5F,EAAA,OAAO,QAAA,CAAS,KAAA,CAAM,OAAO,CAAA,EAAG,MAAA,IAAU,IAAA;AAC5C;AAGA,eAAsB,UAAU,KAAA,EAAoC;AAClE,EAAA,MAAM,IAAA,GAAO,IAAI,UAAA,CAAW,KAAA,CAAM,UAAU,CAAA;AAC5C,EAAA,IAAA,CAAK,IAAI,KAAK,CAAA;AACd,EAAA,MAAM,SAAS,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,WAAW,IAAI,CAAA;AACzD,EAAA,OAAO,CAAC,GAAG,IAAI,UAAA,CAAW,MAAM,CAAC,CAAA,CAAE,IAAI,CAAC,CAAA,KAAM,EAAE,QAAA,CAAS,EAAE,EAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AACxF;AAQA,eAAsB,YAAA,CACpB,KAAA,EACA,QAAA,EACA,IAAA,EACe;AACf,EAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,KAAK,CAAA;AACpC,EAAA,IAAI,MAAA,KAAW,QAAA,CAAS,WAAA,EAAY,EAAG;AACrC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,yCAAA,EAA4C,IAAI,CAAA,kBAAA,EAAqB,QAAQ,SAAS,MAAM,CAAA;AAAA,KAC9F;AAAA,EACF;AACF;;;;;;;;;"}