@notion-headless-cms/core 0.5.1 → 0.5.2

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.
@@ -1,6 +1,6 @@
1
1
  import { t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
2
  import { o as DataSource, t as CacheAdapter } from "./cache-v9jTMnYd.mjs";
3
- import { a as CMSHooks, i as Logger, t as CMSPlugin } from "./plugin-BmrOz8T6.mjs";
3
+ import { a as CMSHooks, i as Logger, t as CMSPlugin } from "./plugin-BPhaVO6-.mjs";
4
4
 
5
5
  //#region src/types/sources.d.ts
6
6
  /**
@@ -182,4 +182,4 @@ interface CreateClientOptions<S extends CMSSources = CMSSources> {
182
182
  }
183
183
  //#endregion
184
184
  export { MergeSourceCollections as _, DEFAULT_RATE_LIMITER as a, RateLimiterConfig as c, RendererPluginList as d, SWRConfig as f, CMSSources as g, CMSAdapter as h, CreateClientOptions as i, RenderOptions as l, defineCollection as m, CollectionsConfig as n, InferCollectionItem as o, StrictCollectionDef as p, ContentConfig as r, LogLevel as s, CollectionDef as t, RendererFn as u };
185
- //# sourceMappingURL=config-i99tKRhN.d.mts.map
185
+ //# sourceMappingURL=config-Ccs0RxoN.d.mts.map
@@ -12,6 +12,7 @@
12
12
  * | `webhook/payload_invalid` | Webhook ペイロード形式不正 |
13
13
  * | `webhook/unknown_collection` | Webhook の対象コレクションが未知 |
14
14
  * | `webhook/not_implemented` | DataSource が parseWebhook を実装していない |
15
+ * | `handler/unknown_collection` | `cms.handler()` のルート (versions / check) が指す対象コレクションが未知 |
15
16
  * | `source/fetch_items_failed` | `DataSource.list()` 失敗 |
16
17
  * | `source/fetch_item_failed` | `DataSource.findByProp()` 失敗 |
17
18
  * | `source/load_markdown_failed` | `DataSource.loadMarkdown()` 失敗 |
@@ -34,7 +35,7 @@
34
35
  *
35
36
  * サードパーティアダプタが独自コードを追加したい場合は `CMSErrorCode` を参照。
36
37
  */
37
- type BuiltInCMSErrorCode = "core/config_invalid" | "core/schema_invalid" | "core/notion_orm_missing" | "core/sort_unsupported_type" | "webhook/signature_invalid" | "webhook/payload_invalid" | "webhook/unknown_collection" | "webhook/not_implemented" | "source/fetch_items_failed" | "source/fetch_item_failed" | "source/load_markdown_failed" | "source/load_blocks_failed" | "source/blocks_unsupported" | "cache/io_failed" | "cache/image_fetch_failed" | "cache/image_invalid_content_type" | "renderer/failed" | "swr/item_check_failed" | "swr/list_check_failed" | "swr/content_rebuild_failed" | "cli/config_invalid" | "cli/config_load_failed" | "cli/schema_invalid" | "cli/generate_failed" | "cli/init_failed" | "cli/notion_api_failed" | "cli/env_file_not_found";
38
+ type BuiltInCMSErrorCode = "core/config_invalid" | "core/schema_invalid" | "core/notion_orm_missing" | "core/sort_unsupported_type" | "webhook/signature_invalid" | "webhook/payload_invalid" | "webhook/unknown_collection" | "webhook/not_implemented" | "handler/unknown_collection" | "source/fetch_items_failed" | "source/fetch_item_failed" | "source/load_markdown_failed" | "source/load_blocks_failed" | "source/blocks_unsupported" | "cache/io_failed" | "cache/image_fetch_failed" | "cache/image_invalid_content_type" | "renderer/failed" | "swr/item_check_failed" | "swr/list_check_failed" | "swr/content_rebuild_failed" | "cli/config_invalid" | "cli/config_load_failed" | "cli/schema_invalid" | "cli/generate_failed" | "cli/init_failed" | "cli/notion_api_failed" | "cli/env_file_not_found";
38
39
  /**
39
40
  * CMS エラーコード。
40
41
  * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、
@@ -93,4 +94,4 @@ declare function matchCMSError<R>(error: unknown, handlers: Partial<Record<CMSEr
93
94
  }): R | undefined;
94
95
  //#endregion
95
96
  export { isCMSError as a, CMSErrorContext as i, CMSError as n, isCMSErrorInNamespace as o, CMSErrorCode as r, matchCMSError as s, BuiltInCMSErrorCode as t };
96
- //# sourceMappingURL=errors-DcNErfYk.d.mts.map
97
+ //# sourceMappingURL=errors-DPD5yq9S.d.mts.map
package/dist/errors.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-DcNErfYk.mjs";
1
+ import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-DPD5yq9S.mjs";
2
2
  export { BuiltInCMSErrorCode, CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace, matchCMSError };
package/dist/errors.mjs CHANGED
@@ -39,6 +39,10 @@ const BUILT_IN_ERROR_HELP = {
39
39
  docsAnchor: "webhook-not_implemented",
40
40
  nextSteps: ["対象 collection の DataSource に parseWebhook を実装する", "Webhook を使わない場合は cms.invalidate() を直接呼ぶ運用に切り替える"]
41
41
  },
42
+ "handler/unknown_collection": {
43
+ docsAnchor: "handler-unknown_collection",
44
+ nextSteps: ["versions / check のルート URL が登録済みのコレクション名を指しているか確認する", "createCMS の collections に該当コレクションが定義されているか確認する"]
45
+ },
42
46
  "source/fetch_items_failed": {
43
47
  docsAnchor: "source-fetch_items_failed",
44
48
  nextSteps: [
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * ライブラリ組み込みの CMS エラーコード。\n *\n * | コード | 発生条件 |\n * |---|---|\n * | `core/config_invalid` | 設定不備(token 未設定など) |\n * | `core/schema_invalid` | schema/mapping の型不整合 |\n * | `core/notion_orm_missing` | `@notion-headless-cms/notion-orm` の動的ロード失敗 |\n * | `core/sort_unsupported_type` | ソートキーの値型が string / number でない |\n * | `webhook/signature_invalid` | Webhook 署名検証失敗 |\n * | `webhook/payload_invalid` | Webhook ペイロード形式不正 |\n * | `webhook/unknown_collection` | Webhook の対象コレクションが未知 |\n * | `webhook/not_implemented` | DataSource が parseWebhook を実装していない |\n * | `source/fetch_items_failed` | `DataSource.list()` 失敗 |\n * | `source/fetch_item_failed` | `DataSource.findByProp()` 失敗 |\n * | `source/load_markdown_failed` | `DataSource.loadMarkdown()` 失敗 |\n * | `source/load_blocks_failed` | `DataSource.loadBlocks()` 失敗 |\n * | `source/blocks_unsupported` | 選択した fetch 戦略が NotionBlockTree 取得を提供していない (markdown 戦略選択時など) |\n * | `cache/io_failed` | document / image キャッシュの I/O 失敗 |\n * | `cache/image_fetch_failed` | Notion 画像の HTTP 取得失敗 |\n * | `cache/image_invalid_content_type` | 画像レスポンスの Content-Type が不正 |\n * | `renderer/failed` | Markdown → HTML 変換失敗 |\n * | `swr/item_check_failed` | SWR バックグラウンドのアイテム差分チェック失敗 |\n * | `swr/list_check_failed` | SWR バックグラウンドのリスト差分チェック失敗 |\n * | `swr/content_rebuild_failed` | SWR バックグラウンドの本文再生成失敗 |\n * | `cli/config_invalid` | `nhc.config.ts` の内容不整合 |\n * | `cli/config_load_failed` | 設定ファイルの読み込み / 評価失敗 |\n * | `cli/schema_invalid` | CLI が受け取ったスキーマ / マッピング不整合 |\n * | `cli/generate_failed` | `nhc generate` の処理失敗 |\n * | `cli/init_failed` | `nhc init` の処理失敗 |\n * | `cli/notion_api_failed` | CLI が Notion API を呼び出す際の失敗 |\n * | `cli/env_file_not_found` | `--env-file` で指定したファイルが存在しない |\n *\n * サードパーティアダプタが独自コードを追加したい場合は `CMSErrorCode` を参照。\n */\nexport type BuiltInCMSErrorCode =\n | \"core/config_invalid\"\n | \"core/schema_invalid\"\n | \"core/notion_orm_missing\"\n | \"core/sort_unsupported_type\"\n | \"webhook/signature_invalid\"\n | \"webhook/payload_invalid\"\n | \"webhook/unknown_collection\"\n | \"webhook/not_implemented\"\n | \"source/fetch_items_failed\"\n | \"source/fetch_item_failed\"\n | \"source/load_markdown_failed\"\n | \"source/load_blocks_failed\"\n | \"source/blocks_unsupported\"\n | \"cache/io_failed\"\n | \"cache/image_fetch_failed\"\n | \"cache/image_invalid_content_type\"\n | \"renderer/failed\"\n | \"swr/item_check_failed\"\n | \"swr/list_check_failed\"\n | \"swr/content_rebuild_failed\"\n | \"cli/config_invalid\"\n | \"cli/config_load_failed\"\n | \"cli/schema_invalid\"\n | \"cli/generate_failed\"\n | \"cli/init_failed\"\n | \"cli/notion_api_failed\"\n | \"cli/env_file_not_found\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n operation: string;\n slug?: string;\n dataSourceId?: string;\n pageId?: string;\n [key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * 組み込みエラーコードごとのドキュメント URL アンカーと既定の次アクション。\n * `CMSError` コンストラクタが呼び出し側の `docsUrl` / `nextSteps` 未指定時のフォールバックに使用する。\n *\n * `docs/ja/errors/index.md` の各セクション ID と同期させること。\n */\nconst ERROR_DOCS_BASE =\n \"https://github.com/kjfsm/notion-headless-cms/blob/main/docs/ja/errors/index.md\";\n\ninterface ErrorHelp {\n readonly docsAnchor: string;\n readonly nextSteps: readonly string[];\n}\n\nconst BUILT_IN_ERROR_HELP: Record<BuiltInCMSErrorCode, ErrorHelp> = {\n \"core/config_invalid\": {\n docsAnchor: \"core-config_invalid\",\n nextSteps: [\n \"createClient の必須オプション (sources / collections) を確認する\",\n \"NOTION_TOKEN が環境変数または .dev.vars に設定されているか確認する\",\n ],\n },\n \"core/schema_invalid\": {\n docsAnchor: \"core-schema_invalid\",\n nextSteps: [\n \"nhc generate を再実行して schema を最新化する\",\n \"PropertyMap / PropertyDef の型が定義どおりか確認する\",\n ],\n },\n \"core/notion_orm_missing\": {\n docsAnchor: \"core-notion_orm_missing\",\n nextSteps: [\n \"@notion-headless-cms/notion-orm をインストールする\",\n \"実行ランタイムが動的 import を許可しているか確認する\",\n ],\n },\n \"core/sort_unsupported_type\": {\n docsAnchor: \"core-sort_unsupported_type\",\n nextSteps: [\n \"sort.by に指定したフィールドの値型が string / number になっているか確認する\",\n \"別フィールドでソートするか、source 側で正規化する\",\n ],\n },\n \"webhook/signature_invalid\": {\n docsAnchor: \"webhook-signature_invalid\",\n nextSteps: [\n \"Notion 側で発行された webhook secret が一致しているか確認する\",\n \"プロキシ / WAF がリクエストボディを書き換えていないか確認する\",\n ],\n },\n \"webhook/payload_invalid\": {\n docsAnchor: \"webhook-payload_invalid\",\n nextSteps: [\n \"リクエストボディが JSON で送信されているか確認する\",\n \"DataSource.parseWebhook の期待するフィールド構造と一致しているか確認する\",\n ],\n },\n \"webhook/unknown_collection\": {\n docsAnchor: \"webhook-unknown_collection\",\n nextSteps: [\n \"createClient の sources / collections に該当 collection が登録されているか確認する\",\n \"Webhook URL が `?collection=` で正しいコレクション名を指しているか確認する\",\n ],\n },\n \"webhook/not_implemented\": {\n docsAnchor: \"webhook-not_implemented\",\n nextSteps: [\n \"対象 collection の DataSource に parseWebhook を実装する\",\n \"Webhook を使わない場合は cms.invalidate() を直接呼ぶ運用に切り替える\",\n ],\n },\n \"source/fetch_items_failed\": {\n docsAnchor: \"source-fetch_items_failed\",\n nextSteps: [\n \"NOTION_TOKEN がインテグレーションに紐づいているか確認する\",\n \"Notion DB がインテグレーションに接続済みか確認する\",\n \"ネットワーク / Notion API の障害状況 (status.notion.so) を確認する\",\n \"rateLimiter.maxRetries / baseDelayMs を調整してリトライ余地を増やす\",\n ],\n },\n \"source/fetch_item_failed\": {\n docsAnchor: \"source-fetch_item_failed\",\n nextSteps: [\n \"slug プロパティが Notion DB に存在し、値がユニークか確認する\",\n \"対象ページがインテグレーションに共有されているか確認する\",\n ],\n },\n \"source/load_markdown_failed\": {\n docsAnchor: \"source-load_markdown_failed\",\n nextSteps: [\n \"対象ページがアーカイブされていないか確認する\",\n \"未対応ブロック (file / video など) が原因なら fetch 戦略を `markdownFetcher()` に切り替えるか、対象ブロックを除外する\",\n ],\n },\n \"source/load_blocks_failed\": {\n docsAnchor: \"source-load_blocks_failed\",\n nextSteps: [\n \"対象ページが削除 / アーカイブされていないか確認する\",\n \"Notion API の rate limit に当たっていないかログで確認する\",\n ],\n },\n \"source/blocks_unsupported\": {\n docsAnchor: \"source-blocks_unsupported\",\n nextSteps: [\n \"react-renderer を使う場合は fetch 戦略を `fetchBlockTree()` に切り替える\",\n \"あるいは markdown 経路で本文表示にフォールバックする\",\n ],\n },\n \"cache/io_failed\": {\n docsAnchor: \"cache-io_failed\",\n nextSteps: [\n \"KV / R2 / メモリキャッシュの binding (env.DOC_CACHE / env.IMG_BUCKET) が正しいか確認する\",\n \"wrangler.toml の binding 名と createClient に渡した env が一致しているか確認する\",\n \"一時的な障害なら SWR が次回読み込み時に自己回復する\",\n ],\n },\n \"cache/image_fetch_failed\": {\n docsAnchor: \"cache-image_fetch_failed\",\n nextSteps: [\n \"Notion 署名 URL の有効期限 (約 1 時間) が切れていないか確認する\",\n \"Worker / Node のアウトバウンドネットワークが許可されているか確認する\",\n ],\n },\n \"cache/image_invalid_content_type\": {\n docsAnchor: \"cache-image_invalid_content_type\",\n nextSteps: [\n \"Notion 画像 URL を直接ブラウザで開いて image/* を返すか確認する\",\n \"プロキシ / CDN が Content-Type を書き換えていないか確認する\",\n ],\n },\n \"renderer/failed\": {\n docsAnchor: \"renderer-failed\",\n nextSteps: [\n \"renderer に渡している remark / rehype プラグインの組み合わせを確認する\",\n \"fetch 戦略と renderer の組み合わせが整合しているか (Notion enhanced markdown には notionMarkdownRenderer が必要) を確認する\",\n ],\n },\n \"swr/item_check_failed\": {\n docsAnchor: \"swr-item_check_failed\",\n nextSteps: [\n \"ログで cause を確認し、source/fetch_item_failed と同じ手順で原因を切り分ける\",\n \"バックグラウンドの失敗は次回 SWR で自動再試行されるため恒久対処不要なケースもある\",\n ],\n },\n \"swr/list_check_failed\": {\n docsAnchor: \"swr-list_check_failed\",\n nextSteps: [\n \"ログで cause を確認し、source/fetch_items_failed と同じ手順で原因を切り分ける\",\n \"Notion API の rate limit に近い場合は rateLimiter を絞る\",\n ],\n },\n \"swr/content_rebuild_failed\": {\n docsAnchor: \"swr-content_rebuild_failed\",\n nextSteps: [\n \"renderer / loadMarkdown が一時的に失敗しただけならログを確認のうえ放置可\",\n \"恒常的に再発する場合は対象 slug を fresh: true で取り直して再現を確認する\",\n ],\n },\n \"cli/config_invalid\": {\n docsAnchor: \"cli-config_invalid\",\n nextSteps: [\n \"nhc.config.ts が defineConfig() を default export しているか確認する\",\n \"collections に少なくとも 1 件、databaseId または dbName が指定されているか確認する\",\n ],\n },\n \"cli/config_load_failed\": {\n docsAnchor: \"cli-config_load_failed\",\n nextSteps: [\n \"nhc.config.ts に構文エラーがないか tsc / エディタで確認する\",\n \"ESM の import パスが拡張子付き (.js) になっているか確認する\",\n ],\n },\n \"cli/schema_invalid\": {\n docsAnchor: \"cli-schema_invalid\",\n nextSteps: [\n \"Notion DB のプロパティ型が CLI 対応 (title / richText / select / status / multiSelect / date / number / checkbox / url) のいずれかか確認する\",\n \"未対応プロパティをスキップするか、Notion 側で型を変える\",\n ],\n },\n \"cli/generate_failed\": {\n docsAnchor: \"cli-generate_failed\",\n nextSteps: [\n \"--verbose を付けて再実行し、失敗箇所のスタックトレースを確認する\",\n \"出力先ディレクトリの書き込み権限を確認する\",\n ],\n },\n \"cli/init_failed\": {\n docsAnchor: \"cli-init_failed\",\n nextSteps: [\n \"出力先パスに既存ファイルがある場合は --force を付けるか別パスを指定する\",\n \"親ディレクトリの書き込み権限を確認する\",\n ],\n },\n \"cli/notion_api_failed\": {\n docsAnchor: \"cli-notion_api_failed\",\n nextSteps: [\n \"NOTION_TOKEN がインテグレーションに紐づいているか確認する\",\n \"対象 DB がインテグレーションに接続されているか (Notion DB → … → Connections) 確認する\",\n \"DB 名で解決している場合は完全一致 (前後空白 / 全角半角) を確認する\",\n \"--verbose で Notion API レスポンスの status / code を確認する\",\n ],\n },\n \"cli/env_file_not_found\": {\n docsAnchor: \"cli-env_file_not_found\",\n nextSteps: [\n \"--env-file で指定したパスを実ファイルパスで確認する\",\n \"プロジェクトルートで実行しているか (相対パスは cwd 基準)\",\n ],\n },\n};\n\nfunction lookupBuiltInHelp(code: CMSErrorCode): ErrorHelp | undefined {\n return BUILT_IN_ERROR_HELP[code as BuiltInCMSErrorCode];\n}\n\nexport class CMSError extends Error {\n readonly code: CMSErrorCode;\n override readonly cause?: unknown;\n readonly context: CMSErrorContext;\n /** エラーを解消するための次のアクション(表示用)。 */\n readonly nextSteps?: readonly string[];\n /** 詳細ドキュメントへの URL(表示用)。 */\n readonly docsUrl?: string;\n\n constructor(params: {\n code: CMSErrorCode;\n message: string;\n cause?: unknown;\n context: CMSErrorContext;\n nextSteps?: readonly string[];\n docsUrl?: string;\n }) {\n super(params.message, { cause: params.cause });\n this.name = \"CMSError\";\n this.code = params.code;\n this.cause = params.cause;\n this.context = params.context;\n\n // 呼び出し側が nextSteps / docsUrl を指定していない場合は、組み込みコード向けの既定値で補完する。\n // サードパーティコードや未登録コードは undefined のままにする。\n const help = lookupBuiltInHelp(params.code);\n this.nextSteps = params.nextSteps ?? help?.nextSteps;\n this.docsUrl =\n params.docsUrl ??\n (help ? `${ERROR_DOCS_BASE}#${help.docsAnchor}` : undefined);\n }\n\n /** エラーコードが指定した値と一致するか判定する。 */\n is(code: CMSErrorCode): boolean {\n return this.code === code;\n }\n\n /** エラーコードが指定した名前空間に属するか判定する(例: `\"source/\"`)。 */\n inNamespace(namespace: string): boolean {\n return this.code.startsWith(namespace);\n }\n\n /**\n * nextSteps と docsUrl を含む人間向けの詳細メッセージを返す。\n * エラーダイアログ・ログ出力時に使う。\n */\n format(): string {\n const lines: string[] = [this.message];\n if (this.nextSteps?.length) {\n lines.push(\"\\n次にやること:\");\n for (const step of this.nextSteps) {\n lines.push(` - ${step}`);\n }\n }\n if (this.docsUrl) {\n lines.push(`\\n詳細: ${this.docsUrl}`);\n }\n return lines.join(\"\\n\");\n }\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n return error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n error: unknown,\n namespace: string,\n): error is CMSError {\n return isCMSError(error) && error.code.startsWith(namespace);\n}\n\ntype CMSErrorHandler<R> = (err: CMSError) => R;\n\n/**\n * `CMSError` を switch 式のように分岐して処理するユーティリティ。\n * `_` キーはフォールバック(CMSError 以外 or 未マッチ時)に使われる。\n *\n * @example\n * matchCMSError(err, {\n * \"source/fetch_items_failed\": (e) => handleFetchError(e),\n * _: (e) => { throw e; },\n * });\n */\nexport function matchCMSError<R>(\n error: unknown,\n handlers: Partial<Record<CMSErrorCode, CMSErrorHandler<R>>> & {\n _?: (err: unknown) => R;\n },\n): R | undefined {\n if (!isCMSError(error)) {\n return handlers._?.(error);\n }\n const handler =\n handlers[error.code as CMSErrorCode] ??\n (handlers._ as CMSErrorHandler<R> | undefined);\n return handler?.(error);\n}\n"],"mappings":";;;;;;;AAqFA,MAAM,kBACJ;AAOF,MAAM,sBAA8D;CAClE,uBAAuB;EACrB,YAAY;EACZ,WAAW,CACT,uDACA,+CACF;CACF;CACA,uBAAuB;EACrB,YAAY;EACZ,WAAW,CACT,qCACA,yCACF;CACF;CACA,2BAA2B;EACzB,YAAY;EACZ,WAAW,CACT,6CACA,gCACF;CACF;CACA,8BAA8B;EAC5B,YAAY;EACZ,WAAW,CACT,sDACA,8BACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW,CACT,8CACA,oCACF;CACF;CACA,2BAA2B;EACzB,YAAY;EACZ,WAAW,CACT,gCACA,kDACF;CACF;CACA,8BAA8B;EAC5B,YAAY;EACZ,WAAW,CACT,qEACA,qDACF;CACF;CACA,2BAA2B;EACzB,YAAY;EACZ,WAAW,CACT,mDACA,iDACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW;GACT;GACA;GACA;GACA;EACF;CACF;CACA,4BAA4B;EAC1B,YAAY;EACZ,WAAW,CACT,0CACA,8BACF;CACF;CACA,+BAA+B;EAC7B,YAAY;EACZ,WAAW,CACT,0BACA,mFACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW,CACT,+BACA,0CACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW,CACT,6DACA,iCACF;CACF;CACA,mBAAmB;EACjB,YAAY;EACZ,WAAW;GACT;GACA;GACA;EACF;CACF;CACA,4BAA4B;EAC1B,YAAY;EACZ,WAAW,CACT,6CACA,2CACF;CACF;CACA,oCAAoC;EAClC,YAAY;EACZ,WAAW,CACT,8CACA,0CACF;CACF;CACA,mBAAmB;EACjB,YAAY;EACZ,WAAW,CACT,oDACA,kGACF;CACF;CACA,yBAAyB;EACvB,YAAY;EACZ,WAAW,CACT,0DACA,6CACF;CACF;CACA,yBAAyB;EACvB,YAAY;EACZ,WAAW,CACT,2DACA,gDACF;CACF;CACA,8BAA8B;EAC5B,YAAY;EACZ,WAAW,CACT,oDACA,gDACF;CACF;CACA,sBAAsB;EACpB,YAAY;EACZ,WAAW,CACT,6DACA,4DACF;CACF;CACA,0BAA0B;EACxB,YAAY;EACZ,WAAW,CACT,4CACA,yCACF;CACF;CACA,sBAAsB;EACpB,YAAY;EACZ,WAAW,CACT,4HACA,iCACF;CACF;CACA,uBAAuB;EACrB,YAAY;EACZ,WAAW,CACT,yCACA,uBACF;CACF;CACA,mBAAmB;EACjB,YAAY;EACZ,WAAW,CACT,4CACA,qBACF;CACF;CACA,yBAAyB;EACvB,YAAY;EACZ,WAAW;GACT;GACA;GACA;GACA;EACF;CACF;CACA,0BAA0B;EACxB,YAAY;EACZ,WAAW,CACT,mCACA,kCACF;CACF;AACF;AAEA,SAAS,kBAAkB,MAA2C;CACpE,OAAO,oBAAoB;AAC7B;AAEA,IAAa,WAAb,cAA8B,MAAM;CAClC;CACA;CACA;;CAEA;;CAEA;CAEA,YAAY,QAOT;EACD,MAAM,OAAO,SAAS,EAAE,OAAO,OAAO,MAAM,CAAC;EAC7C,KAAK,OAAO;EACZ,KAAK,OAAO,OAAO;EACnB,KAAK,QAAQ,OAAO;EACpB,KAAK,UAAU,OAAO;EAItB,MAAM,OAAO,kBAAkB,OAAO,IAAI;EAC1C,KAAK,YAAY,OAAO,aAAa,MAAM;EAC3C,KAAK,UACH,OAAO,YACN,OAAO,GAAG,gBAAgB,GAAG,KAAK,eAAe,KAAA;CACtD;;CAGA,GAAG,MAA6B;EAC9B,OAAO,KAAK,SAAS;CACvB;;CAGA,YAAY,WAA4B;EACtC,OAAO,KAAK,KAAK,WAAW,SAAS;CACvC;;;;;CAMA,SAAiB;EACf,MAAM,QAAkB,CAAC,KAAK,OAAO;EACrC,IAAI,KAAK,WAAW,QAAQ;GAC1B,MAAM,KAAK,WAAW;GACtB,KAAK,MAAM,QAAQ,KAAK,WACtB,MAAM,KAAK,OAAO,MAAM;EAE5B;EACA,IAAI,KAAK,SACP,MAAM,KAAK,SAAS,KAAK,SAAS;EAEpC,OAAO,MAAM,KAAK,IAAI;CACxB;AACF;AAEA,SAAgB,WAAW,OAAmC;CAC5D,OAAO,iBAAiB;AAC1B;;AAGA,SAAgB,sBACd,OACA,WACmB;CACnB,OAAO,WAAW,KAAK,KAAK,MAAM,KAAK,WAAW,SAAS;AAC7D;;;;;;;;;;;AAcA,SAAgB,cACd,OACA,UAGe;CACf,IAAI,CAAC,WAAW,KAAK,GACnB,OAAO,SAAS,IAAI,KAAK;CAK3B,QAFE,SAAS,MAAM,SACd,SAAS,KACK,KAAK;AACxB"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * ライブラリ組み込みの CMS エラーコード。\n *\n * | コード | 発生条件 |\n * |---|---|\n * | `core/config_invalid` | 設定不備(token 未設定など) |\n * | `core/schema_invalid` | schema/mapping の型不整合 |\n * | `core/notion_orm_missing` | `@notion-headless-cms/notion-orm` の動的ロード失敗 |\n * | `core/sort_unsupported_type` | ソートキーの値型が string / number でない |\n * | `webhook/signature_invalid` | Webhook 署名検証失敗 |\n * | `webhook/payload_invalid` | Webhook ペイロード形式不正 |\n * | `webhook/unknown_collection` | Webhook の対象コレクションが未知 |\n * | `webhook/not_implemented` | DataSource が parseWebhook を実装していない |\n * | `handler/unknown_collection` | `cms.handler()` のルート (versions / check) が指す対象コレクションが未知 |\n * | `source/fetch_items_failed` | `DataSource.list()` 失敗 |\n * | `source/fetch_item_failed` | `DataSource.findByProp()` 失敗 |\n * | `source/load_markdown_failed` | `DataSource.loadMarkdown()` 失敗 |\n * | `source/load_blocks_failed` | `DataSource.loadBlocks()` 失敗 |\n * | `source/blocks_unsupported` | 選択した fetch 戦略が NotionBlockTree 取得を提供していない (markdown 戦略選択時など) |\n * | `cache/io_failed` | document / image キャッシュの I/O 失敗 |\n * | `cache/image_fetch_failed` | Notion 画像の HTTP 取得失敗 |\n * | `cache/image_invalid_content_type` | 画像レスポンスの Content-Type が不正 |\n * | `renderer/failed` | Markdown → HTML 変換失敗 |\n * | `swr/item_check_failed` | SWR バックグラウンドのアイテム差分チェック失敗 |\n * | `swr/list_check_failed` | SWR バックグラウンドのリスト差分チェック失敗 |\n * | `swr/content_rebuild_failed` | SWR バックグラウンドの本文再生成失敗 |\n * | `cli/config_invalid` | `nhc.config.ts` の内容不整合 |\n * | `cli/config_load_failed` | 設定ファイルの読み込み / 評価失敗 |\n * | `cli/schema_invalid` | CLI が受け取ったスキーマ / マッピング不整合 |\n * | `cli/generate_failed` | `nhc generate` の処理失敗 |\n * | `cli/init_failed` | `nhc init` の処理失敗 |\n * | `cli/notion_api_failed` | CLI が Notion API を呼び出す際の失敗 |\n * | `cli/env_file_not_found` | `--env-file` で指定したファイルが存在しない |\n *\n * サードパーティアダプタが独自コードを追加したい場合は `CMSErrorCode` を参照。\n */\nexport type BuiltInCMSErrorCode =\n | \"core/config_invalid\"\n | \"core/schema_invalid\"\n | \"core/notion_orm_missing\"\n | \"core/sort_unsupported_type\"\n | \"webhook/signature_invalid\"\n | \"webhook/payload_invalid\"\n | \"webhook/unknown_collection\"\n | \"webhook/not_implemented\"\n | \"handler/unknown_collection\"\n | \"source/fetch_items_failed\"\n | \"source/fetch_item_failed\"\n | \"source/load_markdown_failed\"\n | \"source/load_blocks_failed\"\n | \"source/blocks_unsupported\"\n | \"cache/io_failed\"\n | \"cache/image_fetch_failed\"\n | \"cache/image_invalid_content_type\"\n | \"renderer/failed\"\n | \"swr/item_check_failed\"\n | \"swr/list_check_failed\"\n | \"swr/content_rebuild_failed\"\n | \"cli/config_invalid\"\n | \"cli/config_load_failed\"\n | \"cli/schema_invalid\"\n | \"cli/generate_failed\"\n | \"cli/init_failed\"\n | \"cli/notion_api_failed\"\n | \"cli/env_file_not_found\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n operation: string;\n slug?: string;\n dataSourceId?: string;\n pageId?: string;\n [key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * 組み込みエラーコードごとのドキュメント URL アンカーと既定の次アクション。\n * `CMSError` コンストラクタが呼び出し側の `docsUrl` / `nextSteps` 未指定時のフォールバックに使用する。\n *\n * `docs/ja/errors/index.md` の各セクション ID と同期させること。\n */\nconst ERROR_DOCS_BASE =\n \"https://github.com/kjfsm/notion-headless-cms/blob/main/docs/ja/errors/index.md\";\n\ninterface ErrorHelp {\n readonly docsAnchor: string;\n readonly nextSteps: readonly string[];\n}\n\nconst BUILT_IN_ERROR_HELP: Record<BuiltInCMSErrorCode, ErrorHelp> = {\n \"core/config_invalid\": {\n docsAnchor: \"core-config_invalid\",\n nextSteps: [\n \"createClient の必須オプション (sources / collections) を確認する\",\n \"NOTION_TOKEN が環境変数または .dev.vars に設定されているか確認する\",\n ],\n },\n \"core/schema_invalid\": {\n docsAnchor: \"core-schema_invalid\",\n nextSteps: [\n \"nhc generate を再実行して schema を最新化する\",\n \"PropertyMap / PropertyDef の型が定義どおりか確認する\",\n ],\n },\n \"core/notion_orm_missing\": {\n docsAnchor: \"core-notion_orm_missing\",\n nextSteps: [\n \"@notion-headless-cms/notion-orm をインストールする\",\n \"実行ランタイムが動的 import を許可しているか確認する\",\n ],\n },\n \"core/sort_unsupported_type\": {\n docsAnchor: \"core-sort_unsupported_type\",\n nextSteps: [\n \"sort.by に指定したフィールドの値型が string / number になっているか確認する\",\n \"別フィールドでソートするか、source 側で正規化する\",\n ],\n },\n \"webhook/signature_invalid\": {\n docsAnchor: \"webhook-signature_invalid\",\n nextSteps: [\n \"Notion 側で発行された webhook secret が一致しているか確認する\",\n \"プロキシ / WAF がリクエストボディを書き換えていないか確認する\",\n ],\n },\n \"webhook/payload_invalid\": {\n docsAnchor: \"webhook-payload_invalid\",\n nextSteps: [\n \"リクエストボディが JSON で送信されているか確認する\",\n \"DataSource.parseWebhook の期待するフィールド構造と一致しているか確認する\",\n ],\n },\n \"webhook/unknown_collection\": {\n docsAnchor: \"webhook-unknown_collection\",\n nextSteps: [\n \"createClient の sources / collections に該当 collection が登録されているか確認する\",\n \"Webhook URL が `?collection=` で正しいコレクション名を指しているか確認する\",\n ],\n },\n \"webhook/not_implemented\": {\n docsAnchor: \"webhook-not_implemented\",\n nextSteps: [\n \"対象 collection の DataSource に parseWebhook を実装する\",\n \"Webhook を使わない場合は cms.invalidate() を直接呼ぶ運用に切り替える\",\n ],\n },\n \"handler/unknown_collection\": {\n docsAnchor: \"handler-unknown_collection\",\n nextSteps: [\n \"versions / check のルート URL が登録済みのコレクション名を指しているか確認する\",\n \"createCMS の collections に該当コレクションが定義されているか確認する\",\n ],\n },\n \"source/fetch_items_failed\": {\n docsAnchor: \"source-fetch_items_failed\",\n nextSteps: [\n \"NOTION_TOKEN がインテグレーションに紐づいているか確認する\",\n \"Notion DB がインテグレーションに接続済みか確認する\",\n \"ネットワーク / Notion API の障害状況 (status.notion.so) を確認する\",\n \"rateLimiter.maxRetries / baseDelayMs を調整してリトライ余地を増やす\",\n ],\n },\n \"source/fetch_item_failed\": {\n docsAnchor: \"source-fetch_item_failed\",\n nextSteps: [\n \"slug プロパティが Notion DB に存在し、値がユニークか確認する\",\n \"対象ページがインテグレーションに共有されているか確認する\",\n ],\n },\n \"source/load_markdown_failed\": {\n docsAnchor: \"source-load_markdown_failed\",\n nextSteps: [\n \"対象ページがアーカイブされていないか確認する\",\n \"未対応ブロック (file / video など) が原因なら fetch 戦略を `markdownFetcher()` に切り替えるか、対象ブロックを除外する\",\n ],\n },\n \"source/load_blocks_failed\": {\n docsAnchor: \"source-load_blocks_failed\",\n nextSteps: [\n \"対象ページが削除 / アーカイブされていないか確認する\",\n \"Notion API の rate limit に当たっていないかログで確認する\",\n ],\n },\n \"source/blocks_unsupported\": {\n docsAnchor: \"source-blocks_unsupported\",\n nextSteps: [\n \"react-renderer を使う場合は fetch 戦略を `fetchBlockTree()` に切り替える\",\n \"あるいは markdown 経路で本文表示にフォールバックする\",\n ],\n },\n \"cache/io_failed\": {\n docsAnchor: \"cache-io_failed\",\n nextSteps: [\n \"KV / R2 / メモリキャッシュの binding (env.DOC_CACHE / env.IMG_BUCKET) が正しいか確認する\",\n \"wrangler.toml の binding 名と createClient に渡した env が一致しているか確認する\",\n \"一時的な障害なら SWR が次回読み込み時に自己回復する\",\n ],\n },\n \"cache/image_fetch_failed\": {\n docsAnchor: \"cache-image_fetch_failed\",\n nextSteps: [\n \"Notion 署名 URL の有効期限 (約 1 時間) が切れていないか確認する\",\n \"Worker / Node のアウトバウンドネットワークが許可されているか確認する\",\n ],\n },\n \"cache/image_invalid_content_type\": {\n docsAnchor: \"cache-image_invalid_content_type\",\n nextSteps: [\n \"Notion 画像 URL を直接ブラウザで開いて image/* を返すか確認する\",\n \"プロキシ / CDN が Content-Type を書き換えていないか確認する\",\n ],\n },\n \"renderer/failed\": {\n docsAnchor: \"renderer-failed\",\n nextSteps: [\n \"renderer に渡している remark / rehype プラグインの組み合わせを確認する\",\n \"fetch 戦略と renderer の組み合わせが整合しているか (Notion enhanced markdown には notionMarkdownRenderer が必要) を確認する\",\n ],\n },\n \"swr/item_check_failed\": {\n docsAnchor: \"swr-item_check_failed\",\n nextSteps: [\n \"ログで cause を確認し、source/fetch_item_failed と同じ手順で原因を切り分ける\",\n \"バックグラウンドの失敗は次回 SWR で自動再試行されるため恒久対処不要なケースもある\",\n ],\n },\n \"swr/list_check_failed\": {\n docsAnchor: \"swr-list_check_failed\",\n nextSteps: [\n \"ログで cause を確認し、source/fetch_items_failed と同じ手順で原因を切り分ける\",\n \"Notion API の rate limit に近い場合は rateLimiter を絞る\",\n ],\n },\n \"swr/content_rebuild_failed\": {\n docsAnchor: \"swr-content_rebuild_failed\",\n nextSteps: [\n \"renderer / loadMarkdown が一時的に失敗しただけならログを確認のうえ放置可\",\n \"恒常的に再発する場合は対象 slug を fresh: true で取り直して再現を確認する\",\n ],\n },\n \"cli/config_invalid\": {\n docsAnchor: \"cli-config_invalid\",\n nextSteps: [\n \"nhc.config.ts が defineConfig() を default export しているか確認する\",\n \"collections に少なくとも 1 件、databaseId または dbName が指定されているか確認する\",\n ],\n },\n \"cli/config_load_failed\": {\n docsAnchor: \"cli-config_load_failed\",\n nextSteps: [\n \"nhc.config.ts に構文エラーがないか tsc / エディタで確認する\",\n \"ESM の import パスが拡張子付き (.js) になっているか確認する\",\n ],\n },\n \"cli/schema_invalid\": {\n docsAnchor: \"cli-schema_invalid\",\n nextSteps: [\n \"Notion DB のプロパティ型が CLI 対応 (title / richText / select / status / multiSelect / date / number / checkbox / url) のいずれかか確認する\",\n \"未対応プロパティをスキップするか、Notion 側で型を変える\",\n ],\n },\n \"cli/generate_failed\": {\n docsAnchor: \"cli-generate_failed\",\n nextSteps: [\n \"--verbose を付けて再実行し、失敗箇所のスタックトレースを確認する\",\n \"出力先ディレクトリの書き込み権限を確認する\",\n ],\n },\n \"cli/init_failed\": {\n docsAnchor: \"cli-init_failed\",\n nextSteps: [\n \"出力先パスに既存ファイルがある場合は --force を付けるか別パスを指定する\",\n \"親ディレクトリの書き込み権限を確認する\",\n ],\n },\n \"cli/notion_api_failed\": {\n docsAnchor: \"cli-notion_api_failed\",\n nextSteps: [\n \"NOTION_TOKEN がインテグレーションに紐づいているか確認する\",\n \"対象 DB がインテグレーションに接続されているか (Notion DB → … → Connections) 確認する\",\n \"DB 名で解決している場合は完全一致 (前後空白 / 全角半角) を確認する\",\n \"--verbose で Notion API レスポンスの status / code を確認する\",\n ],\n },\n \"cli/env_file_not_found\": {\n docsAnchor: \"cli-env_file_not_found\",\n nextSteps: [\n \"--env-file で指定したパスを実ファイルパスで確認する\",\n \"プロジェクトルートで実行しているか (相対パスは cwd 基準)\",\n ],\n },\n};\n\nfunction lookupBuiltInHelp(code: CMSErrorCode): ErrorHelp | undefined {\n return BUILT_IN_ERROR_HELP[code as BuiltInCMSErrorCode];\n}\n\nexport class CMSError extends Error {\n readonly code: CMSErrorCode;\n override readonly cause?: unknown;\n readonly context: CMSErrorContext;\n /** エラーを解消するための次のアクション(表示用)。 */\n readonly nextSteps?: readonly string[];\n /** 詳細ドキュメントへの URL(表示用)。 */\n readonly docsUrl?: string;\n\n constructor(params: {\n code: CMSErrorCode;\n message: string;\n cause?: unknown;\n context: CMSErrorContext;\n nextSteps?: readonly string[];\n docsUrl?: string;\n }) {\n super(params.message, { cause: params.cause });\n this.name = \"CMSError\";\n this.code = params.code;\n this.cause = params.cause;\n this.context = params.context;\n\n // 呼び出し側が nextSteps / docsUrl を指定していない場合は、組み込みコード向けの既定値で補完する。\n // サードパーティコードや未登録コードは undefined のままにする。\n const help = lookupBuiltInHelp(params.code);\n this.nextSteps = params.nextSteps ?? help?.nextSteps;\n this.docsUrl =\n params.docsUrl ??\n (help ? `${ERROR_DOCS_BASE}#${help.docsAnchor}` : undefined);\n }\n\n /** エラーコードが指定した値と一致するか判定する。 */\n is(code: CMSErrorCode): boolean {\n return this.code === code;\n }\n\n /** エラーコードが指定した名前空間に属するか判定する(例: `\"source/\"`)。 */\n inNamespace(namespace: string): boolean {\n return this.code.startsWith(namespace);\n }\n\n /**\n * nextSteps と docsUrl を含む人間向けの詳細メッセージを返す。\n * エラーダイアログ・ログ出力時に使う。\n */\n format(): string {\n const lines: string[] = [this.message];\n if (this.nextSteps?.length) {\n lines.push(\"\\n次にやること:\");\n for (const step of this.nextSteps) {\n lines.push(` - ${step}`);\n }\n }\n if (this.docsUrl) {\n lines.push(`\\n詳細: ${this.docsUrl}`);\n }\n return lines.join(\"\\n\");\n }\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n return error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n error: unknown,\n namespace: string,\n): error is CMSError {\n return isCMSError(error) && error.code.startsWith(namespace);\n}\n\ntype CMSErrorHandler<R> = (err: CMSError) => R;\n\n/**\n * `CMSError` を switch 式のように分岐して処理するユーティリティ。\n * `_` キーはフォールバック(CMSError 以外 or 未マッチ時)に使われる。\n *\n * @example\n * matchCMSError(err, {\n * \"source/fetch_items_failed\": (e) => handleFetchError(e),\n * _: (e) => { throw e; },\n * });\n */\nexport function matchCMSError<R>(\n error: unknown,\n handlers: Partial<Record<CMSErrorCode, CMSErrorHandler<R>>> & {\n _?: (err: unknown) => R;\n },\n): R | undefined {\n if (!isCMSError(error)) {\n return handlers._?.(error);\n }\n const handler =\n handlers[error.code as CMSErrorCode] ??\n (handlers._ as CMSErrorHandler<R> | undefined);\n return handler?.(error);\n}\n"],"mappings":";;;;;;;AAuFA,MAAM,kBACJ;AAOF,MAAM,sBAA8D;CAClE,uBAAuB;EACrB,YAAY;EACZ,WAAW,CACT,uDACA,+CACF;CACF;CACA,uBAAuB;EACrB,YAAY;EACZ,WAAW,CACT,qCACA,yCACF;CACF;CACA,2BAA2B;EACzB,YAAY;EACZ,WAAW,CACT,6CACA,gCACF;CACF;CACA,8BAA8B;EAC5B,YAAY;EACZ,WAAW,CACT,sDACA,8BACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW,CACT,8CACA,oCACF;CACF;CACA,2BAA2B;EACzB,YAAY;EACZ,WAAW,CACT,gCACA,kDACF;CACF;CACA,8BAA8B;EAC5B,YAAY;EACZ,WAAW,CACT,qEACA,qDACF;CACF;CACA,2BAA2B;EACzB,YAAY;EACZ,WAAW,CACT,mDACA,iDACF;CACF;CACA,8BAA8B;EAC5B,YAAY;EACZ,WAAW,CACT,sDACA,gDACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW;GACT;GACA;GACA;GACA;EACF;CACF;CACA,4BAA4B;EAC1B,YAAY;EACZ,WAAW,CACT,0CACA,8BACF;CACF;CACA,+BAA+B;EAC7B,YAAY;EACZ,WAAW,CACT,0BACA,mFACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW,CACT,+BACA,0CACF;CACF;CACA,6BAA6B;EAC3B,YAAY;EACZ,WAAW,CACT,6DACA,iCACF;CACF;CACA,mBAAmB;EACjB,YAAY;EACZ,WAAW;GACT;GACA;GACA;EACF;CACF;CACA,4BAA4B;EAC1B,YAAY;EACZ,WAAW,CACT,6CACA,2CACF;CACF;CACA,oCAAoC;EAClC,YAAY;EACZ,WAAW,CACT,8CACA,0CACF;CACF;CACA,mBAAmB;EACjB,YAAY;EACZ,WAAW,CACT,oDACA,kGACF;CACF;CACA,yBAAyB;EACvB,YAAY;EACZ,WAAW,CACT,0DACA,6CACF;CACF;CACA,yBAAyB;EACvB,YAAY;EACZ,WAAW,CACT,2DACA,gDACF;CACF;CACA,8BAA8B;EAC5B,YAAY;EACZ,WAAW,CACT,oDACA,gDACF;CACF;CACA,sBAAsB;EACpB,YAAY;EACZ,WAAW,CACT,6DACA,4DACF;CACF;CACA,0BAA0B;EACxB,YAAY;EACZ,WAAW,CACT,4CACA,yCACF;CACF;CACA,sBAAsB;EACpB,YAAY;EACZ,WAAW,CACT,4HACA,iCACF;CACF;CACA,uBAAuB;EACrB,YAAY;EACZ,WAAW,CACT,yCACA,uBACF;CACF;CACA,mBAAmB;EACjB,YAAY;EACZ,WAAW,CACT,4CACA,qBACF;CACF;CACA,yBAAyB;EACvB,YAAY;EACZ,WAAW;GACT;GACA;GACA;GACA;EACF;CACF;CACA,0BAA0B;EACxB,YAAY;EACZ,WAAW,CACT,mCACA,kCACF;CACF;AACF;AAEA,SAAS,kBAAkB,MAA2C;CACpE,OAAO,oBAAoB;AAC7B;AAEA,IAAa,WAAb,cAA8B,MAAM;CAClC;CACA;CACA;;CAEA;;CAEA;CAEA,YAAY,QAOT;EACD,MAAM,OAAO,SAAS,EAAE,OAAO,OAAO,MAAM,CAAC;EAC7C,KAAK,OAAO;EACZ,KAAK,OAAO,OAAO;EACnB,KAAK,QAAQ,OAAO;EACpB,KAAK,UAAU,OAAO;EAItB,MAAM,OAAO,kBAAkB,OAAO,IAAI;EAC1C,KAAK,YAAY,OAAO,aAAa,MAAM;EAC3C,KAAK,UACH,OAAO,YACN,OAAO,GAAG,gBAAgB,GAAG,KAAK,eAAe,KAAA;CACtD;;CAGA,GAAG,MAA6B;EAC9B,OAAO,KAAK,SAAS;CACvB;;CAGA,YAAY,WAA4B;EACtC,OAAO,KAAK,KAAK,WAAW,SAAS;CACvC;;;;;CAMA,SAAiB;EACf,MAAM,QAAkB,CAAC,KAAK,OAAO;EACrC,IAAI,KAAK,WAAW,QAAQ;GAC1B,MAAM,KAAK,WAAW;GACtB,KAAK,MAAM,QAAQ,KAAK,WACtB,MAAM,KAAK,OAAO,MAAM;EAE5B;EACA,IAAI,KAAK,SACP,MAAM,KAAK,SAAS,KAAK,SAAS;EAEpC,OAAO,MAAM,KAAK,IAAI;CACxB;AACF;AAEA,SAAgB,WAAW,OAAmC;CAC5D,OAAO,iBAAiB;AAC1B;;AAGA,SAAgB,sBACd,OACA,WACmB;CACnB,OAAO,WAAW,KAAK,KAAK,MAAM,KAAK,WAAW,SAAS;AAC7D;;;;;;;;;;;AAcA,SAAgB,cACd,OACA,UAGe;CACf,IAAI,CAAC,WAAW,KAAK,GACnB,OAAO,SAAS,IAAI,KAAK;CAK3B,QAFE,SAAS,MAAM,SACd,SAAS,KACK,KAAK;AACxB"}
package/dist/hooks.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
- import { a as CMSHooks, i as Logger, t as CMSPlugin } from "./plugin-BmrOz8T6.mjs";
2
+ import { a as CMSHooks, i as Logger, t as CMSPlugin } from "./plugin-BPhaVO6-.mjs";
3
3
 
4
4
  //#region src/hooks.d.ts
5
5
  /**
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as CachedItemMeta, c as ContentResult, i as CachedItemList, l as ImageRef, n as CMSSchemaProperties, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem, u as InlineNode } from "./content-DwsfWZao.mjs";
2
2
  import { a as ImageCacheOps, c as InvalidateScope, d as WebhookConfig, i as DocumentCacheOps, l as PropertyDef, n as CacheAdapterStats, o as DataSource, r as CacheAreaStats, s as InvalidateKind, t as CacheAdapter, u as PropertyMap } from "./cache-v9jTMnYd.mjs";
3
- import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-DcNErfYk.mjs";
4
- import { a as CMSHooks, i as Logger, n as definePlugin, o as MaybePromise, r as LogContext, t as CMSPlugin } from "./plugin-BmrOz8T6.mjs";
5
- import { _ as MergeSourceCollections, a as DEFAULT_RATE_LIMITER, c as RateLimiterConfig, d as RendererPluginList, f as SWRConfig, g as CMSSources, i as CreateClientOptions, l as RenderOptions, m as defineCollection, n as CollectionsConfig, o as InferCollectionItem, p as StrictCollectionDef, r as ContentConfig, s as LogLevel, u as RendererFn } from "./config-i99tKRhN.mjs";
3
+ import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-DPD5yq9S.mjs";
4
+ import { a as CMSHooks, i as Logger, n as definePlugin, o as MaybePromise, r as LogContext, t as CMSPlugin } from "./plugin-BPhaVO6-.mjs";
5
+ import { _ as MergeSourceCollections, a as DEFAULT_RATE_LIMITER, c as RateLimiterConfig, d as RendererPluginList, f as SWRConfig, g as CMSSources, i as CreateClientOptions, l as RenderOptions, m as defineCollection, n as CollectionsConfig, o as InferCollectionItem, p as StrictCollectionDef, r as ContentConfig, s as LogLevel, u as RendererFn } from "./config-Ccs0RxoN.mjs";
6
6
  import { MemoryCacheOptions, memoryCache } from "./cache/memory.mjs";
7
7
  import { mergeHooks, mergeLoggers } from "./hooks.mjs";
8
8
  import { NodePresetOptions, nodePreset } from "./preset/node.mjs";
@@ -175,6 +175,10 @@ interface HandlerOptions {
175
175
  imagesPath?: string;
176
176
  /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */
177
177
  revalidatePath?: string;
178
+ /** バージョン照会 (peekVersion) のパス (basePath 相対)。デフォルト `/versions`。 */
179
+ versionsPath?: string;
180
+ /** 更新チェック (check) のパス (basePath 相対)。デフォルト `/check`。 */
181
+ checkPath?: string;
178
182
  /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */
179
183
  webhookSecret?: string;
180
184
  /** デフォルト実装を無効化する場合 true。 */
@@ -185,19 +189,37 @@ interface HandlerAdapter {
185
189
  imageCache: ImageCacheOps;
186
190
  /**
187
191
  * 指定コレクションの DataSource.parseWebhook を呼ぶ。
188
- * 未知コレクション → `webhook/unknown_collection` CMSError
192
+ * 未知コレクション → `handler/unknown_collection` CMSError
189
193
  * parseWebhook 未実装 → `webhook/not_implemented` CMSError
190
194
  */
191
195
  parseWebhookFor(collection: string, req: Request, webhookSecret: string | undefined): Promise<InvalidateScope>;
192
196
  revalidate(scope: InvalidateScope): Promise<void>;
197
+ /**
198
+ * 指定コレクション / slug の `peekVersion`(KV メタのみ、Notion API 非呼び出し)を返す。
199
+ * キャッシュ未登録なら `null`。未知コレクション → `handler/unknown_collection` CMSError。
200
+ */
201
+ peekVersionFor(collection: string, slug: string): Promise<{
202
+ notionUpdatedAt: string;
203
+ cachedAt: number;
204
+ } | null>;
205
+ /**
206
+ * 指定コレクション / slug を `currentVersion` と比較し(Notion を実照会)、
207
+ * 差分があればキャッシュを更新して `stale` を返す。
208
+ * アイテムが存在しない場合は `null`。未知コレクション → `handler/unknown_collection` CMSError。
209
+ */
210
+ checkFor(collection: string, slug: string, currentVersion: string): Promise<{
211
+ stale: boolean;
212
+ } | null>;
193
213
  }
194
214
  /**
195
215
  * Web Standard な Request → Response ルーター。
196
216
  * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。
197
217
  *
198
218
  * ルート:
199
- * - GET `{basePath}/images/:hash` — 画像プロキシ
200
- * - POST `{basePath}/revalidate/:collection` Webhook 受信 + $revalidate()
219
+ * - GET `{basePath}/images/:hash` — 画像プロキシ
220
+ * - GET `{basePath}/versions/:collection/:slug` peekVersion(更新検知ポーリング)
221
+ * - GET/POST `{basePath}/check/:collection/:slug?v=` — check(更新を実照会してキャッシュ更新)
222
+ * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()
201
223
  */
202
224
  declare function createHandler(adapter: HandlerAdapter, opts?: HandlerOptions): (req: Request) => Promise<Response>;
203
225
  //#endregion
package/dist/index.mjs CHANGED
@@ -805,30 +805,61 @@ function makeComparator(sort) {
805
805
  const DEFAULT_OPTS = {
806
806
  basePath: "/api/cms",
807
807
  imagesPath: "/images",
808
- revalidatePath: "/revalidate"
808
+ revalidatePath: "/revalidate",
809
+ versionsPath: "/versions",
810
+ checkPath: "/check"
809
811
  };
810
- /** Webhook 系の CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */
811
- function webhookErrorStatus(code) {
812
+ const JSON_HEADERS = { "content-type": "application/json" };
813
+ /** CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */
814
+ function httpStatusForError(code) {
812
815
  if (code === "webhook/signature_invalid") return 401;
813
816
  if (code === "webhook/not_implemented") return 501;
814
817
  if (code === "webhook/unknown_collection") return 404;
815
818
  if (code === "webhook/payload_invalid") return 400;
819
+ if (code === "handler/unknown_collection") return 404;
816
820
  return null;
817
821
  }
822
+ /** CMSError を HTTP ステータスへ写像できれば JSON レスポンスにする。未対応なら null。 */
823
+ function errorResponse(err) {
824
+ if (!isCMSError(err)) return null;
825
+ const status = httpStatusForError(err.code);
826
+ if (status === null) return null;
827
+ return new Response(JSON.stringify({
828
+ ok: false,
829
+ code: err.code
830
+ }), {
831
+ status,
832
+ headers: JSON_HEADERS
833
+ });
834
+ }
835
+ /** `:collection/:slug` を分解する。どちらか欠ける場合は null。 */
836
+ function splitCollectionSlug(sub) {
837
+ const slashIndex = sub.indexOf("/");
838
+ if (slashIndex <= 0 || slashIndex === sub.length - 1) return null;
839
+ return {
840
+ collection: sub.slice(0, slashIndex),
841
+ slug: sub.slice(slashIndex + 1)
842
+ };
843
+ }
818
844
  /**
819
845
  * Web Standard な Request → Response ルーター。
820
846
  * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。
821
847
  *
822
848
  * ルート:
823
- * - GET `{basePath}/images/:hash` — 画像プロキシ
824
- * - POST `{basePath}/revalidate/:collection` Webhook 受信 + $revalidate()
849
+ * - GET `{basePath}/images/:hash` — 画像プロキシ
850
+ * - GET `{basePath}/versions/:collection/:slug` peekVersion(更新検知ポーリング)
851
+ * - GET/POST `{basePath}/check/:collection/:slug?v=` — check(更新を実照会してキャッシュ更新)
852
+ * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()
825
853
  */
826
854
  function createHandler(adapter, opts = {}) {
827
855
  const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);
828
856
  const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;
829
857
  const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;
858
+ const versionsPath = opts.versionsPath ?? DEFAULT_OPTS.versionsPath;
859
+ const checkPath = opts.checkPath ?? DEFAULT_OPTS.checkPath;
830
860
  return async (req) => {
831
- const path = new URL(req.url).pathname;
861
+ const url = new URL(req.url);
862
+ const path = url.pathname;
832
863
  if (!path.startsWith(basePath)) return new Response("Not Found", { status: 404 });
833
864
  const rel = path.slice(basePath.length) || "/";
834
865
  if (req.method === "GET" && rel.startsWith(`${imagesPath}/`)) {
@@ -841,6 +872,56 @@ function createHandler(adapter, opts = {}) {
841
872
  headers.set("cache-control", "public, max-age=31536000, immutable");
842
873
  return new Response(object.data, { headers });
843
874
  }
875
+ if (req.method === "GET" && rel.startsWith(`${versionsPath}/`)) {
876
+ const target = splitCollectionSlug(rel.slice(versionsPath.length + 1));
877
+ if (!target) return new Response(JSON.stringify({
878
+ ok: false,
879
+ reason: "collection and slug required"
880
+ }), {
881
+ status: 400,
882
+ headers: JSON_HEADERS
883
+ });
884
+ try {
885
+ const version = await adapter.peekVersionFor(target.collection, target.slug);
886
+ return new Response(JSON.stringify(version), {
887
+ status: 200,
888
+ headers: JSON_HEADERS
889
+ });
890
+ } catch (err) {
891
+ const res = errorResponse(err);
892
+ if (res) return res;
893
+ throw err;
894
+ }
895
+ }
896
+ if ((req.method === "GET" || req.method === "POST") && rel.startsWith(`${checkPath}/`)) {
897
+ const target = splitCollectionSlug(rel.slice(checkPath.length + 1));
898
+ const currentVersion = url.searchParams.get("v");
899
+ if (!target || !currentVersion) return new Response(JSON.stringify({
900
+ ok: false,
901
+ reason: "collection, slug and ?v= are required"
902
+ }), {
903
+ status: 400,
904
+ headers: JSON_HEADERS
905
+ });
906
+ try {
907
+ const result = await adapter.checkFor(target.collection, target.slug, currentVersion);
908
+ if (result === null) return new Response(JSON.stringify({
909
+ ok: false,
910
+ reason: "not found"
911
+ }), {
912
+ status: 404,
913
+ headers: JSON_HEADERS
914
+ });
915
+ return new Response(JSON.stringify({ stale: result.stale }), {
916
+ status: 200,
917
+ headers: JSON_HEADERS
918
+ });
919
+ } catch (err) {
920
+ const res = errorResponse(err);
921
+ if (res) return res;
922
+ throw err;
923
+ }
924
+ }
844
925
  if (req.method === "POST" && rel.startsWith(`${revalidatePath}/`)) {
845
926
  const collection = rel.slice(revalidatePath.length + 1);
846
927
  if (!collection || collection.includes("/")) return new Response(JSON.stringify({
@@ -848,7 +929,7 @@ function createHandler(adapter, opts = {}) {
848
929
  reason: "collection required"
849
930
  }), {
850
931
  status: 400,
851
- headers: { "content-type": "application/json" }
932
+ headers: JSON_HEADERS
852
933
  });
853
934
  try {
854
935
  const scope = await adapter.parseWebhookFor(collection, req, opts.webhookSecret);
@@ -858,19 +939,11 @@ function createHandler(adapter, opts = {}) {
858
939
  scope
859
940
  }), {
860
941
  status: 200,
861
- headers: { "content-type": "application/json" }
942
+ headers: JSON_HEADERS
862
943
  });
863
944
  } catch (err) {
864
- if (isCMSError(err)) {
865
- const status = webhookErrorStatus(err.code);
866
- if (status !== null) return new Response(JSON.stringify({
867
- ok: false,
868
- code: err.code
869
- }), {
870
- status,
871
- headers: { "content-type": "application/json" }
872
- });
873
- }
945
+ const res = errorResponse(err);
946
+ if (res) return res;
874
947
  throw err;
875
948
  }
876
949
  }
@@ -1121,7 +1194,34 @@ function createClient(opts) {
1121
1194
  });
1122
1195
  return ds.parseWebhook(req, { secret: webhookSecret });
1123
1196
  },
1124
- revalidate: (scope) => globalOps.invalidate(scope)
1197
+ revalidate: (scope) => globalOps.invalidate(scope),
1198
+ peekVersionFor(collection, slug) {
1199
+ const client = collections[collection];
1200
+ if (!client) throw new CMSError({
1201
+ code: "handler/unknown_collection",
1202
+ message: `Unknown collection: ${collection}`,
1203
+ context: {
1204
+ operation: "peekVersionFor",
1205
+ collection,
1206
+ slug
1207
+ }
1208
+ });
1209
+ return client.peekVersion(slug);
1210
+ },
1211
+ async checkFor(collection, slug, currentVersion) {
1212
+ const client = collections[collection];
1213
+ if (!client) throw new CMSError({
1214
+ code: "handler/unknown_collection",
1215
+ message: `Unknown collection: ${collection}`,
1216
+ context: {
1217
+ operation: "checkFor",
1218
+ collection,
1219
+ slug
1220
+ }
1221
+ });
1222
+ const result = await client.check(slug, currentVersion);
1223
+ return result === null ? null : { stale: result.stale };
1224
+ }
1125
1225
  }, handlerOpts);
1126
1226
  },
1127
1227
  getCachedImage(hash) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/cache/noop.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/cms.ts","../src/page-index.ts","../src/types/config.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/markdown-html` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/markdown-html\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/markdown-html のロードにも失敗しました。\" +\n \" createClient の renderer オプションを指定するか、@notion-headless-cms/markdown-html をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n // markdown 戦略のように loadNotionBlocks 未対応の場合は `source/blocks_unsupported`\n // を吸収して undefined にする (ページ描画は markdown 経路で代替する想定)。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err) && err.is(\"source/blocks_unsupported\")) {\n notionBlocks = undefined;\n } else if (isCMSError(err)) {\n throw err;\n } else {\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n /**\n * リトライ前に呼ばれるフック。`attempt` は 1 始まり、`status` はリトライ対象の HTTP ステータス、\n * `delayMs` は次回試行までの実際の待機時間 (ジッター反映後)。\n * 既存呼び出しは `attempt` / `status` だけで動くよう `delayMs` は省略可。\n */\n onRetry?: (attempt: number, status: number, delayMs?: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n // onRetry には attempt / status と次回待機時間 (ms) を渡す。\n // 呼び出し側のロガーが `LogContext.backoffMs` として記録できる。\n config.onRetry?.(attempt + 1, status, delay);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する (item: `{collection}:{slug}` / list: `{collection}`)。\n *\n * 各 cache adapter は内部で独自のキー戦略を持つが、ログ出力や差分再計算で\n * 同一表現が必要になるため core 側にも公開する。\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名。`source.properties[slugField].notion` を\n * Notion プロパティ名として `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n // TTL 切れはブロッキングで再取得する (stale を返さない要件)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: 即時返却しつつバックグラウンドで差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n // 保存だけはバックグラウンド可: ユーザー向けレスポンスを早めに返す\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async peekVersion(\n slug: string,\n ): Promise<{ notionUpdatedAt: string; cachedAt: number } | null> {\n const meta = await this.ctx.docCache.getMeta<T>(this.ctx.collection, slug);\n if (!meta) return null;\n return { notionUpdatedAt: meta.notionUpdatedAt, cachedAt: meta.cachedAt };\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /** 本文キャッシュ。メタとの整合 (`notionUpdatedAt`) が崩れていれば再生成して書き戻す。 */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n if (cached && cached.notionUpdatedAt === expected) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけ再生成する。エラーは onSwrError フックに通知して握り潰す。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // html() / markdown() / blocks() を同じアイテムから複数回呼んでも I/O は 1 回に集約する\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => {\n const notionBlocks = (await loadPayload()).notionBlocks;\n // markdown 戦略やフェッチャ未設定だと常に undefined になり、\n // React レンダリング側で原因が分からない無言失敗になるため一度だけ案内する。\n if (notionBlocks === undefined) this.warnMissingNotionBlocks();\n return notionBlocks;\n },\n }) as ItemWithContent<T>;\n }\n\n // notionBlocks() が undefined を返したときの案内を CollectionClient 単位で一度だけ出す。\n private notionBlocksWarned = false;\n\n private warnMissingNotionBlocks(): void {\n if (this.notionBlocksWarned) return;\n this.notionBlocksWarned = true;\n this.ctx.logger?.warn?.(\n \"notionBlocks() が undefined を返しました。BlockObjectResponse ツリーは blocks 戦略でのみ得られます (notionSource の fetch 未指定の既定でも有効)。markdownFetcher を使用中の場合は markdown→React の Renderer を使うか、blocksFetcher() に切り替えてください。\",\n { collection: this.ctx.collection, operation: \"notionBlocks\" },\n );\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n // TTL 切れはブロッキングで再取得する (find と同じ理由)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else {\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、cachedAt を更新\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status, delayMs) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", {\n attempt,\n status,\n backoffMs: delayMs,\n });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number, delayMs?: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n backoffMs: delayMs,\n });\n },\n };\n\n // PropertyMap が解決できる場合は単一プロパティ filter で 1 ページだけ取得する (高速)\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // PropertyMap 未提供 / findByProp 未実装の DataSource 向けフォールバック\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順 (未設定なら lastEditedTime 降順) でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/** Webhook 系の CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers, withTraceId } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CacheAdapterStats,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateClientOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\nimport type {\n CMSAdapter,\n CMSSources,\n MergeSourceCollections,\n} from \"./types/sources\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/**\n * `cms.stats()` が返す集約済みキャッシュ統計。\n * 各 adapter の `stats()` 戻り値をそのまま配列で保持しつつ、ヒット率を算出する。\n */\nexport interface CMSStats {\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n traceId: string;\n /** ドキュメントキャッシュの集計 (`handles: [\"document\"]` の adapter の `stats()` 戻り値)。 */\n document?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n /** 0〜1。`hits + misses === 0` のときは 0。 */\n hitRate: number;\n };\n /** 画像キャッシュの集計 (`handles: [\"image\"]` の adapter の `stats()` 戻り値)。 */\n image?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n hitRate: number;\n };\n}\n\nexport interface CMSGlobalOps {\n readonly collections: readonly string[];\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n readonly traceId: string;\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard な Request/Response ベースのルートハンドラ (画像プロキシ + webhook)。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n readonly imageProxyBase: string;\n /**\n * ドキュメント / 画像キャッシュのヒット・ミス・サイズを集約して返す。\n * 各キャッシュアダプタの `stats()` を呼ぶだけで副作用はない。\n * `stats()` を実装していない adapter は集計から除外される (noop など)。\n */\n stats(): Promise<CMSStats>;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n docAdapter: CacheAdapter | undefined;\n img: ImageCacheOps;\n imgName: string;\n imgAdapter: CacheAdapter | undefined;\n hasImg: boolean;\n}\n\n/**\n * adapter の `handles` を見て先勝ちで document / image を割り当てる。未指定は両方 noop。\n * `cms.stats()` から元 adapter の `stats()` を呼びたいので、解決元 adapter も保持する。\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let docAdapter: CacheAdapter | undefined;\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let imgAdapter: CacheAdapter | undefined;\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docAdapter = adapter;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgAdapter = adapter;\n imgFound = true;\n }\n }\n\n return {\n doc,\n docName,\n docAdapter,\n img,\n imgName,\n imgAdapter,\n hasImg: imgFound,\n };\n}\n\n/**\n * 衝突しにくい短い trace ID を生成する。`{epoch36}-{rand36}` の 10〜12 文字程度。\n * core はゼロ依存ルールに従い node:crypto を静的 import しないため、Math.random ベースで十分。\n */\nfunction generateTraceId(): string {\n const t = Date.now().toString(36);\n const r = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, \"0\");\n return `${t}-${r}`;\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * CMS クライアントを生成する。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n *\n * const posts = await cms.posts.list();\n */\nexport function createClient<S extends CMSSources = CMSSources>(\n opts: CreateClientOptions<S>,\n): CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n> {\n // sources の各アダプタが持つ collections をマージする\n const collectionsInput: CollectionsConfig = {};\n if (opts.sources) {\n for (const adapter of Object.values(\n opts.sources as unknown as Record<string, CMSAdapter | undefined>,\n )) {\n if (adapter) Object.assign(collectionsInput, adapter.collections);\n }\n }\n\n if (Object.keys(collectionsInput).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createClient: sources に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createClient\" },\n nextSteps: [\n \"notionSource({ schema, token }) を sources.notion に渡す\",\n \"`nhc generate` でスキーマを生成してから import する\",\n ],\n docsUrl:\n \"https://github.com/kjfsm/notion-headless-cms/blob/main/docs/quickstart.md\",\n });\n }\n\n for (const [name, def] of Object.entries(collectionsInput)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\"notionSource(...) を sources に渡しているか確認する\"],\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\n `nhc.config.ts の ${name} コレクションに slugField を設定する`,\n ],\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n // plugin logger と createClient 引数の logger を合成し、その上でクライアント単位の\n // traceId をログコンテキストに自動付与する。\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const traceId = generateTraceId();\n const tracedLogger = withTraceId(baseLogger, traceId);\n const logger = opts.logLevel\n ? applyLogLevel(tracedLogger, opts.logLevel)\n : tracedLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: string[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(collectionsInput)) {\n collectionNames.push(name);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n traceId,\n async stats(): Promise<CMSStats> {\n const stats: CMSStats = { traceId };\n // doc / img それぞれ、resolveCache が選んだ adapter にだけ stats() を要求する。\n // 同じ adapter が両方を担当している場合 (memoryCache など) は 1 回呼ぶだけで足りる。\n const adapterCache = new Map<CacheAdapter, Promise<CacheAdapterStats>>();\n const ensure = (\n adapter: CacheAdapter | undefined,\n ): Promise<CacheAdapterStats> | undefined => {\n if (!adapter?.stats) return undefined;\n const cached = adapterCache.get(adapter);\n if (cached) return cached;\n const fresh = adapter.stats();\n adapterCache.set(adapter, fresh);\n return fresh;\n };\n const docPromise = ensure(cacheRes.docAdapter);\n const imgPromise = ensure(cacheRes.imgAdapter);\n const computeHitRate = (h: number, m: number): number =>\n h + m === 0 ? 0 : h / (h + m);\n if (docPromise) {\n const docStats = await docPromise;\n if (docStats.doc) {\n stats.document = {\n adapter: docStats.name ?? cacheRes.docName,\n hits: docStats.doc.hits,\n misses: docStats.doc.misses,\n entries: docStats.doc.entries,\n sizeBytes: docStats.doc.sizeBytes,\n hitRate: computeHitRate(docStats.doc.hits, docStats.doc.misses),\n };\n }\n }\n if (imgPromise) {\n const imgStats = await imgPromise;\n if (imgStats.img) {\n stats.image = {\n adapter: imgStats.name ?? cacheRes.imgName,\n hits: imgStats.img.hits,\n misses: imgStats.img.misses,\n entries: imgStats.img.entries,\n sizeBytes: imgStats.img.sizeBytes,\n hitRate: computeHitRate(imgStats.img.hits, imgStats.img.misses),\n };\n }\n }\n return stats;\n },\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = collectionsInput[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n >;\n}\n","import type { CollectionClient } from \"./types/collection\";\nimport type { BaseContentItem } from \"./types/content\";\n\n/** 逆引きの解決先。Notion ページ ID から自サイトの位置を引く。 */\nexport interface PageIndexEntry {\n /** 所属コレクション名。 */\n collection: string;\n /** URL キー。 */\n slug: string;\n /** ページ名(表示テキスト用)。 */\n title?: string | null;\n}\n\n/** `buildPageIndex` が返す逆引きマップ。キーは `normalizePageId` 済みの pageId。 */\nexport type PageIndex = Map<string, PageIndexEntry>;\n\n/**\n * `buildPageIndex` / `buildPageLinkMap` が必要とする最小の CMS 形状。\n * `collections` でコレクション名を列挙し、各名でコレクションクライアントへアクセスする。\n * `CMSClient` はコレクションを自身に spread しているため構造的に満たす。\n */\nexport interface PageIndexSource {\n readonly collections: readonly string[];\n}\n\nexport interface BuildPageIndexOptions {\n /** 走査対象のコレクション名。未指定なら `source.collections` 全件。 */\n collections?: readonly string[];\n}\n\n/**\n * Notion ページ ID を比較用に正規化する。\n * Notion の ID は文脈によりダッシュの有無・大文字小文字が揺れるため、\n * ダッシュ除去 + 小文字化して突き合わせる。\n *\n * react-renderer 側も同一実装で `pageLinks` を引くため、変更時は両方を揃えること。\n */\nexport function normalizePageId(id: string): string {\n return id.replace(/-/g, \"\").toLowerCase();\n}\n\nfunction asCollectionClient(\n source: PageIndexSource,\n name: string,\n): CollectionClient<BaseContentItem> | undefined {\n const client = (source as unknown as Record<string, unknown>)[name] as\n | CollectionClient<BaseContentItem>\n | undefined;\n // collections に名前はあっても spread されていない / list を持たない値は無視する。\n if (!client || typeof client.list !== \"function\") return undefined;\n return client;\n}\n\n/**\n * 全コレクションを `list()` で走査し、pageId → {collection, slug, title} の逆引きマップを構築する。\n * Notion 内部リンク(link_to_page / page mention など)を自サイト URL へ解決するための材料。\n *\n * `list()` は SWR ドキュメントキャッシュ経由のためウォーム後は安価。\n */\nexport async function buildPageIndex(\n source: PageIndexSource,\n opts?: BuildPageIndexOptions,\n): Promise<PageIndex> {\n // collections を持たない / 異常な source でもクラッシュさせず空マップを返す。\n const names = opts?.collections ?? source.collections;\n const index: PageIndex = new Map();\n if (\n !names ||\n typeof (names as Iterable<string>)[Symbol.iterator] !== \"function\"\n ) {\n return index;\n }\n for (const name of names) {\n const client = asCollectionClient(source, name);\n if (!client) continue;\n const items = await client.list();\n if (!Array.isArray(items)) continue;\n for (const item of items) {\n // pageId は一意なので衝突しない前提。万一重複しても先勝ちで保持する。\n const key = normalizePageId(item.id);\n if (!index.has(key)) {\n index.set(key, {\n collection: name,\n slug: item.slug,\n title: item.title,\n });\n }\n }\n }\n return index;\n}\n\n/** 解決済みの内部リンク。`href` は構築時に決まり、`title` は表示テキスト用。 */\nexport interface ResolvedPageLink {\n href: string;\n title?: string | null;\n}\n\n/**\n * 正規化済み pageId → 解決済みリンクのプレーンマップ。\n * 関数ではなくプレーンオブジェクトなので、loader / RSC(Server Component → Client\n * Component)境界を越えて `<NotionRenderer pageLinks={...} />` にそのまま渡せる。\n */\nexport type PageLinkMap = Record<string, ResolvedPageLink>;\n\nexport interface BuildPageLinkMapOptions extends BuildPageIndexOptions {\n /**\n * エントリから URL を組み立てる関数。既定は `/${collection}/${slug}`。\n * 単一コレクションで `/${slug}` にしたい場合などに上書きする。\n */\n url?: (entry: PageIndexEntry, pageId: string) => string;\n /** 事前構築済みインデックス。指定するとリクエストごとの再構築を省ける。 */\n index?: PageIndex;\n}\n\nconst defaultUrl = (entry: PageIndexEntry): string =>\n `/${entry.collection}/${entry.slug}`;\n\n/**\n * Notion 内部リンクを「正規化 pageId → {href, title}」のプレーンマップに解決する。\n * サーバ側(loader / RSC / route handler)で 1 回構築し、`<NotionRenderer pageLinks={...} />`\n * に渡す。プレーンオブジェクトなのでシリアライズ境界(RSC / loader)を越えられる。\n *\n * @example\n * const pageLinks = await buildPageLinkMap(cms);\n * <NotionRenderer blocks={blocks} pageLinks={pageLinks} />;\n */\nexport async function buildPageLinkMap(\n source: PageIndexSource,\n opts?: BuildPageLinkMapOptions,\n): Promise<PageLinkMap> {\n const index = opts?.index ?? (await buildPageIndex(source, opts));\n const toUrl = opts?.url ?? defaultUrl;\n const map: PageLinkMap = {};\n for (const [key, entry] of index) {\n map[key] = { href: toUrl(entry, key), title: entry.title };\n }\n return map;\n}\n","import type { CacheAdapter } from \"./cache\";\nimport type { BaseContentItem } from \"./content\";\nimport type { DataSource } from \"./data-source\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\nimport type { CMSPlugin } from \"./plugin\";\nimport type { CMSSources } from \"./sources\";\n\n/** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/**\n * renderer プラグインの不透明型。\n * core は unified / remark / rehype に依存せず、このリストをそのまま renderer に渡すだけ。\n */\nexport type RendererPluginList = unknown[];\n\n/**\n * render() オプション。core は renderer の実装を知らず、この型だけを扱う。\n * @notion-headless-cms/markdown-html の renderMarkdown() はこのシグネチャと構造的に互換。\n */\nexport interface RenderOptions {\n imageProxyBase?: string;\n cacheImage?: (url: string) => Promise<string>;\n remarkPlugins?: RendererPluginList;\n rehypePlugins?: RendererPluginList;\n}\n\n/** カスタムレンダラー関数の型。デフォルトは @notion-headless-cms/markdown-html の renderMarkdown。 */\nexport type RendererFn = (\n markdown: string,\n opts?: RenderOptions,\n) => Promise<string>;\n\n/** レンダリング・コンテンツ処理設定。 */\nexport interface ContentConfig {\n /** 追加する remark プラグイン。 */\n remarkPlugins?: RendererPluginList;\n /** 追加する rehype プラグイン。 */\n rehypePlugins?: RendererPluginList;\n}\n\n/** SWR(Stale-While-Revalidate)設定。 */\nexport interface SWRConfig {\n /** SWR の有効期間 (ミリ秒)。未設定時は TTL なし(失効まで stale を返す)。 */\n ttlMs?: number;\n}\n\n/**\n * `RateLimiterConfig` のデフォルト値 (Issue #313 / M2)。\n * 型からだけでは見えない既定値を表面化することで、IDE 補完と\n * preset の対称化 (`nodePreset` / `cloudflarePreset` / `nextPreset`) で\n * 同じ既定が適用されることを保証する。\n */\nexport const DEFAULT_RATE_LIMITER: Required<RateLimiterConfig> = {\n maxConcurrent: 3,\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n};\n\n/** レートリミット・リトライ設定。既定値は {@link DEFAULT_RATE_LIMITER}。 */\nexport interface RateLimiterConfig {\n /** 同時実行数の上限。デフォルト: 3 */\n maxConcurrent?: number;\n /** リトライ対象の HTTP ステータスコード。デフォルト: [429, 502, 503] */\n retryOn?: number[];\n /** 最大リトライ回数。デフォルト: 4 */\n maxRetries?: number;\n /** リトライ時の基準待機時間(ミリ秒)。デフォルト: 1000 */\n baseDelayMs?: number;\n}\n\n/**\n * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createClient` に渡される。\n *\n * `source` は notion-orm 等の DataSource 実装。\n * `slugField` / `statusField` は TS フィールド名 (DataSource の `properties` キーと一致)。\n */\nexport interface CollectionDef<T extends BaseContentItem = BaseContentItem> {\n /** Notion etc. のデータソース実装。 */\n source: DataSource<T>;\n /** slug として使う TS フィールド名 (必須)。`source.properties[slugField]` で Notion プロパティ名を解決する。 */\n slugField: string;\n /** ステータスとして使う TS フィールド名。 */\n statusField?: string;\n /** 公開扱いするステータス値。`list()` のデフォルト絞り込みに使う。 */\n publishedStatuses?: readonly string[];\n /** アクセス許可するステータス値。`get()` の閲覧可否判定に使う。 */\n accessibleStatuses?: readonly string[];\n /** コレクション固有のライフサイクルフック。グローバル hooks の後に実行される。 */\n hooks?: CMSHooks<T>;\n}\n\n/**\n * `CollectionDef` の strict 版。`slugField` / `statusField` が `keyof T & string` で\n * 型ガードされており、誤フィールド名は型エラーになる (Issue #314 / M3)。\n * CLI 生成スキーマや `defineCollection<T>()` 経由で利用される。\n */\nexport interface StrictCollectionDef<T extends BaseContentItem>\n extends Omit<CollectionDef<T>, \"slugField\" | \"statusField\"> {\n /** slug として使う TS フィールド名。`keyof T` で型ガードされる。 */\n slugField: keyof T & string;\n /** ステータスとして使う TS フィールド名。`keyof T` で型ガードされる。 */\n statusField?: keyof T & string;\n}\n\n/**\n * 型推論ヘルパー: `T` を明示してコレクション定義を作る。`slugField` / `statusField` は\n * `keyof T & string` で補完・型ガードされ、誤フィールド名 (例: `\"slag\"`) で型エラーになる\n * (Issue #314 / M3)。CLI 生成 `nhc.schema.ts` で利用される。\n *\n * @example\n * ```ts\n * type PostItem = BaseContentItem & { authorName?: string };\n * const posts = defineCollection<PostItem>({\n * source: notionSource(...),\n * slugField: \"slug\", // OK\n * statusField: \"status\", // OK\n * // statusField: \"stat\", // 型エラー\n * });\n * ```\n */\nexport function defineCollection<T extends BaseContentItem>(\n def: StrictCollectionDef<T>,\n): CollectionDef<T> {\n // StrictCollectionDef は CollectionDef の slugField/statusField を絞ったサブ型なので\n // 構造的に CollectionDef<T> へ代入可能。型システム上は再構成のため as 経由。\n return def as unknown as CollectionDef<T>;\n}\n\n/**\n * `createClient({ collections })` の map 型。\n * キーがコレクション名、値が `CollectionDef<T>`。\n */\nexport type CollectionsConfig = Record<string, CollectionDef<BaseContentItem>>;\n\n/** `CollectionsConfig` から各 T を抽出するユーティリティ型。 */\nexport type InferCollectionItem<C> =\n C extends CollectionDef<infer T> ? T : BaseContentItem;\n\n/**\n * `createClient()` の入力。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n */\nexport interface CreateClientOptions<S extends CMSSources = CMSSources> {\n /** データソースアダプター (`@notion-headless-cms/notion-source` 等) のマップ。 */\n sources?: S;\n /**\n * キャッシュアダプタ (配列)。未指定時はキャッシュなし。\n * - `memoryCache()` のように doc + image 両方を担当するもの\n * - `r2Cache()` (image のみ)、`kvCache()` (doc のみ) のように片側のみ担当するもの\n * - 複数 adapter を配列で組み合わせると、各 adapter の `handles` で振り分けられる\n */\n cache?: readonly CacheAdapter[];\n /** SWR(Stale-While-Revalidate)設定。 */\n swr?: SWRConfig;\n /**\n * Markdown→HTML レンダラー。\n * 省略時は `@notion-headless-cms/markdown-html` の `renderMarkdown` を動的 import で使用する。\n * カスタム実装も `RendererFn` 型を満たせば使用可能。\n */\n renderer?: RendererFn;\n /** 画像プロキシのベース URL。デフォルト `/api/images`。 */\n imageProxyBase?: string;\n /** Cloudflare Workers の `waitUntil` に相当する非同期処理の登録関数。 */\n waitUntil?: (p: Promise<unknown>) => void;\n /** ライフサイクルフック (全コレクション共通)。 */\n hooks?: CMSHooks<BaseContentItem>;\n /** プラグイン配列。 */\n plugins?: CMSPlugin<BaseContentItem>[];\n /** ロガー。 */\n logger?: Logger;\n /** ログレベルの下限。指定したレベル未満のログを内部で抑制する。 */\n logLevel?: LogLevel;\n /** レートリミット・リトライ設定。 */\n rateLimiter?: RateLimiterConfig;\n /** レンダリング・コンテンツ処理設定。 */\n content?: ContentConfig;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/cache/noop.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/cms.ts","../src/page-index.ts","../src/types/config.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/markdown-html` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/markdown-html\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/markdown-html のロードにも失敗しました。\" +\n \" createClient の renderer オプションを指定するか、@notion-headless-cms/markdown-html をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n // markdown 戦略のように loadNotionBlocks 未対応の場合は `source/blocks_unsupported`\n // を吸収して undefined にする (ページ描画は markdown 経路で代替する想定)。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err) && err.is(\"source/blocks_unsupported\")) {\n notionBlocks = undefined;\n } else if (isCMSError(err)) {\n throw err;\n } else {\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n /**\n * リトライ前に呼ばれるフック。`attempt` は 1 始まり、`status` はリトライ対象の HTTP ステータス、\n * `delayMs` は次回試行までの実際の待機時間 (ジッター反映後)。\n * 既存呼び出しは `attempt` / `status` だけで動くよう `delayMs` は省略可。\n */\n onRetry?: (attempt: number, status: number, delayMs?: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n // onRetry には attempt / status と次回待機時間 (ms) を渡す。\n // 呼び出し側のロガーが `LogContext.backoffMs` として記録できる。\n config.onRetry?.(attempt + 1, status, delay);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する (item: `{collection}:{slug}` / list: `{collection}`)。\n *\n * 各 cache adapter は内部で独自のキー戦略を持つが、ログ出力や差分再計算で\n * 同一表現が必要になるため core 側にも公開する。\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名。`source.properties[slugField].notion` を\n * Notion プロパティ名として `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n // TTL 切れはブロッキングで再取得する (stale を返さない要件)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: 即時返却しつつバックグラウンドで差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n // 保存だけはバックグラウンド可: ユーザー向けレスポンスを早めに返す\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async peekVersion(\n slug: string,\n ): Promise<{ notionUpdatedAt: string; cachedAt: number } | null> {\n const meta = await this.ctx.docCache.getMeta<T>(this.ctx.collection, slug);\n if (!meta) return null;\n return { notionUpdatedAt: meta.notionUpdatedAt, cachedAt: meta.cachedAt };\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /** 本文キャッシュ。メタとの整合 (`notionUpdatedAt`) が崩れていれば再生成して書き戻す。 */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n if (cached && cached.notionUpdatedAt === expected) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけ再生成する。エラーは onSwrError フックに通知して握り潰す。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // html() / markdown() / blocks() を同じアイテムから複数回呼んでも I/O は 1 回に集約する\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => {\n const notionBlocks = (await loadPayload()).notionBlocks;\n // markdown 戦略やフェッチャ未設定だと常に undefined になり、\n // React レンダリング側で原因が分からない無言失敗になるため一度だけ案内する。\n if (notionBlocks === undefined) this.warnMissingNotionBlocks();\n return notionBlocks;\n },\n }) as ItemWithContent<T>;\n }\n\n // notionBlocks() が undefined を返したときの案内を CollectionClient 単位で一度だけ出す。\n private notionBlocksWarned = false;\n\n private warnMissingNotionBlocks(): void {\n if (this.notionBlocksWarned) return;\n this.notionBlocksWarned = true;\n this.ctx.logger?.warn?.(\n \"notionBlocks() が undefined を返しました。BlockObjectResponse ツリーは blocks 戦略でのみ得られます (notionSource の fetch 未指定の既定でも有効)。markdownFetcher を使用中の場合は markdown→React の Renderer を使うか、blocksFetcher() に切り替えてください。\",\n { collection: this.ctx.collection, operation: \"notionBlocks\" },\n );\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n // TTL 切れはブロッキングで再取得する (find と同じ理由)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else {\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、cachedAt を更新\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status, delayMs) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", {\n attempt,\n status,\n backoffMs: delayMs,\n });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number, delayMs?: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n backoffMs: delayMs,\n });\n },\n };\n\n // PropertyMap が解決できる場合は単一プロパティ filter で 1 ページだけ取得する (高速)\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // PropertyMap 未提供 / findByProp 未実装の DataSource 向けフォールバック\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順 (未設定なら lastEditedTime 降順) でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** バージョン照会 (peekVersion) のパス (basePath 相対)。デフォルト `/versions`。 */\n versionsPath?: string;\n /** 更新チェック (check) のパス (basePath 相対)。デフォルト `/check`。 */\n checkPath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `handler/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n /**\n * 指定コレクション / slug の `peekVersion`(KV メタのみ、Notion API 非呼び出し)を返す。\n * キャッシュ未登録なら `null`。未知コレクション → `handler/unknown_collection` CMSError。\n */\n peekVersionFor(\n collection: string,\n slug: string,\n ): Promise<{ notionUpdatedAt: string; cachedAt: number } | null>;\n /**\n * 指定コレクション / slug を `currentVersion` と比較し(Notion を実照会)、\n * 差分があればキャッシュを更新して `stale` を返す。\n * アイテムが存在しない場合は `null`。未知コレクション → `handler/unknown_collection` CMSError。\n */\n checkFor(\n collection: string,\n slug: string,\n currentVersion: string,\n ): Promise<{ stale: boolean } | null>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n versionsPath: \"/versions\",\n checkPath: \"/check\",\n} as const;\n\nconst JSON_HEADERS = { \"content-type\": \"application/json\" } as const;\n\n/** CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */\nfunction httpStatusForError(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n if (code === \"handler/unknown_collection\") return 404;\n return null;\n}\n\n/** CMSError を HTTP ステータスへ写像できれば JSON レスポンスにする。未対応なら null。 */\nfunction errorResponse(err: unknown): Response | null {\n if (!isCMSError(err)) return null;\n const status = httpStatusForError(err.code);\n if (status === null) return null;\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: JSON_HEADERS,\n });\n}\n\n/** `:collection/:slug` を分解する。どちらか欠ける場合は null。 */\nfunction splitCollectionSlug(\n sub: string,\n): { collection: string; slug: string } | null {\n const slashIndex = sub.indexOf(\"/\");\n if (slashIndex <= 0 || slashIndex === sub.length - 1) return null;\n return {\n collection: sub.slice(0, slashIndex),\n slug: sub.slice(slashIndex + 1),\n };\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - GET `{basePath}/versions/:collection/:slug` — peekVersion(更新検知ポーリング)\n * - GET/POST `{basePath}/check/:collection/:slug?v=` — check(更新を実照会してキャッシュ更新)\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n const versionsPath = opts.versionsPath ?? DEFAULT_OPTS.versionsPath;\n const checkPath = opts.checkPath ?? DEFAULT_OPTS.checkPath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n if (req.method === \"GET\" && rel.startsWith(`${versionsPath}/`)) {\n const target = splitCollectionSlug(rel.slice(versionsPath.length + 1));\n if (!target) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection and slug required\" }),\n { status: 400, headers: JSON_HEADERS },\n );\n }\n try {\n const version = await adapter.peekVersionFor(\n target.collection,\n target.slug,\n );\n // 値が無い場合も 200 + null を返す(ポーリング側は null を「未確定」として継続する)。\n return new Response(JSON.stringify(version), {\n status: 200,\n headers: JSON_HEADERS,\n });\n } catch (err) {\n const res = errorResponse(err);\n if (res) return res;\n throw err;\n }\n }\n\n if (\n (req.method === \"GET\" || req.method === \"POST\") &&\n rel.startsWith(`${checkPath}/`)\n ) {\n const target = splitCollectionSlug(rel.slice(checkPath.length + 1));\n // 比較基準のバージョンは `?v=` クエリで受け取る。\n const currentVersion = url.searchParams.get(\"v\");\n if (!target || !currentVersion) {\n return new Response(\n JSON.stringify({\n ok: false,\n reason: \"collection, slug and ?v= are required\",\n }),\n { status: 400, headers: JSON_HEADERS },\n );\n }\n try {\n const result = await adapter.checkFor(\n target.collection,\n target.slug,\n currentVersion,\n );\n // アイテム未存在は 404。差分判定 (stale) は 200 で返す。\n if (result === null) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"not found\" }),\n {\n status: 404,\n headers: JSON_HEADERS,\n },\n );\n }\n return new Response(JSON.stringify({ stale: result.stale }), {\n status: 200,\n headers: JSON_HEADERS,\n });\n } catch (err) {\n const res = errorResponse(err);\n if (res) return res;\n throw err;\n }\n }\n\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: JSON_HEADERS },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: JSON_HEADERS,\n });\n } catch (err) {\n const res = errorResponse(err);\n if (res) return res;\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers, withTraceId } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CacheAdapterStats,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateClientOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\nimport type {\n CMSAdapter,\n CMSSources,\n MergeSourceCollections,\n} from \"./types/sources\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/**\n * `cms.stats()` が返す集約済みキャッシュ統計。\n * 各 adapter の `stats()` 戻り値をそのまま配列で保持しつつ、ヒット率を算出する。\n */\nexport interface CMSStats {\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n traceId: string;\n /** ドキュメントキャッシュの集計 (`handles: [\"document\"]` の adapter の `stats()` 戻り値)。 */\n document?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n /** 0〜1。`hits + misses === 0` のときは 0。 */\n hitRate: number;\n };\n /** 画像キャッシュの集計 (`handles: [\"image\"]` の adapter の `stats()` 戻り値)。 */\n image?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n hitRate: number;\n };\n}\n\nexport interface CMSGlobalOps {\n readonly collections: readonly string[];\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n readonly traceId: string;\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard な Request/Response ベースのルートハンドラ (画像プロキシ + webhook)。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n readonly imageProxyBase: string;\n /**\n * ドキュメント / 画像キャッシュのヒット・ミス・サイズを集約して返す。\n * 各キャッシュアダプタの `stats()` を呼ぶだけで副作用はない。\n * `stats()` を実装していない adapter は集計から除外される (noop など)。\n */\n stats(): Promise<CMSStats>;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n docAdapter: CacheAdapter | undefined;\n img: ImageCacheOps;\n imgName: string;\n imgAdapter: CacheAdapter | undefined;\n hasImg: boolean;\n}\n\n/**\n * adapter の `handles` を見て先勝ちで document / image を割り当てる。未指定は両方 noop。\n * `cms.stats()` から元 adapter の `stats()` を呼びたいので、解決元 adapter も保持する。\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let docAdapter: CacheAdapter | undefined;\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let imgAdapter: CacheAdapter | undefined;\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docAdapter = adapter;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgAdapter = adapter;\n imgFound = true;\n }\n }\n\n return {\n doc,\n docName,\n docAdapter,\n img,\n imgName,\n imgAdapter,\n hasImg: imgFound,\n };\n}\n\n/**\n * 衝突しにくい短い trace ID を生成する。`{epoch36}-{rand36}` の 10〜12 文字程度。\n * core はゼロ依存ルールに従い node:crypto を静的 import しないため、Math.random ベースで十分。\n */\nfunction generateTraceId(): string {\n const t = Date.now().toString(36);\n const r = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, \"0\");\n return `${t}-${r}`;\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * CMS クライアントを生成する。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n *\n * const posts = await cms.posts.list();\n */\nexport function createClient<S extends CMSSources = CMSSources>(\n opts: CreateClientOptions<S>,\n): CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n> {\n // sources の各アダプタが持つ collections をマージする\n const collectionsInput: CollectionsConfig = {};\n if (opts.sources) {\n for (const adapter of Object.values(\n opts.sources as unknown as Record<string, CMSAdapter | undefined>,\n )) {\n if (adapter) Object.assign(collectionsInput, adapter.collections);\n }\n }\n\n if (Object.keys(collectionsInput).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createClient: sources に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createClient\" },\n nextSteps: [\n \"notionSource({ schema, token }) を sources.notion に渡す\",\n \"`nhc generate` でスキーマを生成してから import する\",\n ],\n docsUrl:\n \"https://github.com/kjfsm/notion-headless-cms/blob/main/docs/quickstart.md\",\n });\n }\n\n for (const [name, def] of Object.entries(collectionsInput)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\"notionSource(...) を sources に渡しているか確認する\"],\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\n `nhc.config.ts の ${name} コレクションに slugField を設定する`,\n ],\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n // plugin logger と createClient 引数の logger を合成し、その上でクライアント単位の\n // traceId をログコンテキストに自動付与する。\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const traceId = generateTraceId();\n const tracedLogger = withTraceId(baseLogger, traceId);\n const logger = opts.logLevel\n ? applyLogLevel(tracedLogger, opts.logLevel)\n : tracedLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: string[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(collectionsInput)) {\n collectionNames.push(name);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n traceId,\n async stats(): Promise<CMSStats> {\n const stats: CMSStats = { traceId };\n // doc / img それぞれ、resolveCache が選んだ adapter にだけ stats() を要求する。\n // 同じ adapter が両方を担当している場合 (memoryCache など) は 1 回呼ぶだけで足りる。\n const adapterCache = new Map<CacheAdapter, Promise<CacheAdapterStats>>();\n const ensure = (\n adapter: CacheAdapter | undefined,\n ): Promise<CacheAdapterStats> | undefined => {\n if (!adapter?.stats) return undefined;\n const cached = adapterCache.get(adapter);\n if (cached) return cached;\n const fresh = adapter.stats();\n adapterCache.set(adapter, fresh);\n return fresh;\n };\n const docPromise = ensure(cacheRes.docAdapter);\n const imgPromise = ensure(cacheRes.imgAdapter);\n const computeHitRate = (h: number, m: number): number =>\n h + m === 0 ? 0 : h / (h + m);\n if (docPromise) {\n const docStats = await docPromise;\n if (docStats.doc) {\n stats.document = {\n adapter: docStats.name ?? cacheRes.docName,\n hits: docStats.doc.hits,\n misses: docStats.doc.misses,\n entries: docStats.doc.entries,\n sizeBytes: docStats.doc.sizeBytes,\n hitRate: computeHitRate(docStats.doc.hits, docStats.doc.misses),\n };\n }\n }\n if (imgPromise) {\n const imgStats = await imgPromise;\n if (imgStats.img) {\n stats.image = {\n adapter: imgStats.name ?? cacheRes.imgName,\n hits: imgStats.img.hits,\n misses: imgStats.img.misses,\n entries: imgStats.img.entries,\n sizeBytes: imgStats.img.sizeBytes,\n hitRate: computeHitRate(imgStats.img.hits, imgStats.img.misses),\n };\n }\n }\n return stats;\n },\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = collectionsInput[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n peekVersionFor(collection, slug) {\n const client = collections[collection];\n if (!client) {\n throw new CMSError({\n code: \"handler/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"peekVersionFor\", collection, slug },\n });\n }\n return client.peekVersion(slug);\n },\n async checkFor(collection, slug, currentVersion) {\n const client = collections[collection];\n if (!client) {\n throw new CMSError({\n code: \"handler/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"checkFor\", collection, slug },\n });\n }\n const result = await client.check(slug, currentVersion);\n // ItemWithContent は lazy 関数を含むため、HTTP には stale 判定のみ返す\n // (差分ありの場合 check() が副作用でキャッシュ更新済み。利用側は loader 再実行で本文取得)。\n return result === null ? null : { stale: result.stale };\n },\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n >;\n}\n","import type { CollectionClient } from \"./types/collection\";\nimport type { BaseContentItem } from \"./types/content\";\n\n/** 逆引きの解決先。Notion ページ ID から自サイトの位置を引く。 */\nexport interface PageIndexEntry {\n /** 所属コレクション名。 */\n collection: string;\n /** URL キー。 */\n slug: string;\n /** ページ名(表示テキスト用)。 */\n title?: string | null;\n}\n\n/** `buildPageIndex` が返す逆引きマップ。キーは `normalizePageId` 済みの pageId。 */\nexport type PageIndex = Map<string, PageIndexEntry>;\n\n/**\n * `buildPageIndex` / `buildPageLinkMap` が必要とする最小の CMS 形状。\n * `collections` でコレクション名を列挙し、各名でコレクションクライアントへアクセスする。\n * `CMSClient` はコレクションを自身に spread しているため構造的に満たす。\n */\nexport interface PageIndexSource {\n readonly collections: readonly string[];\n}\n\nexport interface BuildPageIndexOptions {\n /** 走査対象のコレクション名。未指定なら `source.collections` 全件。 */\n collections?: readonly string[];\n}\n\n/**\n * Notion ページ ID を比較用に正規化する。\n * Notion の ID は文脈によりダッシュの有無・大文字小文字が揺れるため、\n * ダッシュ除去 + 小文字化して突き合わせる。\n *\n * react-renderer 側も同一実装で `pageLinks` を引くため、変更時は両方を揃えること。\n */\nexport function normalizePageId(id: string): string {\n return id.replace(/-/g, \"\").toLowerCase();\n}\n\nfunction asCollectionClient(\n source: PageIndexSource,\n name: string,\n): CollectionClient<BaseContentItem> | undefined {\n const client = (source as unknown as Record<string, unknown>)[name] as\n | CollectionClient<BaseContentItem>\n | undefined;\n // collections に名前はあっても spread されていない / list を持たない値は無視する。\n if (!client || typeof client.list !== \"function\") return undefined;\n return client;\n}\n\n/**\n * 全コレクションを `list()` で走査し、pageId → {collection, slug, title} の逆引きマップを構築する。\n * Notion 内部リンク(link_to_page / page mention など)を自サイト URL へ解決するための材料。\n *\n * `list()` は SWR ドキュメントキャッシュ経由のためウォーム後は安価。\n */\nexport async function buildPageIndex(\n source: PageIndexSource,\n opts?: BuildPageIndexOptions,\n): Promise<PageIndex> {\n // collections を持たない / 異常な source でもクラッシュさせず空マップを返す。\n const names = opts?.collections ?? source.collections;\n const index: PageIndex = new Map();\n if (\n !names ||\n typeof (names as Iterable<string>)[Symbol.iterator] !== \"function\"\n ) {\n return index;\n }\n for (const name of names) {\n const client = asCollectionClient(source, name);\n if (!client) continue;\n const items = await client.list();\n if (!Array.isArray(items)) continue;\n for (const item of items) {\n // pageId は一意なので衝突しない前提。万一重複しても先勝ちで保持する。\n const key = normalizePageId(item.id);\n if (!index.has(key)) {\n index.set(key, {\n collection: name,\n slug: item.slug,\n title: item.title,\n });\n }\n }\n }\n return index;\n}\n\n/** 解決済みの内部リンク。`href` は構築時に決まり、`title` は表示テキスト用。 */\nexport interface ResolvedPageLink {\n href: string;\n title?: string | null;\n}\n\n/**\n * 正規化済み pageId → 解決済みリンクのプレーンマップ。\n * 関数ではなくプレーンオブジェクトなので、loader / RSC(Server Component → Client\n * Component)境界を越えて `<NotionRenderer pageLinks={...} />` にそのまま渡せる。\n */\nexport type PageLinkMap = Record<string, ResolvedPageLink>;\n\nexport interface BuildPageLinkMapOptions extends BuildPageIndexOptions {\n /**\n * エントリから URL を組み立てる関数。既定は `/${collection}/${slug}`。\n * 単一コレクションで `/${slug}` にしたい場合などに上書きする。\n */\n url?: (entry: PageIndexEntry, pageId: string) => string;\n /** 事前構築済みインデックス。指定するとリクエストごとの再構築を省ける。 */\n index?: PageIndex;\n}\n\nconst defaultUrl = (entry: PageIndexEntry): string =>\n `/${entry.collection}/${entry.slug}`;\n\n/**\n * Notion 内部リンクを「正規化 pageId → {href, title}」のプレーンマップに解決する。\n * サーバ側(loader / RSC / route handler)で 1 回構築し、`<NotionRenderer pageLinks={...} />`\n * に渡す。プレーンオブジェクトなのでシリアライズ境界(RSC / loader)を越えられる。\n *\n * @example\n * const pageLinks = await buildPageLinkMap(cms);\n * <NotionRenderer blocks={blocks} pageLinks={pageLinks} />;\n */\nexport async function buildPageLinkMap(\n source: PageIndexSource,\n opts?: BuildPageLinkMapOptions,\n): Promise<PageLinkMap> {\n const index = opts?.index ?? (await buildPageIndex(source, opts));\n const toUrl = opts?.url ?? defaultUrl;\n const map: PageLinkMap = {};\n for (const [key, entry] of index) {\n map[key] = { href: toUrl(entry, key), title: entry.title };\n }\n return map;\n}\n","import type { CacheAdapter } from \"./cache\";\nimport type { BaseContentItem } from \"./content\";\nimport type { DataSource } from \"./data-source\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\nimport type { CMSPlugin } from \"./plugin\";\nimport type { CMSSources } from \"./sources\";\n\n/** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/**\n * renderer プラグインの不透明型。\n * core は unified / remark / rehype に依存せず、このリストをそのまま renderer に渡すだけ。\n */\nexport type RendererPluginList = unknown[];\n\n/**\n * render() オプション。core は renderer の実装を知らず、この型だけを扱う。\n * @notion-headless-cms/markdown-html の renderMarkdown() はこのシグネチャと構造的に互換。\n */\nexport interface RenderOptions {\n imageProxyBase?: string;\n cacheImage?: (url: string) => Promise<string>;\n remarkPlugins?: RendererPluginList;\n rehypePlugins?: RendererPluginList;\n}\n\n/** カスタムレンダラー関数の型。デフォルトは @notion-headless-cms/markdown-html の renderMarkdown。 */\nexport type RendererFn = (\n markdown: string,\n opts?: RenderOptions,\n) => Promise<string>;\n\n/** レンダリング・コンテンツ処理設定。 */\nexport interface ContentConfig {\n /** 追加する remark プラグイン。 */\n remarkPlugins?: RendererPluginList;\n /** 追加する rehype プラグイン。 */\n rehypePlugins?: RendererPluginList;\n}\n\n/** SWR(Stale-While-Revalidate)設定。 */\nexport interface SWRConfig {\n /** SWR の有効期間 (ミリ秒)。未設定時は TTL なし(失効まで stale を返す)。 */\n ttlMs?: number;\n}\n\n/**\n * `RateLimiterConfig` のデフォルト値 (Issue #313 / M2)。\n * 型からだけでは見えない既定値を表面化することで、IDE 補完と\n * preset の対称化 (`nodePreset` / `cloudflarePreset` / `nextPreset`) で\n * 同じ既定が適用されることを保証する。\n */\nexport const DEFAULT_RATE_LIMITER: Required<RateLimiterConfig> = {\n maxConcurrent: 3,\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n};\n\n/** レートリミット・リトライ設定。既定値は {@link DEFAULT_RATE_LIMITER}。 */\nexport interface RateLimiterConfig {\n /** 同時実行数の上限。デフォルト: 3 */\n maxConcurrent?: number;\n /** リトライ対象の HTTP ステータスコード。デフォルト: [429, 502, 503] */\n retryOn?: number[];\n /** 最大リトライ回数。デフォルト: 4 */\n maxRetries?: number;\n /** リトライ時の基準待機時間(ミリ秒)。デフォルト: 1000 */\n baseDelayMs?: number;\n}\n\n/**\n * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createClient` に渡される。\n *\n * `source` は notion-orm 等の DataSource 実装。\n * `slugField` / `statusField` は TS フィールド名 (DataSource の `properties` キーと一致)。\n */\nexport interface CollectionDef<T extends BaseContentItem = BaseContentItem> {\n /** Notion etc. のデータソース実装。 */\n source: DataSource<T>;\n /** slug として使う TS フィールド名 (必須)。`source.properties[slugField]` で Notion プロパティ名を解決する。 */\n slugField: string;\n /** ステータスとして使う TS フィールド名。 */\n statusField?: string;\n /** 公開扱いするステータス値。`list()` のデフォルト絞り込みに使う。 */\n publishedStatuses?: readonly string[];\n /** アクセス許可するステータス値。`get()` の閲覧可否判定に使う。 */\n accessibleStatuses?: readonly string[];\n /** コレクション固有のライフサイクルフック。グローバル hooks の後に実行される。 */\n hooks?: CMSHooks<T>;\n}\n\n/**\n * `CollectionDef` の strict 版。`slugField` / `statusField` が `keyof T & string` で\n * 型ガードされており、誤フィールド名は型エラーになる (Issue #314 / M3)。\n * CLI 生成スキーマや `defineCollection<T>()` 経由で利用される。\n */\nexport interface StrictCollectionDef<T extends BaseContentItem>\n extends Omit<CollectionDef<T>, \"slugField\" | \"statusField\"> {\n /** slug として使う TS フィールド名。`keyof T` で型ガードされる。 */\n slugField: keyof T & string;\n /** ステータスとして使う TS フィールド名。`keyof T` で型ガードされる。 */\n statusField?: keyof T & string;\n}\n\n/**\n * 型推論ヘルパー: `T` を明示してコレクション定義を作る。`slugField` / `statusField` は\n * `keyof T & string` で補完・型ガードされ、誤フィールド名 (例: `\"slag\"`) で型エラーになる\n * (Issue #314 / M3)。CLI 生成 `nhc.schema.ts` で利用される。\n *\n * @example\n * ```ts\n * type PostItem = BaseContentItem & { authorName?: string };\n * const posts = defineCollection<PostItem>({\n * source: notionSource(...),\n * slugField: \"slug\", // OK\n * statusField: \"status\", // OK\n * // statusField: \"stat\", // 型エラー\n * });\n * ```\n */\nexport function defineCollection<T extends BaseContentItem>(\n def: StrictCollectionDef<T>,\n): CollectionDef<T> {\n // StrictCollectionDef は CollectionDef の slugField/statusField を絞ったサブ型なので\n // 構造的に CollectionDef<T> へ代入可能。型システム上は再構成のため as 経由。\n return def as unknown as CollectionDef<T>;\n}\n\n/**\n * `createClient({ collections })` の map 型。\n * キーがコレクション名、値が `CollectionDef<T>`。\n */\nexport type CollectionsConfig = Record<string, CollectionDef<BaseContentItem>>;\n\n/** `CollectionsConfig` から各 T を抽出するユーティリティ型。 */\nexport type InferCollectionItem<C> =\n C extends CollectionDef<infer T> ? T : BaseContentItem;\n\n/**\n * `createClient()` の入力。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n */\nexport interface CreateClientOptions<S extends CMSSources = CMSSources> {\n /** データソースアダプター (`@notion-headless-cms/notion-source` 等) のマップ。 */\n sources?: S;\n /**\n * キャッシュアダプタ (配列)。未指定時はキャッシュなし。\n * - `memoryCache()` のように doc + image 両方を担当するもの\n * - `r2Cache()` (image のみ)、`kvCache()` (doc のみ) のように片側のみ担当するもの\n * - 複数 adapter を配列で組み合わせると、各 adapter の `handles` で振り分けられる\n */\n cache?: readonly CacheAdapter[];\n /** SWR(Stale-While-Revalidate)設定。 */\n swr?: SWRConfig;\n /**\n * Markdown→HTML レンダラー。\n * 省略時は `@notion-headless-cms/markdown-html` の `renderMarkdown` を動的 import で使用する。\n * カスタム実装も `RendererFn` 型を満たせば使用可能。\n */\n renderer?: RendererFn;\n /** 画像プロキシのベース URL。デフォルト `/api/images`。 */\n imageProxyBase?: string;\n /** Cloudflare Workers の `waitUntil` に相当する非同期処理の登録関数。 */\n waitUntil?: (p: Promise<unknown>) => void;\n /** ライフサイクルフック (全コレクション共通)。 */\n hooks?: CMSHooks<BaseContentItem>;\n /** プラグイン配列。 */\n plugins?: CMSPlugin<BaseContentItem>[];\n /** ロガー。 */\n logger?: Logger;\n /** ログレベルの下限。指定したレベル未満のログを内部で抑制する。 */\n logLevel?: LogLevel;\n /** レートリミット・リトライ設定。 */\n rateLimiter?: RateLimiterConfig;\n /** レンダリング・コンテンツ処理設定。 */\n content?: ContentConfig;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
- import { n as CMSError } from "./errors-DcNErfYk.mjs";
2
+ import { n as CMSError } from "./errors-DPD5yq9S.mjs";
3
3
 
4
4
  //#region src/types/hooks.d.ts
5
5
  type MaybePromise<T> = T | Promise<T>;
@@ -81,4 +81,4 @@ interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {
81
81
  declare function definePlugin<T extends BaseContentItem>(plugin: CMSPlugin<T>): CMSPlugin<T>;
82
82
  //#endregion
83
83
  export { CMSHooks as a, Logger as i, definePlugin as n, MaybePromise as o, LogContext as r, CMSPlugin as t };
84
- //# sourceMappingURL=plugin-BmrOz8T6.d.mts.map
84
+ //# sourceMappingURL=plugin-BPhaVO6-.d.mts.map
@@ -1,5 +1,5 @@
1
1
  import { t as CacheAdapter } from "../cache-v9jTMnYd.mjs";
2
- import { f as SWRConfig } from "../config-i99tKRhN.mjs";
2
+ import { f as SWRConfig } from "../config-Ccs0RxoN.mjs";
3
3
  import { MemoryCacheOptions } from "../cache/memory.mjs";
4
4
 
5
5
  //#region src/preset/node.d.ts
@@ -1,3 +1,3 @@
1
1
  import { t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
- import { _ as MergeSourceCollections, g as CMSSources, h as CMSAdapter, n as CollectionsConfig, o as InferCollectionItem, t as CollectionDef } from "./config-i99tKRhN.mjs";
2
+ import { _ as MergeSourceCollections, g as CMSSources, h as CMSAdapter, n as CollectionsConfig, o as InferCollectionItem, t as CollectionDef } from "./config-Ccs0RxoN.mjs";
3
3
  export type { BaseContentItem, CMSAdapter, CMSSources, CollectionDef, CollectionsConfig, InferCollectionItem, MergeSourceCollections };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notion-headless-cms/core",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
5
5
  "keywords": [
6
6
  "notion",