{"version":3,"file":"resolve-image-urls-BHMbEFlI.mjs","names":[],"sources":["../src/resolve-image-urls.ts"],"sourcesContent":["import type { NotionBlock } from \"./types\";\n\n/**\n * Notion の署名 URL を永続キャッシュ URL に置換する関数。\n * 通常は `cms.cacheImage` を渡す。1 時間で失効する Notion 署名 URL を\n * SHA256 ハッシュキーで R2 / KV / 任意ストレージに保存し、安定 URL を返す。\n *\n * @see {@link resolveBlockImageUrls}\n */\nexport type CacheImageFn = (url: string) => Promise<string>;\n\n/**\n * `NotionBlock` ツリーを走査し、画像系 block の URL を `cacheImage` 経由で\n * プロキシ URL へ書き換えた**新しい**ツリーを返す。\n *\n * - `cacheImage` 未指定または対応外ブロックは入力をそのまま素通し\n * - file 型のみ書き換える (external 型は Notion 経由ではないため触らない)\n * - children は再帰的に処理し、入力ツリーをミューテートしない\n *\n * Notion 署名 URL は約 1 時間で失効するため、サーバー側で事前にプロキシ URL に\n * 差し替えてから React コンポーネントに渡すことで、レンダリングを同期保てる。\n *\n * @example\n * ```ts\n * const stableBlocks = await resolveBlockImageUrls(blocks, cms.cacheImage);\n * return <NotionRenderer blocks={stableBlocks} />;\n * ```\n *\n * @see {@link CacheImageFn}\n */\nexport async function resolveBlockImageUrls(\n  blocks: NotionBlock[],\n  cacheImage: CacheImageFn | undefined,\n): Promise<NotionBlock[]> {\n  if (!cacheImage) return blocks;\n  return Promise.all(blocks.map((block) => resolveBlock(block, cacheImage)));\n}\n\nasync function resolveBlock(\n  block: NotionBlock,\n  cacheImage: CacheImageFn,\n): Promise<NotionBlock> {\n  let next = await rewriteFileBlock(block, cacheImage);\n  if (next.children && next.children.length > 0) {\n    const children = await Promise.all(\n      next.children.map((child) => resolveBlock(child, cacheImage)),\n    );\n    next = { ...next, children };\n  }\n  return next;\n}\n\nconst FILE_BLOCK_KEYS = [\"image\", \"video\", \"audio\", \"file\", \"pdf\"] as const;\n\ninterface FilePayload {\n  type: \"file\" | \"external\";\n  file?: { url: string; expiry_time?: string };\n  external?: { url: string };\n}\n\n/**\n * Notion API の `image` / `video` / `audio` / `file` / `pdf` block は\n * 同じ `{ type: \"file\" | \"external\", file?, external?, caption }` 形状を持つ。\n * file 型のみ URL を書き換える。\n */\nasync function rewriteFileBlock(\n  block: NotionBlock,\n  cacheImage: CacheImageFn,\n): Promise<NotionBlock> {\n  const record = block as unknown as Record<string, unknown>;\n  for (const key of FILE_BLOCK_KEYS) {\n    const payload = record[key];\n    if (!isFilePayload(payload)) continue;\n    if (payload.type !== \"file\" || !payload.file) continue;\n    const proxied = await cacheImage(payload.file.url);\n    if (proxied === payload.file.url) return block;\n    return {\n      ...block,\n      [key]: {\n        ...payload,\n        file: { ...payload.file, url: proxied },\n      },\n    } as NotionBlock;\n  }\n  return block;\n}\n\nfunction isFilePayload(value: unknown): value is FilePayload {\n  if (!value || typeof value !== \"object\") return false;\n  const v = value as { type?: unknown };\n  return v.type === \"file\" || v.type === \"external\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,sBACpB,QACA,YACwB;CACxB,IAAI,CAAC,YAAY,OAAO;CACxB,OAAO,QAAQ,IAAI,OAAO,KAAK,UAAU,aAAa,OAAO,UAAU,CAAC,CAAC;AAC3E;AAEA,eAAe,aACb,OACA,YACsB;CACtB,IAAI,OAAO,MAAM,iBAAiB,OAAO,UAAU;CACnD,IAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAAG;EAC7C,MAAM,WAAW,MAAM,QAAQ,IAC7B,KAAK,SAAS,KAAK,UAAU,aAAa,OAAO,UAAU,CAAC,CAC9D;EACA,OAAO;GAAE,GAAG;GAAM;EAAS;CAC7B;CACA,OAAO;AACT;AAEA,MAAM,kBAAkB;CAAC;CAAS;CAAS;CAAS;CAAQ;AAAK;;;;;;AAajE,eAAe,iBACb,OACA,YACsB;CACtB,MAAM,SAAS;CACf,KAAK,MAAM,OAAO,iBAAiB;EACjC,MAAM,UAAU,OAAO;EACvB,IAAI,CAAC,cAAc,OAAO,GAAG;EAC7B,IAAI,QAAQ,SAAS,UAAU,CAAC,QAAQ,MAAM;EAC9C,MAAM,UAAU,MAAM,WAAW,QAAQ,KAAK,GAAG;EACjD,IAAI,YAAY,QAAQ,KAAK,KAAK,OAAO;EACzC,OAAO;GACL,GAAG;IACF,MAAM;IACL,GAAG;IACH,MAAM;KAAE,GAAG,QAAQ;KAAM,KAAK;IAAQ;GACxC;EACF;CACF;CACA,OAAO;AACT;AAEA,SAAS,cAAc,OAAsC;CAC3D,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,IAAI;CACV,OAAO,EAAE,SAAS,UAAU,EAAE,SAAS;AACzC"}