{"version":3,"file":"revalidate-DuZiDzZg.mjs","names":[],"sources":["../src/internal/revalidate.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport useSWRSubscription from \"swr/subscription\";\n\n/**\n * 再検証のトリガー。\n * - \"mount\": ハイドレーション直後に 1 度だけ実行\n * - \"visibility\": タブ可視化 (`visibilitychange` で hidden→visible) の度に実行\n */\nexport type NotionRevalidateTrigger = \"mount\" | \"visibility\";\n\n/**\n * 鮮度チェックのオプション（DO/realtime が無い環境のフォールバック）。\n *\n * mount / 再フォーカス時に `POST {basePath}/check/{collection}/{slug}?v=` を叩き、\n * サーバーが Notion と突合した結果 `stale: true` のときだけ revalidate する。\n * `url` は省略でき、その場合 `collection` と `slug`（または `item.slug`）から導出する。\n * realtime（DO）を併用する場合、このチェックは行わない（push が主経路）。\n */\nexport interface NotionPollOptions {\n  /**\n   * check エンドポイント URL。省略時は `collection` / `slug`（または `item`）\n   * から `${basePath}/check/${collection}/${slug}` を導出する。\n   */\n  url?: string;\n  /** URL 導出に使うコレクション名（例: \"posts\"）。`url` 省略時に必須。 */\n  collection?: string;\n  /** URL 導出に使う slug。省略時は `item.slug` を使う。 */\n  slug?: string;\n  /** `slug` と `version` をまとめて導出するためのアイテム。 */\n  item?: { slug: string; lastEditedTime: string };\n  /** URL 導出時のベースパス。既定 `/api/cms`（`cms.handler()` の既定 basePath）。 */\n  basePath?: string;\n  /** チェック時にサーバーへ送る現在の表示バージョン。省略時は `item.lastEditedTime` を使う。 */\n  version?: string;\n  /**\n   * 間隔チェック（ms）。既定は未設定＝間隔チェックなし（mount / 再フォーカス契機のみ）。\n   * 明示すると従来どおり一定間隔でも check する（Notion API を消費するので注意）。\n   */\n  intervalMs?: number;\n}\n\n/**\n * リアルタイム購読（push）のオプション。設定すると WebSocket でサーバ push を受け、\n * メッセージ受信で即 revalidate する。`url`（ws/wss）を明示するか、`collection` /\n * `slug`（または `item`）から `${basePath}${path}?collection=&slug=` を導出する。\n */\nexport interface NotionRealtimeOptions {\n  /** 接続先 WebSocket URL（ws/wss）。明示時は導出より優先。 */\n  url?: string;\n  /** URL 導出に使うコレクション名。`url` 省略時に必須。 */\n  collection?: string;\n  /** URL 導出に使う slug。省略時は `item.slug`。 */\n  slug?: string;\n  /** `slug` を導出するためのアイテム。 */\n  item?: { slug: string };\n  /** URL 導出時のベースパス。既定 `/api/cms`。 */\n  basePath?: string;\n  /** realtime ルートのパス。既定 `/realtime`。 */\n  path?: string;\n}\n\n/** `cms.handler()` の既定 basePath。check / realtime URL 導出のデフォルト。 */\nconst DEFAULT_CMS_BASE_PATH = \"/api/cms\";\nconst DEFAULT_REALTIME_PATH = \"/realtime\";\n\n/**\n * poll（check）オプションから実際に叩く check URL と送信バージョンを解決する。\n * どちらでも解決できなければ null（= チェックしない）。\n */\nexport function resolvePoll(poll: NotionPollOptions | undefined): {\n  url: string;\n  version: string;\n  intervalMs?: number;\n} | null {\n  if (!poll) return null;\n  const slug = poll.slug ?? poll.item?.slug;\n  const version = poll.version ?? poll.item?.lastEditedTime;\n  const basePath = poll.basePath ?? DEFAULT_CMS_BASE_PATH;\n  const url =\n    poll.url ??\n    (poll.collection && slug\n      ? `${basePath}/check/${poll.collection}/${slug}`\n      : undefined);\n  if (!url || version === undefined) return null;\n  return {\n    url,\n    version,\n    intervalMs: poll.intervalMs,\n  };\n}\n\n/**\n * realtime オプションから接続先を解決する。\n * 明示 `url` はそのまま、導出時は origin 非依存の相対 `path` を返す\n * （ws/wss スキームの組み立ては呼び出し側で `window.location` を使って行う）。\n */\nexport function resolveRealtime(\n  realtime: NotionRealtimeOptions | undefined,\n): { url: string } | { path: string } | null {\n  if (!realtime) return null;\n  if (realtime.url) return { url: realtime.url };\n  if (!realtime.collection) return null;\n  const slug = realtime.slug ?? realtime.item?.slug;\n  const basePath = realtime.basePath ?? DEFAULT_CMS_BASE_PATH;\n  const path = realtime.path ?? DEFAULT_REALTIME_PATH;\n  const qs = new URLSearchParams({ collection: realtime.collection });\n  if (slug) qs.set(\"slug\", slug);\n  return { path: `${basePath}${path}?${qs.toString()}` };\n}\n\n/** 解決済み realtime descriptor を実際の ws/wss URL へ変換する。 */\nexport function wsUrlFromResolved(\n  resolved: { url: string } | { path: string },\n  locationHref: string,\n): string {\n  if (\"url\" in resolved) return resolved.url;\n  const u = new URL(resolved.path, locationHref);\n  u.protocol = u.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n  return u.toString();\n}\n\nexport interface UseNotionRevalidateOptions {\n  /** 既定値: `[\"mount\", \"visibility\"]`（開いた時＋再フォーカス）。複数指定可。 */\n  on?: NotionRevalidateTrigger | NotionRevalidateTrigger[];\n  /** 鮮度チェック（DO/realtime 無効時のフォールバック。`POST /check`）。 */\n  poll?: NotionPollOptions;\n  /** リアルタイム購読（DO, push 主経路）。設定するとポーリング/check は停止する。 */\n  realtime?: NotionRealtimeOptions;\n}\n\n/** `on` を正規化。未指定時の既定は `[\"mount\", \"visibility\"]`（開いた時＋再フォーカス）。 */\nconst toTriggerList = (\n  on: NotionRevalidateTrigger | NotionRevalidateTrigger[] | undefined,\n): NotionRevalidateTrigger[] => {\n  if (on !== undefined) return Array.isArray(on) ? on : [on];\n  return [\"mount\", \"visibility\"];\n};\n\ninterface CheckResponse {\n  stale: boolean;\n  version: string;\n}\n\n/** `POST {check}/{collection}/{slug}?v=` を叩き、サーバーの Notion 突合結果を返す。 */\nconst postCheck = async (\n  url: string,\n  version: string,\n): Promise<CheckResponse | null> => {\n  const sep = url.includes(\"?\") ? \"&\" : \"?\";\n  const res = await fetch(`${url}${sep}v=${encodeURIComponent(version)}`, {\n    method: \"POST\",\n  });\n  if (!res.ok) return null;\n  return (await res.json()) as CheckResponse | null;\n};\n\n/**\n * フレームワーク差分を吸収するための関数 hook。\n * `revalidate` には「現ルートを再評価する」関数を渡す:\n *   - React Router: `() => useRevalidator().revalidate()`\n *   - Next.js: `() => useRouter().refresh()`\n *\n * 更新検知:\n *   - realtime（DO, push 主経路）有効時: `useSWRSubscription` で WebSocket を購読しメッセージで即 revalidate。\n *     ポーリング/check は行わない（push が主経路）。\n *   - realtime 無効時: mount / 再フォーカス（visibility）で `POST /check` を叩き、サーバーが Notion と\n *     突合した結果 `stale: true` のときだけ revalidate する。連続ポーリングは既定で行わない\n *     （`poll.intervalMs` を明示したときのみ一定間隔でも check する）。\n */\nexport function useRevalidateEffect(\n  revalidate: () => void,\n  opts: UseNotionRevalidateOptions,\n): void {\n  const resolvedRealtime = resolveRealtime(opts.realtime);\n  // DO（realtime）有効時は check を行わない（push が主経路、ポーリング停止）。\n  const useRealtime = Boolean(resolvedRealtime);\n  const resolvedCheck = useRealtime ? null : resolvePoll(opts.poll);\n  const triggerKey = toTriggerList(opts.on).join(\",\");\n\n  const checkUrl = resolvedCheck?.url;\n  const checkVersion = resolvedCheck?.version;\n  const checkIntervalMs = resolvedCheck?.intervalMs;\n\n  // realtime は SSR/初回レンダーでも安定値が要るため、location 依存の組み立ては\n  // 文字列キーに落としてから渡す（毎レンダー別オブジェクトになるのを防ぐ）。\n  const wsUrl =\n    resolvedRealtime && typeof window !== \"undefined\"\n      ? wsUrlFromResolved(resolvedRealtime, window.location.href)\n      : null;\n\n  useEffect(() => {\n    // check 設定があれば「Notion と突合 → stale のときだけ revalidate」。\n    // 無ければ revalidate を直接呼ぶ（DO 有効時は loader 再実行→裏チェック→push で fresh になる）。\n    const run = () => {\n      if (checkUrl && checkVersion !== undefined) {\n        void postCheck(checkUrl, checkVersion).then((r) => {\n          if (r?.stale) revalidate();\n        });\n      } else {\n        revalidate();\n      }\n    };\n    const triggers = triggerKey\n      .split(\",\")\n      .filter((t): t is NotionRevalidateTrigger => t.length > 0);\n    const cleanups: Array<() => void> = [];\n    if (triggers.includes(\"mount\")) run();\n    if (triggers.includes(\"visibility\")) {\n      const handler = () => {\n        if (document.visibilityState === \"visible\") run();\n      };\n      document.addEventListener(\"visibilitychange\", handler);\n      cleanups.push(() =>\n        document.removeEventListener(\"visibilitychange\", handler),\n      );\n    }\n    if (\n      checkUrl &&\n      checkVersion !== undefined &&\n      checkIntervalMs &&\n      checkIntervalMs > 0\n    ) {\n      const id = setInterval(run, checkIntervalMs);\n      cleanups.push(() => clearInterval(id));\n    }\n    return () => {\n      for (const c of cleanups) c();\n    };\n  }, [revalidate, triggerKey, checkUrl, checkVersion, checkIntervalMs]);\n\n  // push 主経路: WebSocket を購読し、更新通知メッセージで即 revalidate する。\n  useSWRSubscription(wsUrl, (key, { next }) => {\n    const ws = new WebSocket(key);\n    ws.addEventListener(\"message\", () => revalidate());\n    ws.addEventListener(\"error\", (event) => next(event));\n    return () => ws.close();\n  });\n}\n"],"mappings":"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"}