@notion-headless-cms/core 0.3.4 → 0.3.6

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.
package/dist/errors.d.mts CHANGED
@@ -1,4 +1,28 @@
1
1
  //#region src/errors.d.ts
2
+ /**
3
+ * ライブラリ組み込みの CMS エラーコード。
4
+ *
5
+ * | コード | 発生条件 |
6
+ * |---|---|
7
+ * | `core/config_invalid` | 設定不備(token 未設定など) |
8
+ * | `core/schema_invalid` | schema/mapping の型不整合 |
9
+ * | `core/notion_orm_missing` | `@notion-headless-cms/notion-orm` の動的ロード失敗 |
10
+ * | `source/fetch_items_failed` | `DataSource.list()` 失敗 |
11
+ * | `source/fetch_item_failed` | `DataSource.findByProp()` 失敗 |
12
+ * | `source/load_markdown_failed` | `DataSource.loadMarkdown()` 失敗 |
13
+ * | `cache/io_failed` | document / image キャッシュの I/O 失敗 |
14
+ * | `cache/image_fetch_failed` | Notion 画像の HTTP 取得失敗 |
15
+ * | `renderer/failed` | Markdown → HTML 変換失敗 |
16
+ * | `cli/config_invalid` | `nhc.config.ts` の内容不整合 |
17
+ * | `cli/config_load_failed` | 設定ファイルの読み込み / 評価失敗 |
18
+ * | `cli/schema_invalid` | CLI が受け取ったスキーマ / マッピング不整合 |
19
+ * | `cli/generate_failed` | `nhc generate` の処理失敗 |
20
+ * | `cli/init_failed` | `nhc init` の処理失敗 |
21
+ * | `cli/notion_api_failed` | CLI が Notion API を呼び出す際の失敗 |
22
+ * | `cli/env_file_not_found` | `--env-file` で指定したファイルが存在しない |
23
+ *
24
+ * サードパーティアダプタが独自コードを追加したい場合は `CMSErrorCode` を参照。
25
+ */
2
26
  type BuiltInCMSErrorCode = "core/config_invalid" | "core/schema_invalid" | "core/notion_orm_missing" | "source/fetch_items_failed" | "source/fetch_item_failed" | "source/load_markdown_failed" | "cache/io_failed" | "cache/image_fetch_failed" | "renderer/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";
3
27
  /**
4
28
  * CMS エラーコード。
@@ -28,5 +52,5 @@ declare function isCMSError(error: unknown): error is CMSError;
28
52
  /** エラーコードが特定の名前空間に属するかを判定する(例: "source/")。 */
29
53
  declare function isCMSErrorInNamespace(error: unknown, namespace: string): error is CMSError;
30
54
  //#endregion
31
- export { CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace };
55
+ export { BuiltInCMSErrorCode, CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace };
32
56
  //# sourceMappingURL=errors.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["type BuiltInCMSErrorCode =\n\t| \"core/config_invalid\"\n\t| \"core/schema_invalid\"\n\t| \"core/notion_orm_missing\"\n\t| \"source/fetch_items_failed\"\n\t| \"source/fetch_item_failed\"\n\t| \"source/load_markdown_failed\"\n\t| \"cache/io_failed\"\n\t| \"cache/image_fetch_failed\"\n\t| \"renderer/failed\"\n\t| \"cli/config_invalid\"\n\t| \"cli/config_load_failed\"\n\t| \"cli/schema_invalid\"\n\t| \"cli/generate_failed\"\n\t| \"cli/init_failed\"\n\t| \"cli/notion_api_failed\"\n\t| \"cli/env_file_not_found\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n\toperation: string;\n\tslug?: string;\n\tdataSourceId?: string;\n\tpageId?: string;\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\nexport class CMSError extends Error {\n\treadonly code: CMSErrorCode;\n\toverride readonly cause?: unknown;\n\treadonly context: CMSErrorContext;\n\n\tconstructor(params: {\n\t\tcode: CMSErrorCode;\n\t\tmessage: string;\n\t\tcause?: unknown;\n\t\tcontext: CMSErrorContext;\n\t}) {\n\t\tsuper(params.message, { cause: params.cause });\n\t\tthis.name = \"CMSError\";\n\t\tthis.code = params.code;\n\t\tthis.cause = params.cause;\n\t\tthis.context = params.context;\n\t}\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n\treturn error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n\terror: unknown,\n\tnamespace: string,\n): error is CMSError {\n\treturn isCMSError(error) && error.code.startsWith(namespace);\n}\n"],"mappings":";AAiCA,IAAa,WAAb,cAA8B,MAAM;CACnC;CACA;CACA;CAEA,YAAY,QAKT;AACF,QAAM,OAAO,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC;AAC9C,OAAK,OAAO;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;AAIxB,SAAgB,WAAW,OAAmC;AAC7D,QAAO,iBAAiB;;;AAIzB,SAAgB,sBACf,OACA,WACoB;AACpB,QAAO,WAAW,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU"}
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 * | `source/fetch_items_failed` | `DataSource.list()` 失敗 |\n * | `source/fetch_item_failed` | `DataSource.findByProp()` 失敗 |\n * | `source/load_markdown_failed` | `DataSource.loadMarkdown()` 失敗 |\n * | `cache/io_failed` | document / image キャッシュの I/O 失敗 |\n * | `cache/image_fetch_failed` | Notion 画像の HTTP 取得失敗 |\n * | `renderer/failed` | Markdown → HTML 変換失敗 |\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\t| \"core/config_invalid\"\n\t| \"core/schema_invalid\"\n\t| \"core/notion_orm_missing\"\n\t| \"source/fetch_items_failed\"\n\t| \"source/fetch_item_failed\"\n\t| \"source/load_markdown_failed\"\n\t| \"cache/io_failed\"\n\t| \"cache/image_fetch_failed\"\n\t| \"renderer/failed\"\n\t| \"cli/config_invalid\"\n\t| \"cli/config_load_failed\"\n\t| \"cli/schema_invalid\"\n\t| \"cli/generate_failed\"\n\t| \"cli/init_failed\"\n\t| \"cli/notion_api_failed\"\n\t| \"cli/env_file_not_found\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n\toperation: string;\n\tslug?: string;\n\tdataSourceId?: string;\n\tpageId?: string;\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\nexport class CMSError extends Error {\n\treadonly code: CMSErrorCode;\n\toverride readonly cause?: unknown;\n\treadonly context: CMSErrorContext;\n\n\tconstructor(params: {\n\t\tcode: CMSErrorCode;\n\t\tmessage: string;\n\t\tcause?: unknown;\n\t\tcontext: CMSErrorContext;\n\t}) {\n\t\tsuper(params.message, { cause: params.cause });\n\t\tthis.name = \"CMSError\";\n\t\tthis.code = params.code;\n\t\tthis.cause = params.cause;\n\t\tthis.context = params.context;\n\t}\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n\treturn error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n\terror: unknown,\n\tnamespace: string,\n): error is CMSError {\n\treturn isCMSError(error) && error.code.startsWith(namespace);\n}\n"],"mappings":";AAyDA,IAAa,WAAb,cAA8B,MAAM;CACnC;CACA;CACA;CAEA,YAAY,QAKT;AACF,QAAM,OAAO,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC;AAC9C,OAAK,OAAO;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;AAIxB,SAAgB,WAAW,OAAmC;AAC7D,QAAO,iBAAiB;;;AAIzB,SAAgB,sBACf,OACA,WACoB;AACpB,QAAO,WAAW,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU"}
@@ -1,4 +1,4 @@
1
- import { r as CachedItem, t as BaseContentItem } from "./content-WydAfQtk.mjs";
1
+ import { i as CachedItemList, r as CachedItem, t as BaseContentItem } from "./content-WydAfQtk.mjs";
2
2
 
3
3
  //#region src/types/hooks.d.ts
4
4
  type MaybePromise<T> = T | Promise<T>;
@@ -8,11 +8,11 @@ interface CMSHooks<T extends BaseContentItem = BaseContentItem> {
8
8
  onCacheHit?: (slug: string, item: CachedItem<T>) => void;
9
9
  onCacheMiss?: (slug: string) => void;
10
10
  /** SWR バックグラウンド差分チェックで更新を検出し、キャッシュを差し替えたときに呼ばれる。 */
11
- onCacheUpdate?: (slug: string, item: CachedItem<T>) => void;
12
- onListCacheHit?: (items: T[], cachedAt: number) => void;
11
+ onCacheRevalidated?: (slug: string, item: CachedItem<T>) => void;
12
+ onListCacheHit?: (list: CachedItemList<T>) => void;
13
13
  onListCacheMiss?: () => void;
14
14
  /** SWR バックグラウンド差分チェックでリスト更新を検出し、キャッシュを差し替えたときに呼ばれる。 */
15
- onListCacheUpdate?: (items: T[]) => void;
15
+ onListCacheRevalidated?: (list: CachedItemList<T>) => void;
16
16
  onError?: (error: Error) => void;
17
17
  onRenderStart?: (slug: string) => void;
18
18
  onRenderEnd?: (slug: string, durationMs: number) => void;
@@ -67,4 +67,4 @@ declare function mergeLoggers(plugins: Array<{
67
67
  }>, directLogger?: Logger): Logger | undefined;
68
68
  //#endregion
69
69
  export { Logger as a, definePlugin as i, mergeLoggers as n, CMSHooks as o, CMSPlugin as r, MaybePromise as s, mergeHooks as t };
70
- //# sourceMappingURL=hooks-fFMItkry.d.mts.map
70
+ //# sourceMappingURL=hooks-D8Lgf-Co.d.mts.map
package/dist/hooks.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as mergeLoggers, t as mergeHooks } from "./hooks-fFMItkry.mjs";
1
+ import { n as mergeLoggers, t as mergeHooks } from "./hooks-D8Lgf-Co.mjs";
2
2
  export { mergeHooks, mergeLoggers };
package/dist/hooks.mjs CHANGED
@@ -12,10 +12,10 @@ function mergeHooks(plugins, directHooks, logger) {
12
12
  afterRender: buildRenderPipeline(allHooks),
13
13
  onCacheHit: buildObserver(allHooks, "onCacheHit", logger),
14
14
  onCacheMiss: buildObserver(allHooks, "onCacheMiss", logger),
15
- onCacheUpdate: buildObserver(allHooks, "onCacheUpdate", logger),
15
+ onCacheRevalidated: buildObserver(allHooks, "onCacheRevalidated", logger),
16
16
  onListCacheHit: buildObserver(allHooks, "onListCacheHit", logger),
17
17
  onListCacheMiss: buildObserver(allHooks, "onListCacheMiss", logger),
18
- onListCacheUpdate: buildObserver(allHooks, "onListCacheUpdate", logger),
18
+ onListCacheRevalidated: buildObserver(allHooks, "onListCacheRevalidated", logger),
19
19
  onError: buildObserver(allHooks, "onError", logger),
20
20
  onRenderStart: buildObserver(allHooks, "onRenderStart", logger),
21
21
  onRenderEnd: buildObserver(allHooks, "onRenderEnd", logger)
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.mjs","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["import type { BaseContentItem } from \"./types/content\";\nimport type { CMSHooks, MaybePromise } from \"./types/hooks\";\nimport type { Logger } from \"./types/logger\";\nimport type { CMSPlugin } from \"./types/plugin\";\n\n/**\n * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。\n * beforeCache / afterRender はパイプライン(前の出力が次の入力)。\n * onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。\n */\nexport function mergeHooks<T extends BaseContentItem>(\n\tplugins: CMSPlugin<T>[],\n\tdirectHooks?: CMSHooks<T>,\n\tlogger?: Logger,\n): CMSHooks<T> {\n\tconst allHooks: CMSHooks<T>[] = [\n\t\t...plugins.map((p) => p.hooks ?? {}),\n\t\t...(directHooks ? [directHooks] : []),\n\t];\n\n\tif (allHooks.length === 0) return {};\n\n\treturn {\n\t\tbeforeCache: buildPipeline(allHooks, \"beforeCache\"),\n\t\tafterRender: buildRenderPipeline(allHooks),\n\t\tonCacheHit: buildObserver(allHooks, \"onCacheHit\", logger),\n\t\tonCacheMiss: buildObserver(allHooks, \"onCacheMiss\", logger),\n\t\tonCacheUpdate: buildObserver(allHooks, \"onCacheUpdate\", logger),\n\t\tonListCacheHit: buildObserver(allHooks, \"onListCacheHit\", logger),\n\t\tonListCacheMiss: buildObserver(allHooks, \"onListCacheMiss\", logger),\n\t\tonListCacheUpdate: buildObserver(allHooks, \"onListCacheUpdate\", logger),\n\t\tonError: buildObserver(allHooks, \"onError\", logger),\n\t\tonRenderStart: buildObserver(allHooks, \"onRenderStart\", logger),\n\t\tonRenderEnd: buildObserver(allHooks, \"onRenderEnd\", logger),\n\t};\n}\n\nfunction buildPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n\tkey: \"beforeCache\",\n): CMSHooks<T>[\"beforeCache\"] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"beforeCache\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (item) => {\n\t\tlet current = item;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current) as MaybePromise<typeof item>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildRenderPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n): CMSHooks<T>[\"afterRender\"] {\n\tconst fns = hooks.map((h) => h.afterRender).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"afterRender\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (html, item) => {\n\t\tlet current = html;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current, item) as MaybePromise<string>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildObserver<T extends BaseContentItem, K extends keyof CMSHooks<T>>(\n\thooks: CMSHooks<T>[],\n\tkey: K,\n\tlogger?: Logger,\n): CMSHooks<T>[K] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean);\n\tif (fns.length === 0) return undefined;\n\treturn ((...args: unknown[]) => {\n\t\tfor (const fn of fns) {\n\t\t\ttry {\n\t\t\t\t(fn as (...a: unknown[]) => void)(...args);\n\t\t\t} catch (err) {\n\t\t\t\tlogger?.error?.(\"観測フックで例外が発生\", {\n\t\t\t\t\thook: String(key),\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}) as CMSHooks<T>[K];\n}\n\n/** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */\nexport function mergeLoggers(\n\tplugins: Array<{ logger?: Partial<Logger> }>,\n\tdirectLogger?: Logger,\n): Logger | undefined {\n\tconst loggers: Partial<Logger>[] = [\n\t\t...plugins.map((p) => p.logger ?? {}),\n\t\t...(directLogger ? [directLogger] : []),\n\t];\n\tif (loggers.length === 0) return undefined;\n\n\tconst merged: Logger = {};\n\tfor (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n\t\tconst fns = loggers.map((l) => l[level]).filter(Boolean) as NonNullable<\n\t\t\tLogger[typeof level]\n\t\t>[];\n\t\tif (fns.length > 0) {\n\t\t\tmerged[level] = (message, context) => {\n\t\t\t\tfor (const fn of fns) fn(message, context);\n\t\t\t};\n\t\t}\n\t}\n\treturn merged;\n}\n"],"mappings":";;;;;;AAUA,SAAgB,WACf,SACA,aACA,QACc;CACd,MAAM,WAA0B,CAC/B,GAAG,QAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,CAAC,EACpC,GAAI,cAAc,CAAC,YAAY,GAAG,EAAE,CACpC;AAED,KAAI,SAAS,WAAW,EAAG,QAAO,EAAE;AAEpC,QAAO;EACN,aAAa,cAAc,UAAU,cAAc;EACnD,aAAa,oBAAoB,SAAS;EAC1C,YAAY,cAAc,UAAU,cAAc,OAAO;EACzD,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D,eAAe,cAAc,UAAU,iBAAiB,OAAO;EAC/D,gBAAgB,cAAc,UAAU,kBAAkB,OAAO;EACjE,iBAAiB,cAAc,UAAU,mBAAmB,OAAO;EACnE,mBAAmB,cAAc,UAAU,qBAAqB,OAAO;EACvE,SAAS,cAAc,UAAU,WAAW,OAAO;EACnD,eAAe,cAAc,UAAU,iBAAiB,OAAO;EAC/D,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D;;AAGF,SAAS,cACR,OACA,KAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AAGpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,SAAS;EACtB,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,QAAQ;AAE7B,SAAO;;;AAIT,SAAS,oBACR,OAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,YAAY,CAAC,OAAO,QAAQ;AAG3D,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,MAAM,SAAS;EAC5B,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,SAAS,KAAK;AAEnC,SAAO;;;AAIT,SAAS,cACR,OACA,KACA,QACiB;CACjB,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AACpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,UAAS,GAAG,SAAoB;AAC/B,OAAK,MAAM,MAAM,IAChB,KAAI;AACF,MAAiC,GAAG,KAAK;WAClC,KAAK;AACb,WAAQ,QAAQ,eAAe;IAC9B,MAAM,OAAO,IAAI;IACjB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,CAAC;;;;;AAON,SAAgB,aACf,SACA,cACqB;CACrB,MAAM,UAA6B,CAClC,GAAG,QAAQ,KAAK,MAAM,EAAE,UAAU,EAAE,CAAC,EACrC,GAAI,eAAe,CAAC,aAAa,GAAG,EAAE,CACtC;AACD,KAAI,QAAQ,WAAW,EAAG,QAAO,KAAA;CAEjC,MAAM,SAAiB,EAAE;AACzB,MAAK,MAAM,SAAS;EAAC;EAAS;EAAQ;EAAQ;EAAQ,EAAW;EAChE,MAAM,MAAM,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,OAAO,QAAQ;AAGxD,MAAI,IAAI,SAAS,EAChB,QAAO,UAAU,SAAS,YAAY;AACrC,QAAK,MAAM,MAAM,IAAK,IAAG,SAAS,QAAQ;;;AAI7C,QAAO"}
1
+ {"version":3,"file":"hooks.mjs","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["import type { BaseContentItem } from \"./types/content\";\nimport type { CMSHooks, MaybePromise } from \"./types/hooks\";\nimport type { Logger } from \"./types/logger\";\nimport type { CMSPlugin } from \"./types/plugin\";\n\n/**\n * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。\n * beforeCache / afterRender はパイプライン(前の出力が次の入力)。\n * onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。\n */\nexport function mergeHooks<T extends BaseContentItem>(\n\tplugins: CMSPlugin<T>[],\n\tdirectHooks?: CMSHooks<T>,\n\tlogger?: Logger,\n): CMSHooks<T> {\n\tconst allHooks: CMSHooks<T>[] = [\n\t\t...plugins.map((p) => p.hooks ?? {}),\n\t\t...(directHooks ? [directHooks] : []),\n\t];\n\n\tif (allHooks.length === 0) return {};\n\n\treturn {\n\t\tbeforeCache: buildPipeline(allHooks, \"beforeCache\"),\n\t\tafterRender: buildRenderPipeline(allHooks),\n\t\tonCacheHit: buildObserver(allHooks, \"onCacheHit\", logger),\n\t\tonCacheMiss: buildObserver(allHooks, \"onCacheMiss\", logger),\n\t\tonCacheRevalidated: buildObserver(allHooks, \"onCacheRevalidated\", logger),\n\t\tonListCacheHit: buildObserver(allHooks, \"onListCacheHit\", logger),\n\t\tonListCacheMiss: buildObserver(allHooks, \"onListCacheMiss\", logger),\n\t\tonListCacheRevalidated: buildObserver(\n\t\t\tallHooks,\n\t\t\t\"onListCacheRevalidated\",\n\t\t\tlogger,\n\t\t),\n\t\tonError: buildObserver(allHooks, \"onError\", logger),\n\t\tonRenderStart: buildObserver(allHooks, \"onRenderStart\", logger),\n\t\tonRenderEnd: buildObserver(allHooks, \"onRenderEnd\", logger),\n\t};\n}\n\nfunction buildPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n\tkey: \"beforeCache\",\n): CMSHooks<T>[\"beforeCache\"] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"beforeCache\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (item) => {\n\t\tlet current = item;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current) as MaybePromise<typeof item>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildRenderPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n): CMSHooks<T>[\"afterRender\"] {\n\tconst fns = hooks.map((h) => h.afterRender).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"afterRender\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (html, item) => {\n\t\tlet current = html;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current, item) as MaybePromise<string>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildObserver<T extends BaseContentItem, K extends keyof CMSHooks<T>>(\n\thooks: CMSHooks<T>[],\n\tkey: K,\n\tlogger?: Logger,\n): CMSHooks<T>[K] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean);\n\tif (fns.length === 0) return undefined;\n\treturn ((...args: unknown[]) => {\n\t\tfor (const fn of fns) {\n\t\t\ttry {\n\t\t\t\t(fn as (...a: unknown[]) => void)(...args);\n\t\t\t} catch (err) {\n\t\t\t\tlogger?.error?.(\"観測フックで例外が発生\", {\n\t\t\t\t\thook: String(key),\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}) as CMSHooks<T>[K];\n}\n\n/** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */\nexport function mergeLoggers(\n\tplugins: Array<{ logger?: Partial<Logger> }>,\n\tdirectLogger?: Logger,\n): Logger | undefined {\n\tconst loggers: Partial<Logger>[] = [\n\t\t...plugins.map((p) => p.logger ?? {}),\n\t\t...(directLogger ? [directLogger] : []),\n\t];\n\tif (loggers.length === 0) return undefined;\n\n\tconst merged: Logger = {};\n\tfor (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n\t\tconst fns = loggers.map((l) => l[level]).filter(Boolean) as NonNullable<\n\t\t\tLogger[typeof level]\n\t\t>[];\n\t\tif (fns.length > 0) {\n\t\t\tmerged[level] = (message, context) => {\n\t\t\t\tfor (const fn of fns) fn(message, context);\n\t\t\t};\n\t\t}\n\t}\n\treturn merged;\n}\n"],"mappings":";;;;;;AAUA,SAAgB,WACf,SACA,aACA,QACc;CACd,MAAM,WAA0B,CAC/B,GAAG,QAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,CAAC,EACpC,GAAI,cAAc,CAAC,YAAY,GAAG,EAAE,CACpC;AAED,KAAI,SAAS,WAAW,EAAG,QAAO,EAAE;AAEpC,QAAO;EACN,aAAa,cAAc,UAAU,cAAc;EACnD,aAAa,oBAAoB,SAAS;EAC1C,YAAY,cAAc,UAAU,cAAc,OAAO;EACzD,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D,oBAAoB,cAAc,UAAU,sBAAsB,OAAO;EACzE,gBAAgB,cAAc,UAAU,kBAAkB,OAAO;EACjE,iBAAiB,cAAc,UAAU,mBAAmB,OAAO;EACnE,wBAAwB,cACvB,UACA,0BACA,OACA;EACD,SAAS,cAAc,UAAU,WAAW,OAAO;EACnD,eAAe,cAAc,UAAU,iBAAiB,OAAO;EAC/D,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D;;AAGF,SAAS,cACR,OACA,KAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AAGpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,SAAS;EACtB,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,QAAQ;AAE7B,SAAO;;;AAIT,SAAS,oBACR,OAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,YAAY,CAAC,OAAO,QAAQ;AAG3D,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,MAAM,SAAS;EAC5B,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,SAAS,KAAK;AAEnC,SAAO;;;AAIT,SAAS,cACR,OACA,KACA,QACiB;CACjB,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AACpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,UAAS,GAAG,SAAoB;AAC/B,OAAK,MAAM,MAAM,IAChB,KAAI;AACF,MAAiC,GAAG,KAAK;WAClC,KAAK;AACb,WAAQ,QAAQ,eAAe;IAC9B,MAAM,OAAO,IAAI;IACjB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,CAAC;;;;;AAON,SAAgB,aACf,SACA,cACqB;CACrB,MAAM,UAA6B,CAClC,GAAG,QAAQ,KAAK,MAAM,EAAE,UAAU,EAAE,CAAC,EACrC,GAAI,eAAe,CAAC,aAAa,GAAG,EAAE,CACtC;AACD,KAAI,QAAQ,WAAW,EAAG,QAAO,KAAA;CAEjC,MAAM,SAAiB,EAAE;AACzB,MAAK,MAAM,SAAS;EAAC;EAAS;EAAQ;EAAQ;EAAQ,EAAW;EAChE,MAAM,MAAM,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,OAAO,QAAQ;AAGxD,MAAI,IAAI,SAAS,EAChB,QAAO,UAAU,SAAS,YAAY;AACrC,QAAK,MAAM,MAAM,IAAK,IAAG,SAAS,QAAQ;;;AAI7C,QAAO"}
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { a as StorageBinary, c as ImageRef, i as CachedItemList, l as InlineNode, n as CMSSchemaProperties, o as ContentBlock, r as CachedItem, s as ContentResult, t as BaseContentItem } from "./content-WydAfQtk.mjs";
2
2
  import { a as CollectionConfig, c as InferCollectionItem, d as PropertyMap, f as WebhookConfig, i as CMSSchema, l as InvalidateScope, n as DocumentCacheAdapter, o as DataSource, r as ImageCacheAdapter, s as DataSourceFactory, t as CacheConfig, u as PropertyDef } from "./cache-D051BP4G.mjs";
3
- import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-fFMItkry.mjs";
3
+ import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-D8Lgf-Co.mjs";
4
4
  import { MemoryDocumentCacheOptions, MemoryImageCacheOptions, memoryDocumentCache, memoryImageCache } from "./cache/memory.mjs";
5
5
  import { noopDocumentCache, noopImageCache } from "./cache/noop.mjs";
6
- import { CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace } from "./errors.mjs";
6
+ import { BuiltInCMSErrorCode, CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace } from "./errors.mjs";
7
7
 
8
8
  //#region src/types/collection.d.ts
9
9
  /** 並び順指定。 */
@@ -71,6 +71,8 @@ interface CollectionClient<T extends BaseContentItem = BaseContentItem> {
71
71
  }
72
72
  //#endregion
73
73
  //#region src/types/config.d.ts
74
+ /** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */
75
+ type LogLevel = "debug" | "info" | "warn" | "error";
74
76
  /**
75
77
  * renderer プラグインの不透明型。
76
78
  * core は unified / remark / rehype に依存せず、このリストをそのまま renderer に渡すだけ。
@@ -113,8 +115,12 @@ type DataSourceMap = Record<string, DataSource<any>>;
113
115
  /**
114
116
  * コレクション別のページ構成セマンティクス。
115
117
  * `createCMS({ collections: { posts: { ... } } })` に渡す。
118
+ *
119
+ * `T` を指定するとコレクション固有の型付きフックを定義できる。
120
+ * `createCMS` の `dataSources` から `T` が自動推論されるため、アプリ側が
121
+ * `CMSHooks<Post>` などを直接記述する必要がなくなる。
116
122
  */
117
- interface CollectionSemantics {
123
+ interface CollectionSemantics<T extends BaseContentItem = BaseContentItem> {
118
124
  /**
119
125
  * slug として使うフィールド名(必須)。
120
126
  * DataSource の `properties` マップのキーと一致させる。
@@ -131,6 +137,12 @@ interface CollectionSemantics {
131
137
  * アクセス許可するステータス値。DataSource 側の `accessibleStatuses` より優先される。
132
138
  */
133
139
  accessibleStatuses?: readonly string[];
140
+ /**
141
+ * コレクション固有のライフサイクルフック。
142
+ * トップレベルの `hooks` と合成して実行される(グローバルフック → コレクションフックの順)。
143
+ * `T` が確定しているため `item.item.myField` など独自フィールドに型安全にアクセスできる。
144
+ */
145
+ hooks?: CMSHooks<T>;
134
146
  }
135
147
  /** `DataSourceMap` から各 T を抽出するユーティリティ型。 */
136
148
  type InferDataSourceItem<D> = D extends DataSource<infer T> ? T : BaseContentItem;
@@ -182,28 +194,43 @@ interface CreateCMSOptions<D extends DataSourceMap = DataSourceMap> {
182
194
  plugins?: CMSPlugin<any>[];
183
195
  /** ロガー。 */
184
196
  logger?: Logger;
197
+ /**
198
+ * ログレベルの下限。指定したレベル未満のログを内部で抑制する。
199
+ * Cloudflare Workers の Observability のように debug ログが課金対象になる環境では
200
+ * `"info"` を指定すると debug ログを出力しなくなる。
201
+ *
202
+ * @example
203
+ * createCMS({ ..., logLevel: "info" }) // debug ログを抑制
204
+ */
205
+ logLevel?: LogLevel;
185
206
  /** レートリミット・リトライ設定。 */
186
207
  rateLimiter?: RateLimiterConfig;
187
208
  /**
188
209
  * コレクション別のページ構成セマンティクス。
189
- * slug・status・公開条件を指定する。
210
+ * slug・status・公開条件・コレクション固有フックを指定する。
190
211
  * 指定したコレクションでは `slug` が必須(未指定時はエラー)。
191
212
  * 指定したコレクションの `publishedStatuses`/`accessibleStatuses` は
192
213
  * DataSource 側の設定より優先される。
193
214
  *
215
+ * `hooks` にコレクション固有フックを定義すると、`dataSources` の型から `T` が
216
+ * 自動推論されるため `CMSHooks<Post>` などを直接書かずに済む。
217
+ *
194
218
  * @example
195
219
  * createCMS({
196
- * dataSources: { posts: createNotionCollection({ ... }) },
220
+ * dataSources: { posts: createNotionCollection<Post>({ ... }) },
197
221
  * collections: {
198
222
  * posts: {
199
223
  * slug: "slug",
200
224
  * status: "status",
201
225
  * publishedStatuses: ["公開済み"],
226
+ * hooks: {
227
+ * onCacheHit: (slug, item) => console.log(item.item.title),
228
+ * },
202
229
  * }
203
230
  * }
204
231
  * })
205
232
  */
206
- collections?: Partial<Record<keyof D & string, CollectionSemantics>>;
233
+ collections?: { [K in keyof D]?: CollectionSemantics<InferDataSourceItem<D[K]>> };
207
234
  }
208
235
  //#endregion
209
236
  //#region src/cache.d.ts
@@ -303,7 +330,14 @@ interface RetryConfig {
303
330
  onRetry?: (attempt: number, status: number) => void;
304
331
  }
305
332
  declare const DEFAULT_RETRY_CONFIG: RetryConfig;
306
- /** 指数バックオフ(オプションでジッター付き)でリトライする。retryOn に含まれる HTTP エラーのみ対象。 */
333
+ /**
334
+ * 指数バックオフ(オプションでジッター付き)でリトライする。
335
+ *
336
+ * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。
337
+ * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。
338
+ * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して
339
+ * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。
340
+ */
307
341
  declare function withRetry<T>(fn: () => Promise<T>, config: RetryConfig): Promise<T>;
308
342
  //#endregion
309
343
  //#region src/collection.d.ts
@@ -396,5 +430,5 @@ interface NodePresetOptions {
396
430
  */
397
431
  declare function nodePreset(opts?: NodePresetOptions): Pick<CreateCMSOptions, "cache" | "renderer">;
398
432
  //#endregion
399
- export { type AdjacencyOptions, type BaseContentItem, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchema, type CMSSchemaProperties, type CacheConfig, type CachedItem, type CachedItemList, type CollectionClient, CollectionClientImpl, type CollectionConfig, type CollectionContext, type CollectionSemantics, type ContentBlock, type ContentConfig, type ContentResult, type CreateCMSOptions, DEFAULT_RETRY_CONFIG, type DataSource, type DataSourceFactory, type DataSourceMap, type DocumentCacheAdapter, type GetListOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheAdapter, type ImageRef, type InferCollectionItem, type InferDataSourceItem, type InlineNode, type InvalidateScope, type ItemWithContent, type Logger, type MaybePromise, type MemoryDocumentCacheOptions, type MemoryImageCacheOptions, type NodePresetOptions, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type RetryConfig, type SortOption, type StorageBinary, type WebhookConfig, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, memoryDocumentCache, memoryImageCache, mergeHooks, mergeLoggers, nodePreset, noopDocumentCache, noopImageCache, sha256Hex, withRetry };
433
+ export { type AdjacencyOptions, type BaseContentItem, type BuiltInCMSErrorCode, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchema, type CMSSchemaProperties, type CacheConfig, type CachedItem, type CachedItemList, type CollectionClient, CollectionClientImpl, type CollectionConfig, type CollectionContext, type CollectionSemantics, type ContentBlock, type ContentConfig, type ContentResult, type CreateCMSOptions, DEFAULT_RETRY_CONFIG, type DataSource, type DataSourceFactory, type DataSourceMap, type DocumentCacheAdapter, type GetListOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheAdapter, type ImageRef, type InferCollectionItem, type InferDataSourceItem, type InlineNode, type InvalidateScope, type ItemWithContent, type Logger, type MaybePromise, type MemoryDocumentCacheOptions, type MemoryImageCacheOptions, type NodePresetOptions, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type RetryConfig, type SortOption, type StorageBinary, type WebhookConfig, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, memoryDocumentCache, memoryImageCache, mergeHooks, mergeLoggers, nodePreset, noopDocumentCache, noopImageCache, sha256Hex, withRetry };
400
434
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -80,7 +80,14 @@ async function fetchAndCacheImage(cache, notionUrl, imageProxyBase, logger) {
80
80
  }
81
81
  return proxyUrl;
82
82
  }
83
- /** ImageCacheAdapter と imageProxyBase から cacheImage 関数を構築するファクトリ。 */
83
+ /**
84
+ * ImageCacheAdapter と imageProxyBase から cacheImage 関数を構築するファクトリ。
85
+ *
86
+ * 返り値の関数は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして
87
+ * {@link ImageCacheAdapter} に保存後、フロントエンドへの配信用プロキシ URL を返す。
88
+ * Content-Type はレスポンスヘッダまたは URL の拡張子から推測する。
89
+ * タイムアウトは 10 秒固定。
90
+ */
84
91
  function buildCacheImageFn(cache, imageProxyBase, logger) {
85
92
  return (notionUrl) => fetchAndCacheImage(cache, notionUrl, imageProxyBase, logger);
86
93
  }
@@ -193,7 +200,14 @@ const DEFAULT_RETRY_CONFIG = {
193
200
  baseDelayMs: 1e3,
194
201
  jitter: true
195
202
  };
196
- /** 指数バックオフ(オプションでジッター付き)でリトライする。retryOn に含まれる HTTP エラーのみ対象。 */
203
+ /**
204
+ * 指数バックオフ(オプションでジッター付き)でリトライする。
205
+ *
206
+ * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。
207
+ * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。
208
+ * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して
209
+ * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。
210
+ */
197
211
  async function withRetry(fn, config) {
198
212
  let lastError;
199
213
  for (let attempt = 0; attempt <= config.maxRetries; attempt++) try {
@@ -394,7 +408,7 @@ var CollectionClientImpl = class {
394
408
  collection: this.ctx.collection,
395
409
  cacheAdapter: this.ctx.docCache.name
396
410
  });
397
- this.ctx.hooks.onListCacheHit?.(cached.items, cached.cachedAt);
411
+ this.ctx.hooks.onListCacheHit?.(cached);
398
412
  return cached.items;
399
413
  }
400
414
  this.ctx.logger?.debug?.("リストキャッシュミス、フェッチ", {
@@ -426,7 +440,7 @@ var CollectionClientImpl = class {
426
440
  collection: this.ctx.collection,
427
441
  notionUpdatedAt: cached.notionUpdatedAt
428
442
  });
429
- this.ctx.hooks.onCacheUpdate?.(slug, entry);
443
+ this.ctx.hooks.onCacheRevalidated?.(slug, entry);
430
444
  } else if (this.ctx.ttlMs !== void 0) {
431
445
  await this.ctx.docCache.setItem(slug, {
432
446
  ...cached,
@@ -450,15 +464,16 @@ var CollectionClientImpl = class {
450
464
  try {
451
465
  const items = await this.fetchListRaw();
452
466
  if (this.ctx.source.getListVersion(items) !== this.ctx.source.getListVersion(cached.items)) {
453
- await this.ctx.docCache.setList({
467
+ const listEntry = {
454
468
  items,
455
469
  cachedAt: Date.now()
456
- });
470
+ };
471
+ await this.ctx.docCache.setList(listEntry);
457
472
  this.ctx.logger?.debug?.("SWR: リスト差分を検出、キャッシュを差し替え", {
458
473
  operation: "getList:bg",
459
474
  collection: this.ctx.collection
460
475
  });
461
- this.ctx.hooks.onListCacheUpdate?.(items);
476
+ this.ctx.hooks.onListCacheRevalidated?.(listEntry);
462
477
  } else if (this.ctx.ttlMs !== void 0) {
463
478
  await this.ctx.docCache.setList({
464
479
  ...cached,
@@ -687,6 +702,25 @@ function scopeDocumentCache(base, collection) {
687
702
  }
688
703
  };
689
704
  }
705
+ const LOG_LEVEL_ORDER = {
706
+ debug: 0,
707
+ info: 1,
708
+ warn: 2,
709
+ error: 3
710
+ };
711
+ /** `logger` から `minLevel` 未満のレベルを除いた新しい Logger を返す。 */
712
+ function applyLogLevel(logger, minLevel) {
713
+ if (!logger) return void 0;
714
+ const minOrder = LOG_LEVEL_ORDER[minLevel];
715
+ const filtered = {};
716
+ for (const level of [
717
+ "debug",
718
+ "info",
719
+ "warn",
720
+ "error"
721
+ ]) if (LOG_LEVEL_ORDER[level] >= minOrder) filtered[level] = logger[level];
722
+ return filtered;
723
+ }
690
724
  /**
691
725
  * `preset` オプションを解決して `cache` / `renderer` のデフォルトを補完する内部関数。
692
726
  * 明示的な `cache` / `renderer` がある場合はそちらが優先される。
@@ -751,7 +785,8 @@ function createCMS(opts) {
751
785
  const contentConfig = opts.content;
752
786
  const rendererFn = resolved.renderer;
753
787
  const waitUntil = opts.waitUntil;
754
- const logger = mergeLoggers(opts.plugins ?? [], opts.logger);
788
+ const baseLogger = mergeLoggers(opts.plugins ?? [], opts.logger);
789
+ const logger = opts.logLevel ? applyLogLevel(baseLogger, opts.logLevel) : baseLogger;
755
790
  const hooks = mergeHooks(opts.plugins ?? [], opts.hooks, logger);
756
791
  const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;
757
792
  const retryConfig = {
@@ -765,23 +800,27 @@ function createCMS(opts) {
765
800
  const source = opts.dataSources[name];
766
801
  const scopedCache = scopeDocumentCache(baseDocCache, name);
767
802
  scopedCaches.push(scopedCache);
768
- const renderCtx = {
769
- source,
770
- rendererFn,
771
- imgCache,
772
- hasImageCache,
773
- imageProxyBase,
774
- contentConfig,
775
- hooks,
776
- logger
777
- };
778
803
  const col = opts.collections?.[name];
804
+ const colHooks = col?.hooks;
805
+ const collectionHooks = colHooks ? mergeHooks([{
806
+ name: `${name}:global`,
807
+ hooks
808
+ }], colHooks, logger) : hooks;
779
809
  collections[name] = new CollectionClientImpl({
780
810
  collection: name,
781
811
  source,
782
812
  docCache: scopedCache,
783
- render: renderCtx,
784
- hooks,
813
+ render: {
814
+ source,
815
+ rendererFn,
816
+ imgCache,
817
+ hasImageCache,
818
+ imageProxyBase,
819
+ contentConfig,
820
+ hooks: collectionHooks,
821
+ logger
822
+ },
823
+ hooks: collectionHooks,
785
824
  logger,
786
825
  ttlMs,
787
826
  publishedStatuses: col?.publishedStatuses ? [...col.publishedStatuses] : [],
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/preset-node.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n\tconst data = new TextEncoder().encode(input);\n\tconst hash = await crypto.subtle.digest(\"SHA-256\", data);\n\treturn Array.from(new Uint8Array(hash))\n\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t.join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n\tif (ttlMs === undefined) return false;\n\treturn Date.now() - cachedAt > ttlMs;\n}\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheAdapter, Logger, StorageBinary } from \"./types/index\";\n\n/** レスポンスヘッダまたはURLの拡張子からContent-Typeを推測する。 */\nfunction inferContentType(\n\turl: string,\n\tresponseContentType: string | null,\n): string {\n\tif (responseContentType?.startsWith(\"image/\")) {\n\t\treturn responseContentType.split(\";\")[0].trim();\n\t}\n\tif (url.includes(\".png\")) return \"image/png\";\n\tif (url.includes(\".gif\")) return \"image/gif\";\n\tif (url.includes(\".webp\")) return \"image/webp\";\n\treturn \"image/jpeg\";\n}\n\n/**\n * Notion画像URLをfetchしてImageCacheAdapterにキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n\tcache: ImageCacheAdapter,\n\tnotionUrl: string,\n\timageProxyBase: string,\n\tlogger?: Logger,\n): Promise<string> {\n\tconst hash = await sha256Hex(notionUrl);\n\tconst proxyUrl = `${imageProxyBase}/${hash}`;\n\n\tconst existing = await cache.get(hash);\n\tif (existing) {\n\t\tlogger?.debug?.(\"画像キャッシュヒット\", {\n\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\tcacheAdapter: cache.name,\n\t\t\timageHash: hash,\n\t\t});\n\t\treturn proxyUrl;\n\t}\n\n\tlogger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n\t\toperation: \"fetchAndCacheImage\",\n\t\tcacheAdapter: cache.name,\n\t\timageHash: hash,\n\t});\n\n\ttry {\n\t\tconst response = await fetch(notionUrl, {\n\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"cache/image_fetch_failed\",\n\t\t\t\tmessage: `Failed to fetch Notion image: HTTP ${response.status}`,\n\t\t\t\tcontext: {\n\t\t\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\t\t\tnotionUrl,\n\t\t\t\t\thttpStatus: response.status,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst data = await response.arrayBuffer();\n\t\tconst contentType = inferContentType(\n\t\t\tnotionUrl,\n\t\t\tresponse.headers.get(\"content-type\"),\n\t\t);\n\t\tawait cache.set(hash, data, contentType);\n\t\tlogger?.debug?.(\"画像をキャッシュに保存\", {\n\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\tcacheAdapter: cache.name,\n\t\t\timageHash: hash,\n\t\t});\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"cache/io_failed\",\n\t\t\tmessage: \"Failed to fetch or cache Notion image.\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"fetchAndCacheImage\", notionUrl },\n\t\t});\n\t}\n\n\treturn proxyUrl;\n}\n\n/** ImageCacheAdapter と imageProxyBase から cacheImage 関数を構築するファクトリ。 */\nexport function buildCacheImageFn(\n\tcache: ImageCacheAdapter,\n\timageProxyBase: string,\n\tlogger?: Logger,\n): (notionUrl: string) => Promise<string> {\n\treturn (notionUrl) =>\n\t\tfetchAndCacheImage(cache, notionUrl, imageProxyBase, logger);\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n\tBaseContentItem,\n\tCachedItem,\n\tCMSHooks,\n\tContentConfig,\n\tDataSource,\n\tImageCacheAdapter,\n\tLogger,\n\tRendererFn,\n} from \"./types/index\";\n\n/** `buildCachedItem` に必要な CMS の依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n\tsource: DataSource<T>;\n\trendererFn: RendererFn | undefined;\n\timgCache: ImageCacheAdapter;\n\thasImageCache: boolean;\n\timageProxyBase: string;\n\tcontentConfig: ContentConfig | undefined;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n}\n\n/**\n * コンテンツアイテムをソースから Markdown ロード → blocks 生成 → HTML レンダリング\n * → フック適用まで実行し、キャッシュ保存用の `CachedItem` を返す。\n */\nexport async function buildCachedItem<T extends BaseContentItem>(\n\titem: T,\n\tctx: RenderContext<T>,\n): Promise<CachedItem<T>> {\n\tconst start = Date.now();\n\tctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n\t\tslug: item.slug,\n\t\tpageId: item.id,\n\t});\n\tctx.hooks.onRenderStart?.(item.slug);\n\n\tlet markdown: string;\n\ttry {\n\t\tmarkdown = await ctx.source.loadMarkdown(item);\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"source/load_markdown_failed\",\n\t\t\tmessage: \"Failed to load markdown from source.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:loadMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tlet blocks: ContentBlock[] = [];\n\ttry {\n\t\tblocks = await ctx.source.loadBlocks(item);\n\t} catch (err) {\n\t\tctx.logger?.warn?.(\"loadBlocks に失敗したため raw フォールバック\", {\n\t\t\tslug: item.slug,\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\tblocks = [];\n\t}\n\n\tconst cacheImage = ctx.hasImageCache\n\t\t? buildCacheImageFn(ctx.imgCache, ctx.imageProxyBase, ctx.logger)\n\t\t: undefined;\n\n\tlet html: string;\n\tconst rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\ttry {\n\t\thtml = await rendererFn(markdown, {\n\t\t\timageProxyBase: ctx.imageProxyBase,\n\t\t\tcacheImage,\n\t\t\tremarkPlugins: ctx.contentConfig?.remarkPlugins,\n\t\t\trehypePlugins: ctx.contentConfig?.rehypePlugins,\n\t\t});\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage: \"Failed to render markdown.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:renderMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tif (ctx.hooks.afterRender) {\n\t\thtml = await ctx.hooks.afterRender(html, item);\n\t}\n\n\tlet result: CachedItem<T> = {\n\t\thtml,\n\t\tblocks,\n\t\tmarkdown,\n\t\titem,\n\t\tnotionUpdatedAt: ctx.source.getLastModified(item),\n\t\tcachedAt: Date.now(),\n\t};\n\n\tif (ctx.hooks.beforeCache) {\n\t\tresult = (await ctx.hooks.beforeCache(result)) as CachedItem<T>;\n\t}\n\n\tconst durationMs = Date.now() - start;\n\tctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n\t\tslug: item.slug,\n\t\tdurationMs,\n\t});\n\tctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n\treturn result;\n}\n\n/**\n * renderer オプション未指定時のフォールバック。\n * @notion-headless-cms/renderer を動的 import する。\n * createCMS({ renderer }) で明示注入された場合はこのパスを通らない。\n */\nexport async function loadDefaultRenderer(): Promise<RendererFn> {\n\ttry {\n\t\tconst mod = await import(\"@notion-headless-cms/renderer\");\n\t\treturn mod.renderMarkdown as RendererFn;\n\t} catch (err) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage:\n\t\t\t\t\"renderer オプションが未指定で @notion-headless-cms/renderer が見つかりません。\" +\n\t\t\t\t\" createCMS({ renderer }) でレンダラーを注入するか、@notion-headless-cms/renderer をインストールしてください。\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"loadDefaultRenderer\" },\n\t\t});\n\t}\n}\n","export interface RetryConfig {\n\tretryOn: number[];\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n\t/** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n\tjitter?: boolean;\n\tonRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n\tretryOn: [429, 502, 503],\n\tmaxRetries: 4,\n\tbaseDelayMs: 1000,\n\tjitter: true,\n};\n\n/** 指数バックオフ(オプションでジッター付き)でリトライする。retryOn に含まれる HTTP エラーのみ対象。 */\nexport async function withRetry<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig,\n): Promise<T> {\n\tlet lastError: unknown;\n\tfor (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (err) {\n\t\t\tconst status = (err as { status?: number }).status;\n\t\t\tif (status === undefined || !config.retryOn.includes(status)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tlastError = err;\n\t\t\tif (attempt < config.maxRetries) {\n\t\t\t\tconfig.onRetry?.(attempt + 1, status);\n\t\t\t\tconst jitterFactor =\n\t\t\t\t\tconfig.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n\t\t\t\tconst delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t\t}\n\t\t}\n\t}\n\tthrow lastError;\n}\n","import { isStale } from \"./cache\";\nimport type { ContentBlock, ContentResult } from \"./content/blocks\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItem } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n\tAdjacencyOptions,\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tCMSHooks,\n\tCollectionClient,\n\tDataSource,\n\tDocumentCacheAdapter,\n\tGetListOptions,\n\tItemWithContent,\n\tLogger,\n\tSortOption,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n */\nexport function collectionKey(collection: string, slug?: string): string {\n\treturn slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n\tcollection: string;\n\tsource: DataSource<T>;\n\tdocCache: DocumentCacheAdapter<T>;\n\trender: RenderContext<T>;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n\tttlMs: number | undefined;\n\tpublishedStatuses: string[];\n\taccessibleStatuses: string[];\n\tretryConfig: RetryConfig;\n\tmaxConcurrent: number;\n\twaitUntil: ((p: Promise<unknown>) => void) | undefined;\n\t/**\n\t * slug として使うフィールド名。\n\t * `createCMS({ collections })` で指定した値。\n\t * 設定時は `source.properties[slugField].notion` を Notion プロパティ名として\n\t * `findByProp` を呼び出す。\n\t */\n\tslugField?: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n\timplements CollectionClient<T>\n{\n\tconstructor(private readonly ctx: CollectionContext<T>) {}\n\n\t// ── 基本取得 ──────────────────────────────────────────────────────────\n\n\tasync getItem(slug: string): Promise<ItemWithContent<T> | null> {\n\t\tconst cached = await this.ctx.docCache.getItem(slug);\n\t\tif (cached) {\n\t\t\tif (\n\t\t\t\tthis.ctx.ttlMs !== undefined &&\n\t\t\t\tisStale(cached.cachedAt, this.ctx.ttlMs)\n\t\t\t) {\n\t\t\t\t// TTL 設定あり + 期限切れ: ブロッキングフェッチ(stale を返さない)\n\t\t\t\tthis.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n\t\t\t\t\toperation: \"getItem\",\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t\t});\n\t\t\t\tthis.ctx.hooks.onCacheMiss?.(slug);\n\t\t\t\tconst item = await this.findRaw(slug);\n\t\t\t\tif (!item) return null;\n\t\t\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\t\t\tawait this.ctx.docCache.setItem(slug, entry);\n\t\t\t\treturn this.attachContent(entry.item, entry);\n\t\t\t}\n\t\t\t// TTL 未設定 or 期限内: キャッシュを即時返却し、バックグラウンドで差分チェック\n\t\t\tconst bg = this.checkAndUpdateItemBg(slug, cached);\n\t\t\tif (this.ctx.waitUntil) {\n\t\t\t\tthis.ctx.waitUntil(bg);\n\t\t\t}\n\t\t\tthis.ctx.logger?.debug?.(\"キャッシュヒット\", {\n\t\t\t\toperation: \"getItem\",\n\t\t\t\tslug,\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t\tcachedAt: cached.cachedAt,\n\t\t\t});\n\t\t\tthis.ctx.hooks.onCacheHit?.(slug, cached);\n\t\t\treturn this.attachContent(cached.item, cached);\n\t\t}\n\n\t\t// キャッシュなし: 同期フェッチ\n\t\tthis.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n\t\t\toperation: \"getItem\",\n\t\t\tslug,\n\t\t\tcollection: this.ctx.collection,\n\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t});\n\t\tthis.ctx.hooks.onCacheMiss?.(slug);\n\t\tconst item = await this.findRaw(slug);\n\t\tif (!item) {\n\t\t\tthis.ctx.logger?.debug?.(\"アイテムが見つかりません\", {\n\t\t\t\toperation: \"getItem\",\n\t\t\t\tslug,\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\n\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\tconst save = this.ctx.docCache.setItem(slug, entry);\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\n\t\treturn this.attachContent(entry.item, entry);\n\t}\n\n\tasync getList(opts?: GetListOptions<T>): Promise<T[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn applyGetListOptions(items, opts);\n\t}\n\n\t// ── SSG / ナビゲーション ─────────────────────────────────────────────\n\n\tasync getStaticParams(): Promise<{ slug: string }[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => ({ slug: item.slug }));\n\t}\n\n\tasync getStaticPaths(): Promise<string[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => item.slug);\n\t}\n\n\tasync adjacent(\n\t\tslug: string,\n\t\topts?: AdjacencyOptions<T>,\n\t): Promise<{ prev: T | null; next: T | null }> {\n\t\tconst items = applyGetListOptions(await this.fetchList(), {\n\t\t\tsort: opts?.sort,\n\t\t});\n\t\tconst index = items.findIndex((it) => it.slug === slug);\n\t\tif (index === -1) return { prev: null, next: null };\n\t\treturn {\n\t\t\tprev: index > 0 ? (items[index - 1] ?? null) : null,\n\t\t\tnext: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n\t\t};\n\t}\n\n\t// ── キャッシュ ────────────────────────────────────────────────────────\n\n\tasync revalidate(scope?: \"all\" | { slug: string }): Promise<void> {\n\t\tthis.ctx.logger?.debug?.(\"キャッシュを無効化\", {\n\t\t\toperation: \"revalidate\",\n\t\t\tcollection: this.ctx.collection,\n\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\tslug: typeof scope === \"object\" ? scope.slug : undefined,\n\t\t});\n\t\tif (!this.ctx.docCache.invalidate) return;\n\t\tif (scope === undefined || scope === \"all\") {\n\t\t\tawait this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n\t\t} else {\n\t\t\tawait this.ctx.docCache.invalidate({\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tslug: scope.slug,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync prefetch(opts?: {\n\t\tconcurrency?: number;\n\t\tonProgress?: (done: number, total: number) => void;\n\t}): Promise<{ ok: number; failed: number }> {\n\t\tconst items = await this.fetchListRaw();\n\t\tconst concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n\t\tlet ok = 0;\n\t\tlet failed = 0;\n\n\t\tfor (let i = 0; i < items.length; i += concurrency) {\n\t\t\tconst chunk = items.slice(i, i + concurrency);\n\t\t\tawait Promise.all(\n\t\t\t\tchunk.map(async (item) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rendered = await buildCachedItem(item, this.ctx.render);\n\t\t\t\t\t\tawait this.ctx.docCache.setItem(item.slug, rendered);\n\t\t\t\t\t\tok++;\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tfailed++;\n\t\t\t\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\t\t\t\"prefetch: アイテムの事前レンダリングに失敗\",\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\t\t\tpageId: item.id,\n\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t\topts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n\t\t}\n\n\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\treturn { ok, failed };\n\t}\n\n\t// ── 内部 ──────────────────────────────────────────────────────────────\n\n\tprivate attachContent(item: T, cached: CachedItem<T>): ItemWithContent<T> {\n\t\tconst ctx = this.ctx;\n\t\tlet blocksCache: ContentBlock[] | undefined;\n\t\tlet htmlCache: string | undefined = cached.html;\n\t\tlet markdownCache: string | undefined;\n\n\t\tconst content: ContentResult = {\n\t\t\tget blocks(): ContentBlock[] {\n\t\t\t\tif (!blocksCache) {\n\t\t\t\t\t// lazy: キャッシュに blocks が保存されていない場合、簡易な single-raw に落とす\n\t\t\t\t\tblocksCache = [\n\t\t\t\t\t\t{ type: \"raw\", html: cached.html } satisfies ContentBlock,\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\treturn blocksCache;\n\t\t\t},\n\t\t\tasync html(): Promise<string> {\n\t\t\t\tif (htmlCache !== undefined) return htmlCache;\n\t\t\t\thtmlCache = cached.html;\n\t\t\t\treturn htmlCache;\n\t\t\t},\n\t\t\tasync markdown(): Promise<string> {\n\t\t\t\tif (markdownCache !== undefined) return markdownCache;\n\t\t\t\tmarkdownCache = await ctx.source.loadMarkdown(item);\n\t\t\t\treturn markdownCache;\n\t\t\t},\n\t\t};\n\n\t\tif (cached.blocks) blocksCache = cached.blocks;\n\n\t\treturn Object.assign(Object.create(null) as object, item, {\n\t\t\tcontent,\n\t\t}) as ItemWithContent<T>;\n\t}\n\n\tprivate async fetchList(): Promise<T[]> {\n\t\tconst cached = await this.ctx.docCache.getList();\n\t\tif (cached) {\n\t\t\tif (\n\t\t\t\tthis.ctx.ttlMs !== undefined &&\n\t\t\t\tisStale(cached.cachedAt, this.ctx.ttlMs)\n\t\t\t) {\n\t\t\t\t// TTL 設定あり + 期限切れ: ブロッキングフェッチ(stale を返さない)\n\t\t\t\tthis.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n\t\t\t\t\toperation: \"getList\",\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t\t});\n\t\t\t\tthis.ctx.hooks.onListCacheMiss?.();\n\t\t\t\tconst items = await this.fetchListRaw();\n\t\t\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\t\t\treturn items;\n\t\t\t}\n\t\t\t// TTL 未設定 or 期限内: キャッシュを即時返却し、バックグラウンドで差分チェック\n\t\t\tconst bg = this.checkAndUpdateListBg(cached);\n\t\t\tif (this.ctx.waitUntil) {\n\t\t\t\tthis.ctx.waitUntil(bg);\n\t\t\t}\n\t\t\tthis.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n\t\t\t\toperation: \"getList\",\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t});\n\t\t\tthis.ctx.hooks.onListCacheHit?.(cached.items, cached.cachedAt);\n\t\t\treturn cached.items;\n\t\t}\n\n\t\t// キャッシュなし: 同期フェッチ\n\t\tthis.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n\t\t\toperation: \"getList\",\n\t\t\tcollection: this.ctx.collection,\n\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t});\n\t\tthis.ctx.hooks.onListCacheMiss?.();\n\t\tconst items = await this.fetchListRaw();\n\t\tconst cachedAt = Date.now();\n\t\tconst save = this.ctx.docCache.setList({ items, cachedAt });\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\t\treturn items;\n\t}\n\n\tprivate async checkAndUpdateItemBg(\n\t\tslug: string,\n\t\tcached: CachedItem<T>,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst item = await this.findRaw(slug);\n\t\t\tif (!item) return;\n\t\t\tif (this.ctx.source.getLastModified(item) !== cached.notionUpdatedAt) {\n\t\t\t\t// 更新あり: 再レンダリングしてキャッシュを差し替える\n\t\t\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\t\t\tawait this.ctx.docCache.setItem(slug, entry);\n\t\t\t\tthis.ctx.logger?.debug?.(\"SWR: 差分を検出、キャッシュを差し替え\", {\n\t\t\t\t\toperation: \"getItem:bg\",\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\tnotionUpdatedAt: cached.notionUpdatedAt,\n\t\t\t\t});\n\t\t\t\tthis.ctx.hooks.onCacheUpdate?.(slug, entry);\n\t\t\t} else if (this.ctx.ttlMs !== undefined) {\n\t\t\t\t// 変更なし + TTL あり: cachedAt をリセットして次回の期限切れを先送りする\n\t\t\t\tawait this.ctx.docCache.setItem(slug, {\n\t\t\t\t\t...cached,\n\t\t\t\t\tcachedAt: Date.now(),\n\t\t\t\t});\n\t\t\t\tthis.ctx.logger?.debug?.(\"SWR: 差分なし、TTL をリセット\", {\n\t\t\t\t\toperation: \"getItem:bg\",\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n\t\t\t\t{\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n\t\ttry {\n\t\t\tconst items = await this.fetchListRaw();\n\t\t\tif (\n\t\t\t\tthis.ctx.source.getListVersion(items) !==\n\t\t\t\tthis.ctx.source.getListVersion(cached.items)\n\t\t\t) {\n\t\t\t\t// 更新あり: リストを差し替える\n\t\t\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\t\t\tthis.ctx.logger?.debug?.(\n\t\t\t\t\t\"SWR: リスト差分を検出、キャッシュを差し替え\",\n\t\t\t\t\t{\n\t\t\t\t\t\toperation: \"getList:bg\",\n\t\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthis.ctx.hooks.onListCacheUpdate?.(items);\n\t\t\t} else if (this.ctx.ttlMs !== undefined) {\n\t\t\t\t// 変更なし + TTL あり: cachedAt をリセットする\n\t\t\t\tawait this.ctx.docCache.setList({\n\t\t\t\t\t...cached,\n\t\t\t\t\tcachedAt: Date.now(),\n\t\t\t\t});\n\t\t\t\tthis.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n\t\t\t\t\toperation: \"getList:bg\",\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\"SWR: リストのバックグラウンド差分チェックに失敗\",\n\t\t\t\t{\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate fetchListRaw(): Promise<T[]> {\n\t\treturn withRetry(\n\t\t\t() =>\n\t\t\t\tthis.ctx.source.list({\n\t\t\t\t\tpublishedStatuses:\n\t\t\t\t\t\tthis.ctx.publishedStatuses.length > 0\n\t\t\t\t\t\t\t? this.ctx.publishedStatuses\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t}),\n\t\t\t{\n\t\t\t\t...this.ctx.retryConfig,\n\t\t\t\tonRetry: (attempt, status) => {\n\t\t\t\t\tthis.ctx.logger?.warn?.(\"getList() リトライ中\", { attempt, status });\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate async findRaw(slug: string): Promise<T | null> {\n\t\tconst retryOpts = {\n\t\t\t...this.ctx.retryConfig,\n\t\t\tonRetry: (attempt: number, status: number) => {\n\t\t\t\tthis.ctx.logger?.warn?.(\"getItem() リトライ中\", {\n\t\t\t\t\tattempt,\n\t\t\t\t\tstatus,\n\t\t\t\t\tslug,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\n\t\t// slug フィールドが指定され、DataSource が findByProp を持つ場合は\n\t\t// Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n\t\tconst slugField = this.ctx.slugField;\n\t\tconst notionPropName = slugField\n\t\t\t? this.ctx.source.properties?.[slugField]?.notion\n\t\t\t: undefined;\n\n\t\tlet item: T | null;\n\t\tconst findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n\t\tif (notionPropName && findByProp) {\n\t\t\titem = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n\t\t} else {\n\t\t\t// フォールバック: list して線形探索\n\t\t\tconst all = await withRetry(() => this.ctx.source.list(), retryOpts);\n\t\t\titem = all.find((i) => i.slug === slug) ?? null;\n\t\t}\n\n\t\tif (!item) return null;\n\t\tif (\n\t\t\tthis.ctx.accessibleStatuses.length > 0 &&\n\t\t\t(!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\treturn item;\n\t}\n}\n\nfunction applyGetListOptions<T extends BaseContentItem>(\n\titems: T[],\n\topts?: GetListOptions<T>,\n): T[] {\n\tif (!opts) return items;\n\tlet result = items;\n\n\tif (opts.statuses && opts.statuses.length > 0) {\n\t\tconst allow = new Set(opts.statuses);\n\t\tresult = result.filter(\n\t\t\t(it) => it.status !== undefined && allow.has(it.status),\n\t\t);\n\t}\n\n\tif (opts.tag) {\n\t\tconst tag = opts.tag;\n\t\tresult = result.filter((it) => {\n\t\t\tconst tags = (it as unknown as { tags?: unknown }).tags;\n\t\t\treturn Array.isArray(tags) && tags.includes(tag);\n\t\t});\n\t}\n\n\tif (opts.where) {\n\t\tconst where = opts.where;\n\t\tresult = result.filter((it) =>\n\t\t\tObject.entries(where).every(\n\t\t\t\t([key, value]) => (it as Record<string, unknown>)[key] === value,\n\t\t\t),\n\t\t);\n\t}\n\n\tif (opts.sort) {\n\t\tresult = [...result].sort(makeComparator(opts.sort));\n\t}\n\n\tconst skip = opts.skip ?? 0;\n\tconst limit = opts.limit;\n\tif (skip > 0 || limit !== undefined) {\n\t\tresult = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n\t}\n\n\treturn result;\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n\tsort: SortOption<T>,\n): (a: T, b: T) => number {\n\tconst by = sort.by;\n\tconst dir = sort.direction === \"asc\" ? 1 : -1;\n\treturn (a, b) => {\n\t\tconst av = (a as Record<string, unknown>)[by];\n\t\tconst bv = (b as Record<string, unknown>)[by];\n\t\tif (av === bv) return 0;\n\t\tif (av === undefined) return 1;\n\t\tif (bv === undefined) return -1;\n\t\t// biome-ignore lint/suspicious/noExplicitAny: 汎用比較\n\t\treturn (av as any) > (bv as any) ? dir : -dir;\n\t};\n}\n","import type { ImageCacheAdapter, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n\t/** マウントするベースパス。デフォルト `/api/cms`。 */\n\tbasePath?: string;\n\t/** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n\timagesPath?: string;\n\t/** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n\trevalidatePath?: string;\n\t/** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n\twebhookSecret?: string;\n\t/** デフォルト実装を無効化する場合 true。 */\n\tdisabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n\timageCache: ImageCacheAdapter;\n\t/** コレクション名で DataSource を取り出し parseWebhook にフォワードする。 */\n\tparseWebhook(\n\t\treq: Request,\n\t\twebhookSecret: string | undefined,\n\t): Promise<InvalidateScope | null>;\n\trevalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n\tbasePath: \"/api/cms\",\n\timagesPath: \"/images\",\n\trevalidatePath: \"/revalidate\",\n} as const;\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` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n\tadapter: HandlerAdapter,\n\topts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n\tconst basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n\tconst imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n\tconst revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n\treturn async (req: Request): Promise<Response> => {\n\t\tconst url = new URL(req.url);\n\t\tconst path = url.pathname;\n\n\t\tif (!path.startsWith(basePath)) {\n\t\t\treturn new Response(\"Not Found\", { status: 404 });\n\t\t}\n\t\tconst rel = path.slice(basePath.length) || \"/\";\n\n\t\t// 画像: GET {basePath}/images/:hash\n\t\tif (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n\t\t\tconst hash = rel.slice(imagesPath.length + 1);\n\t\t\tif (!hash) return new Response(\"Bad Request\", { status: 400 });\n\t\t\tconst object = await adapter.imageCache.get(hash);\n\t\t\tif (!object) return new Response(\"Not Found\", { status: 404 });\n\t\t\tconst headers = new Headers();\n\t\t\tif (object.contentType) headers.set(\"content-type\", object.contentType);\n\t\t\theaders.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n\t\t\treturn new Response(object.data, { headers });\n\t\t}\n\n\t\t// Revalidate: POST {basePath}/revalidate\n\t\tif (req.method === \"POST\" && rel === revalidatePath) {\n\t\t\tconst scope = await adapter.parseWebhook(req, opts.webhookSecret);\n\t\t\tif (!scope) {\n\t\t\t\treturn new Response(JSON.stringify({ ok: false, reason: \"invalid\" }), {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait adapter.revalidate(scope);\n\t\t\treturn new Response(JSON.stringify({ ok: true, scope }), {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t};\n}\n\nfunction trimTrailingSlash(s: string): string {\n\treturn s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { memoryDocumentCache, memoryImageCache } from \"./cache/memory\";\nimport type { CacheConfig, CreateCMSOptions, RendererFn } from \"./types/index\";\n\n/** `nodePreset()` のオプション。 */\nexport interface NodePresetOptions {\n\t/**\n\t * キャッシュ設定。\n\t * - 省略時: memoryDocumentCache + memoryImageCache をデフォルト有効化\n\t * - `\"disabled\"`: キャッシュを完全無効化\n\t * - オブジェクト: 任意の cache adapter を差し込む\n\t */\n\tcache?: CacheConfig | \"disabled\";\n\t/** SWR の TTL (ミリ秒)。`cache` をオブジェクトで渡した場合はそちらが優先される。 */\n\tttlMs?: number;\n\t/** カスタムレンダラー。未指定時は core が @notion-headless-cms/renderer を動的ロード。 */\n\trenderer?: RendererFn;\n}\n\n/**\n * Node.js ランタイム向けの `createCMS` オプションプリセット。\n * メモリキャッシュをデフォルト有効にした `{ cache, renderer }` を返す。\n *\n * @example\n * import { createCMS, nodePreset } from \"@notion-headless-cms/core\";\n * import { cmsDataSources } from \"./generated/cms-schema\";\n *\n * const cms = createCMS({\n * ...nodePreset({ ttlMs: 5 * 60_000 }),\n * dataSources: cmsDataSources,\n * });\n */\nexport function nodePreset(\n\topts: NodePresetOptions = {},\n): Pick<CreateCMSOptions, \"cache\" | \"renderer\"> {\n\tif (opts.cache === \"disabled\") {\n\t\treturn { cache: undefined, renderer: opts.renderer };\n\t}\n\treturn {\n\t\tcache: opts.cache ?? {\n\t\t\tdocument: memoryDocumentCache(),\n\t\t\timage: memoryImageCache(),\n\t\t\tttlMs: opts.ttlMs,\n\t\t},\n\t\trenderer: opts.renderer,\n\t};\n}\n","import { noopDocumentCache, noopImageCache } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport { nodePreset } from \"./preset-node\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n\tBaseContentItem,\n\tCacheConfig,\n\tCachedItemList,\n\tCMSHooks,\n\tCollectionClient,\n\tCollectionSemantics,\n\tCreateCMSOptions,\n\tDataSource,\n\tDataSourceMap,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tInferDataSourceItem,\n\tInvalidateScope,\n\tLogger,\n\tRendererFn,\n} from \"./types/index\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<D>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<D extends DataSourceMap> = {\n\t[K in keyof D]: CollectionClient<InferDataSourceItem<D[K]>>;\n} & CMSGlobalOps<D>;\n\n/** `CMSClient` のグローバル名前空間。`$` プレフィックス。 */\nexport interface CMSGlobalOps<D extends DataSourceMap> {\n\t/** 登録されているコレクション名の一覧。 */\n\treadonly $collections: readonly (keyof D & string)[];\n\t/** 全コレクションまたは特定コレクションのキャッシュを無効化する。 */\n\t$revalidate(scope?: InvalidateScope): Promise<void>;\n\t/** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n\t$handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n\t/** ハッシュキーでキャッシュ画像を取得する。 */\n\t$getCachedImage(hash: string): ReturnType<ImageCacheAdapter[\"get\"]>;\n}\n\nfunction resolveDocumentCache(\n\tcache: CacheConfig | undefined,\n\t// biome-ignore lint/suspicious/noExplicitAny: 横断的に利用\n): DocumentCacheAdapter<any> {\n\tif (!cache || cache === \"disabled\" || !cache.document) {\n\t\treturn noopDocumentCache();\n\t}\n\treturn cache.document;\n}\n\nfunction resolveImageCache(cache: CacheConfig | undefined): ImageCacheAdapter {\n\tif (!cache || cache === \"disabled\" || !cache.image) {\n\t\treturn noopImageCache();\n\t}\n\treturn cache.image;\n}\n\nfunction resolveTtl(cache: CacheConfig | undefined): number | undefined {\n\tif (!cache || cache === \"disabled\") return undefined;\n\treturn cache.ttlMs;\n}\n\nfunction hasImageCacheConfigured(cache: CacheConfig | undefined): boolean {\n\tif (!cache || cache === \"disabled\") return false;\n\treturn !!cache.image;\n}\n\n/**\n * `{collection}:{slug}` キー空間で動作するコレクション別キャッシュビューを生成する。\n * 単一の `DocumentCacheAdapter` に複数コレクションを同居させるためのアダプタ。\n */\nfunction scopeDocumentCache<T extends BaseContentItem>(\n\t// biome-ignore lint/suspicious/noExplicitAny: 共有ストレージのため\n\tbase: DocumentCacheAdapter<any>,\n\tcollection: string,\n): DocumentCacheAdapter<T> {\n\tconst itemKey = (slug: string): string => `${collection}:${slug}`;\n\t// リストはコレクション別にクロージャ変数で管理する。\n\t// base.getList/setList はコレクション名前空間を持たないため、\n\t// 複数コレクションが同じ base を共有すると上書きし合うバグが起きる。\n\t// 初回アクセスのみ base から読み込むことで pre-populate されたキャッシュを活かしつつ、\n\t// 以降の読み書きはコレクション固有のスロットに限定する。\n\tlet listSlot: CachedItemList<T> | null = null;\n\tlet listInitialized = false;\n\n\treturn {\n\t\tname: `${base.name}@${collection}`,\n\t\tgetList: async () => {\n\t\t\tif (!listInitialized) {\n\t\t\t\tlistInitialized = true;\n\t\t\t\tlistSlot = (await base.getList()) as CachedItemList<T> | null;\n\t\t\t}\n\t\t\treturn listSlot;\n\t\t},\n\t\tsetList: (data) => {\n\t\t\tlistSlot = data as CachedItemList<T>;\n\t\t\tlistInitialized = true;\n\t\t\treturn Promise.resolve();\n\t\t},\n\t\tgetItem: (slug) => base.getItem(itemKey(slug)),\n\t\tsetItem: (slug, data) => base.setItem(itemKey(slug), data),\n\t\tasync invalidate(scope) {\n\t\t\tlistSlot = null;\n\t\t\tlistInitialized = true; // 無効化後は base を再読みしない\n\t\t\tif (!base.invalidate) return;\n\t\t\tif (scope === \"all\") {\n\t\t\t\treturn base.invalidate({ collection });\n\t\t\t}\n\t\t\tif (\"slug\" in scope) {\n\t\t\t\t// アイテムは `{collection}:{slug}` で保存されるため正しいキーに変換する\n\t\t\t\treturn base.invalidate({\n\t\t\t\t\tcollection: scope.collection,\n\t\t\t\t\tslug: itemKey(scope.slug),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn base.invalidate(scope);\n\t\t},\n\t};\n}\n\n/**\n * `preset` オプションを解決して `cache` / `renderer` のデフォルトを補完する内部関数。\n * 明示的な `cache` / `renderer` がある場合はそちらが優先される。\n * `preset` 未指定時は opts をそのまま返す。\n */\nfunction resolvePreset<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CreateCMSOptions<D> {\n\tif (opts.preset === \"disabled\") {\n\t\treturn { ...opts, cache: undefined };\n\t}\n\tif (opts.preset === \"node\") {\n\t\tconst presetResult = nodePreset({ ttlMs: opts.ttlMs });\n\t\treturn {\n\t\t\t...opts,\n\t\t\tcache: opts.cache ?? presetResult.cache,\n\t\t\trenderer: opts.renderer ?? presetResult.renderer,\n\t\t};\n\t}\n\treturn opts;\n}\n\n/**\n * 複数の DataSource を束ねた CMS クライアントを生成する。\n *\n * @example\n * // Node.js(preset を使った簡潔な記法)\n * const cms = createCMS({ dataSources: cmsDataSources, preset: \"node\", ttlMs: 5 * 60_000 });\n *\n * @example\n * // 従来の spread パターン(引き続き動作する)\n * const cms = createCMS({ ...nodePreset({ ttlMs: 5 * 60_000 }), dataSources: cmsDataSources });\n *\n * @example\n * // キャッシュを細かく指定する場合\n * const cms = createCMS({\n * dataSources,\n * cache: { document, image, ttlMs: 60_000 },\n * });\n */\nexport function createCMS<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CMSClient<D> {\n\tif (!opts.dataSources || Object.keys(opts.dataSources).length === 0) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"core/config_invalid\",\n\t\t\tmessage:\n\t\t\t\t\"createCMS: dataSources に少なくとも1つのコレクションを指定してください。\",\n\t\t\tcontext: { operation: \"createCMS\" },\n\t\t});\n\t}\n\n\t// collections が指定されたコレクションは slug が必須。\n\tfor (const [name, col] of Object.entries(opts.collections ?? {})) {\n\t\tconst c = col as CollectionSemantics | undefined;\n\t\tif (c && !c.slug) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"core/config_invalid\",\n\t\t\t\tmessage: `createCMS: コレクション \"${name}\" の collections.slug は必須です。slug として使うフィールド名を指定してください。`,\n\t\t\t\tcontext: { operation: \"createCMS\", collection: name },\n\t\t\t});\n\t\t}\n\t}\n\n\tconst resolved = resolvePreset(opts);\n\n\tconst baseDocCache = resolveDocumentCache(resolved.cache);\n\tconst imgCache = resolveImageCache(resolved.cache);\n\tconst hasImageCache = hasImageCacheConfigured(resolved.cache);\n\tconst ttlMs = resolveTtl(resolved.cache);\n\tconst imageProxyBase =\n\t\topts.content?.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n\tconst contentConfig = opts.content;\n\tconst rendererFn: RendererFn | undefined = resolved.renderer;\n\tconst waitUntil = opts.waitUntil;\n\tconst logger: Logger | undefined = mergeLoggers(\n\t\topts.plugins ?? [],\n\t\topts.logger,\n\t);\n\tconst hooks: CMSHooks<BaseContentItem> = mergeHooks(\n\t\topts.plugins ?? [],\n\t\topts.hooks,\n\t\tlogger,\n\t);\n\tconst maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n\tconst retryConfig: RetryConfig = {\n\t\t...DEFAULT_RETRY_CONFIG,\n\t\t...(opts.rateLimiter ?? {}),\n\t};\n\n\tconst collectionNames = Object.keys(opts.dataSources) as (keyof D & string)[];\n\n\t// biome-ignore lint/suspicious/noExplicitAny: 各 T を保持\n\tconst collections: Record<string, CollectionClient<any>> = {};\n\t// biome-ignore lint/suspicious/noExplicitAny: 横断的に利用\n\tconst scopedCaches: DocumentCacheAdapter<any>[] = [];\n\tfor (const name of collectionNames) {\n\t\tconst source = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\tconst scopedCache = scopeDocumentCache<BaseContentItem>(baseDocCache, name);\n\t\tscopedCaches.push(scopedCache);\n\t\tconst renderCtx: RenderContext<BaseContentItem> = {\n\t\t\tsource,\n\t\t\trendererFn,\n\t\t\timgCache,\n\t\t\thasImageCache,\n\t\t\timageProxyBase,\n\t\t\tcontentConfig,\n\t\t\thooks,\n\t\t\tlogger,\n\t\t};\n\t\tconst col = opts.collections?.[name] as CollectionSemantics | undefined;\n\t\tconst ctx: CollectionContext<BaseContentItem> = {\n\t\t\tcollection: name,\n\t\t\tsource,\n\t\t\tdocCache: scopedCache,\n\t\t\trender: renderCtx,\n\t\t\thooks,\n\t\t\tlogger,\n\t\t\tttlMs,\n\t\t\t// 公開条件は CollectionSemantics(createCMS の collections オプション)が権威\n\t\t\tpublishedStatuses: col?.publishedStatuses\n\t\t\t\t? [...col.publishedStatuses]\n\t\t\t\t: [],\n\t\t\taccessibleStatuses: col?.accessibleStatuses\n\t\t\t\t? [...col.accessibleStatuses]\n\t\t\t\t: [],\n\t\t\tretryConfig,\n\t\t\tmaxConcurrent,\n\t\t\twaitUntil,\n\t\t\tslugField: col?.slug,\n\t\t};\n\t\tcollections[name] = new CollectionClientImpl(ctx);\n\t}\n\n\tconst globalOps: CMSGlobalOps<D> = {\n\t\t$collections: collectionNames,\n\t\tasync $revalidate(scope?: InvalidateScope): Promise<void> {\n\t\t\tlogger?.debug?.(\"グローバルキャッシュを無効化\", {\n\t\t\t\toperation: \"$revalidate\",\n\t\t\t\tcacheAdapter: baseDocCache.name,\n\t\t\t});\n\t\t\t// baseDocCache を直接呼ばず各スコープキャッシュ経由で呼ぶ。\n\t\t\t// 直接呼ぶと scopeDocumentCache の listSlot がクリアされず stale になる。\n\t\t\tfor (const cache of scopedCaches) {\n\t\t\t\tif (!cache.invalidate) continue;\n\t\t\t\tawait cache.invalidate(scope ?? \"all\");\n\t\t\t}\n\t\t},\n\t\t$handler(handlerOpts?: HandlerOptions) {\n\t\t\treturn createHandler(\n\t\t\t\t{\n\t\t\t\t\timageCache: imgCache,\n\t\t\t\t\tparseWebhook: async (req, webhookSecret) => {\n\t\t\t\t\t\t// 各 DataSource の parseWebhook を順に試す\n\t\t\t\t\t\tfor (const name of collectionNames) {\n\t\t\t\t\t\t\tconst ds = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\t\t\t\t\t\tif (ds.parseWebhook) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst scope = await ds.parseWebhook(req.clone(), {\n\t\t\t\t\t\t\t\t\t\tsecret: webhookSecret,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\treturn scope;\n\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\tlogger?.warn?.(\"parseWebhook 失敗\", {\n\t\t\t\t\t\t\t\t\t\tcollection: name,\n\t\t\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// フォールバック: { slug } だけの汎用 JSON body\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst body = (await req.json()) as {\n\t\t\t\t\t\t\t\tslug?: string;\n\t\t\t\t\t\t\t\tcollection?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif (body.slug && body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection, slug: body.slug };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t},\n\t\t\t\t\trevalidate: (scope) => globalOps.$revalidate(scope),\n\t\t\t\t},\n\t\t\t\thandlerOpts,\n\t\t\t);\n\t\t},\n\t\t$getCachedImage(hash) {\n\t\t\treturn imgCache.get(hash);\n\t\t},\n\t};\n\n\treturn Object.assign(\n\t\tObject.create(null) as object,\n\t\tcollections,\n\t\tglobalOps,\n\t) as CMSClient<D>;\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\tname: string;\n\thooks?: CMSHooks<T>;\n\tlogger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n\tplugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n\treturn plugin;\n}\n"],"mappings":";;;;;;AACA,eAAsB,UAAU,OAAgC;CAC/D,MAAM,OAAO,IAAI,aAAa,CAAC,OAAO,MAAM;CAC5C,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK;AACxD,QAAO,MAAM,KAAK,IAAI,WAAW,KAAK,CAAC,CACrC,KAAK,MAAM,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAC3C,KAAK,GAAG;;;;;;AAOX,SAAgB,QAAQ,UAAkB,OAAyB;AAClE,KAAI,UAAU,KAAA,EAAW,QAAO;AAChC,QAAO,KAAK,KAAK,GAAG,WAAW;;;;;ACVhC,SAAS,iBACR,KACA,qBACS;AACT,KAAI,qBAAqB,WAAW,SAAS,CAC5C,QAAO,oBAAoB,MAAM,IAAI,CAAC,GAAG,MAAM;AAEhD,KAAI,IAAI,SAAS,OAAO,CAAE,QAAO;AACjC,KAAI,IAAI,SAAS,OAAO,CAAE,QAAO;AACjC,KAAI,IAAI,SAAS,QAAQ,CAAE,QAAO;AAClC,QAAO;;;;;;AAOR,eAAe,mBACd,OACA,WACA,gBACA,QACkB;CAClB,MAAM,OAAO,MAAM,UAAU,UAAU;CACvC,MAAM,WAAW,GAAG,eAAe,GAAG;AAGtC,KAAI,MADmB,MAAM,IAAI,KAAK,EACxB;AACb,UAAQ,QAAQ,cAAc;GAC7B,WAAW;GACX,cAAc,MAAM;GACpB,WAAW;GACX,CAAC;AACF,SAAO;;AAGR,SAAQ,QAAQ,2BAA2B;EAC1C,WAAW;EACX,cAAc,MAAM;EACpB,WAAW;EACX,CAAC;AAEF,KAAI;EACH,MAAM,WAAW,MAAM,MAAM,WAAW,EACvC,QAAQ,YAAY,QAAQ,IAAO,EACnC,CAAC;AACF,MAAI,CAAC,SAAS,GACb,OAAM,IAAI,SAAS;GAClB,MAAM;GACN,SAAS,sCAAsC,SAAS;GACxD,SAAS;IACR,WAAW;IACX;IACA,YAAY,SAAS;IACrB;GACD,CAAC;EAGH,MAAM,OAAO,MAAM,SAAS,aAAa;EACzC,MAAM,cAAc,iBACnB,WACA,SAAS,QAAQ,IAAI,eAAe,CACpC;AACD,QAAM,MAAM,IAAI,MAAM,MAAM,YAAY;AACxC,UAAQ,QAAQ,eAAe;GAC9B,WAAW;GACX,cAAc,MAAM;GACpB,WAAW;GACX,CAAC;UACM,KAAK;AACb,MAAI,WAAW,IAAI,CAAE,OAAM;AAC3B,QAAM,IAAI,SAAS;GAClB,MAAM;GACN,SAAS;GACT,OAAO;GACP,SAAS;IAAE,WAAW;IAAsB;IAAW;GACvD,CAAC;;AAGH,QAAO;;;AAIR,SAAgB,kBACf,OACA,gBACA,QACyC;AACzC,SAAQ,cACP,mBAAmB,OAAO,WAAW,gBAAgB,OAAO;;;;;;;;AChE9D,eAAsB,gBACrB,MACA,KACyB;CACzB,MAAM,QAAQ,KAAK,KAAK;AACxB,KAAI,QAAQ,OAAO,kBAAkB;EACpC,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,CAAC;AACF,KAAI,MAAM,gBAAgB,KAAK,KAAK;CAEpC,IAAI;AACJ,KAAI;AACH,aAAW,MAAM,IAAI,OAAO,aAAa,KAAK;UACtC,KAAK;AACb,MAAI,WAAW,IAAI,CAAE,OAAM;AAC3B,QAAM,IAAI,SAAS;GAClB,MAAM;GACN,SAAS;GACT,OAAO;GACP,SAAS;IACR,WAAW;IACX,QAAQ,KAAK;IACb,MAAM,KAAK;IACX;GACD,CAAC;;CAGH,IAAI,SAAyB,EAAE;AAC/B,KAAI;AACH,WAAS,MAAM,IAAI,OAAO,WAAW,KAAK;UAClC,KAAK;AACb,MAAI,QAAQ,OAAO,kCAAkC;GACpD,MAAM,KAAK;GACX,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACvD,CAAC;AACF,WAAS,EAAE;;CAGZ,MAAM,aAAa,IAAI,gBACpB,kBAAkB,IAAI,UAAU,IAAI,gBAAgB,IAAI,OAAO,GAC/D,KAAA;CAEH,IAAI;CACJ,MAAM,aAAa,IAAI,cAAe,MAAM,qBAAqB;AACjE,KAAI;AACH,SAAO,MAAM,WAAW,UAAU;GACjC,gBAAgB,IAAI;GACpB;GACA,eAAe,IAAI,eAAe;GAClC,eAAe,IAAI,eAAe;GAClC,CAAC;UACM,KAAK;AACb,MAAI,WAAW,IAAI,CAAE,OAAM;AAC3B,QAAM,IAAI,SAAS;GAClB,MAAM;GACN,SAAS;GACT,OAAO;GACP,SAAS;IACR,WAAW;IACX,QAAQ,KAAK;IACb,MAAM,KAAK;IACX;GACD,CAAC;;AAGH,KAAI,IAAI,MAAM,YACb,QAAO,MAAM,IAAI,MAAM,YAAY,MAAM,KAAK;CAG/C,IAAI,SAAwB;EAC3B;EACA;EACA;EACA;EACA,iBAAiB,IAAI,OAAO,gBAAgB,KAAK;EACjD,UAAU,KAAK,KAAK;EACpB;AAED,KAAI,IAAI,MAAM,YACb,UAAU,MAAM,IAAI,MAAM,YAAY,OAAO;CAG9C,MAAM,aAAa,KAAK,KAAK,GAAG;AAChC,KAAI,QAAQ,OAAO,kBAAkB;EACpC,MAAM,KAAK;EACX;EACA,CAAC;AACF,KAAI,MAAM,cAAc,KAAK,MAAM,WAAW;AAE9C,QAAO;;;;;;;AAQR,eAAsB,sBAA2C;AAChE,KAAI;AAEH,UAAO,MADW,OAAO,kCACd;UACH,KAAK;AACb,QAAM,IAAI,SAAS;GAClB,MAAM;GACN,SACC;GAED,OAAO;GACP,SAAS,EAAE,WAAW,uBAAuB;GAC7C,CAAC;;;;;ACnIJ,MAAa,uBAAoC;CAChD,SAAS;EAAC;EAAK;EAAK;EAAI;CACxB,YAAY;CACZ,aAAa;CACb,QAAQ;CACR;;AAGD,eAAsB,UACrB,IACA,QACa;CACb,IAAI;AACJ,MAAK,IAAI,UAAU,GAAG,WAAW,OAAO,YAAY,UACnD,KAAI;AACH,SAAO,MAAM,IAAI;UACT,KAAK;EACb,MAAM,SAAU,IAA4B;AAC5C,MAAI,WAAW,KAAA,KAAa,CAAC,OAAO,QAAQ,SAAS,OAAO,CAC3D,OAAM;AAEP,cAAY;AACZ,MAAI,UAAU,OAAO,YAAY;AAChC,UAAO,UAAU,UAAU,GAAG,OAAO;GACrC,MAAM,eACL,OAAO,WAAW,QAAQ,KAAM,KAAK,QAAQ,GAAG,KAAM;GACvD,MAAM,QAAQ,OAAO,cAAc,KAAK,UAAU;AAClD,SAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;;;AAI7D,OAAM;;;;;;;;ACfP,SAAgB,cAAc,YAAoB,MAAuB;AACxE,QAAO,OAAO,GAAG,WAAW,GAAG,SAAS;;;AA2BzC,IAAa,uBAAb,MAEA;CACC,YAAY,KAA4C;AAA3B,OAAA,MAAA;;CAI7B,MAAM,QAAQ,MAAkD;EAC/D,MAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ,KAAK;AACpD,MAAI,QAAQ;AACX,OACC,KAAK,IAAI,UAAU,KAAA,KACnB,QAAQ,OAAO,UAAU,KAAK,IAAI,MAAM,EACvC;AAED,SAAK,IAAI,QAAQ,QAAQ,uBAAuB;KAC/C,WAAW;KACX;KACA,YAAY,KAAK,IAAI;KACrB,cAAc,KAAK,IAAI,SAAS;KAChC,CAAC;AACF,SAAK,IAAI,MAAM,cAAc,KAAK;IAClC,MAAM,OAAO,MAAM,KAAK,QAAQ,KAAK;AACrC,QAAI,CAAC,KAAM,QAAO;IAClB,MAAM,QAAQ,MAAM,gBAAgB,MAAM,KAAK,IAAI,OAAO;AAC1D,UAAM,KAAK,IAAI,SAAS,QAAQ,MAAM,MAAM;AAC5C,WAAO,KAAK,cAAc,MAAM,MAAM,MAAM;;GAG7C,MAAM,KAAK,KAAK,qBAAqB,MAAM,OAAO;AAClD,OAAI,KAAK,IAAI,UACZ,MAAK,IAAI,UAAU,GAAG;AAEvB,QAAK,IAAI,QAAQ,QAAQ,YAAY;IACpC,WAAW;IACX;IACA,YAAY,KAAK,IAAI;IACrB,cAAc,KAAK,IAAI,SAAS;IAChC,UAAU,OAAO;IACjB,CAAC;AACF,QAAK,IAAI,MAAM,aAAa,MAAM,OAAO;AACzC,UAAO,KAAK,cAAc,OAAO,MAAM,OAAO;;AAI/C,OAAK,IAAI,QAAQ,QAAQ,gBAAgB;GACxC,WAAW;GACX;GACA,YAAY,KAAK,IAAI;GACrB,cAAc,KAAK,IAAI,SAAS;GAChC,CAAC;AACF,OAAK,IAAI,MAAM,cAAc,KAAK;EAClC,MAAM,OAAO,MAAM,KAAK,QAAQ,KAAK;AACrC,MAAI,CAAC,MAAM;AACV,QAAK,IAAI,QAAQ,QAAQ,gBAAgB;IACxC,WAAW;IACX;IACA,YAAY,KAAK,IAAI;IACrB,CAAC;AACF,UAAO;;EAGR,MAAM,QAAQ,MAAM,gBAAgB,MAAM,KAAK,IAAI,OAAO;EAC1D,MAAM,OAAO,KAAK,IAAI,SAAS,QAAQ,MAAM,MAAM;AACnD,MAAI,KAAK,IAAI,UACZ,MAAK,IAAI,UAAU,KAAK;MAExB,OAAM;AAGP,SAAO,KAAK,cAAc,MAAM,MAAM,MAAM;;CAG7C,MAAM,QAAQ,MAAwC;AAErD,SAAO,oBAAoB,MADP,KAAK,WAAW,EACF,KAAK;;CAKxC,MAAM,kBAA+C;AAEpD,UAAO,MADa,KAAK,WAAW,EACvB,KAAK,UAAU,EAAE,MAAM,KAAK,MAAM,EAAE;;CAGlD,MAAM,iBAAoC;AAEzC,UAAO,MADa,KAAK,WAAW,EACvB,KAAK,SAAS,KAAK,KAAK;;CAGtC,MAAM,SACL,MACA,MAC8C;EAC9C,MAAM,QAAQ,oBAAoB,MAAM,KAAK,WAAW,EAAE,EACzD,MAAM,MAAM,MACZ,CAAC;EACF,MAAM,QAAQ,MAAM,WAAW,OAAO,GAAG,SAAS,KAAK;AACvD,MAAI,UAAU,GAAI,QAAO;GAAE,MAAM;GAAM,MAAM;GAAM;AACnD,SAAO;GACN,MAAM,QAAQ,IAAK,MAAM,QAAQ,MAAM,OAAQ;GAC/C,MAAM,QAAQ,MAAM,SAAS,IAAK,MAAM,QAAQ,MAAM,OAAQ;GAC9D;;CAKF,MAAM,WAAW,OAAiD;AACjE,OAAK,IAAI,QAAQ,QAAQ,aAAa;GACrC,WAAW;GACX,YAAY,KAAK,IAAI;GACrB,cAAc,KAAK,IAAI,SAAS;GAChC,MAAM,OAAO,UAAU,WAAW,MAAM,OAAO,KAAA;GAC/C,CAAC;AACF,MAAI,CAAC,KAAK,IAAI,SAAS,WAAY;AACnC,MAAI,UAAU,KAAA,KAAa,UAAU,MACpC,OAAM,KAAK,IAAI,SAAS,WAAW,EAAE,YAAY,KAAK,IAAI,YAAY,CAAC;MAEvE,OAAM,KAAK,IAAI,SAAS,WAAW;GAClC,YAAY,KAAK,IAAI;GACrB,MAAM,MAAM;GACZ,CAAC;;CAIJ,MAAM,SAAS,MAG6B;EAC3C,MAAM,QAAQ,MAAM,KAAK,cAAc;EACvC,MAAM,cAAc,MAAM,eAAe,KAAK,IAAI;EAClD,IAAI,KAAK;EACT,IAAI,SAAS;AAEb,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,aAAa;GACnD,MAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,YAAY;AAC7C,SAAM,QAAQ,IACb,MAAM,IAAI,OAAO,SAAS;AACzB,QAAI;KACH,MAAM,WAAW,MAAM,gBAAgB,MAAM,KAAK,IAAI,OAAO;AAC7D,WAAM,KAAK,IAAI,SAAS,QAAQ,KAAK,MAAM,SAAS;AACpD;aACQ,KAAK;AACb;AACA,UAAK,IAAI,QAAQ,OAChB,8BACA;MACC,MAAM,KAAK;MACX,QAAQ,KAAK;MACb,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;MACvD,CACD;;KAED,CACF;AACD,SAAM,aAAa,KAAK,IAAI,IAAI,aAAa,MAAM,OAAO,EAAE,MAAM,OAAO;;AAG1E,QAAM,KAAK,IAAI,SAAS,QAAQ;GAAE;GAAO,UAAU,KAAK,KAAK;GAAE,CAAC;AAChE,SAAO;GAAE;GAAI;GAAQ;;CAKtB,cAAsB,MAAS,QAA2C;EACzE,MAAM,MAAM,KAAK;EACjB,IAAI;EACJ,IAAI,YAAgC,OAAO;EAC3C,IAAI;EAEJ,MAAM,UAAyB;GAC9B,IAAI,SAAyB;AAC5B,QAAI,CAAC,YAEJ,eAAc,CACb;KAAE,MAAM;KAAO,MAAM,OAAO;KAAM,CAClC;AAEF,WAAO;;GAER,MAAM,OAAwB;AAC7B,QAAI,cAAc,KAAA,EAAW,QAAO;AACpC,gBAAY,OAAO;AACnB,WAAO;;GAER,MAAM,WAA4B;AACjC,QAAI,kBAAkB,KAAA,EAAW,QAAO;AACxC,oBAAgB,MAAM,IAAI,OAAO,aAAa,KAAK;AACnD,WAAO;;GAER;AAED,MAAI,OAAO,OAAQ,eAAc,OAAO;AAExC,SAAO,OAAO,OAAO,OAAO,OAAO,KAAK,EAAY,MAAM,EACzD,SACA,CAAC;;CAGH,MAAc,YAA0B;EACvC,MAAM,SAAS,MAAM,KAAK,IAAI,SAAS,SAAS;AAChD,MAAI,QAAQ;AACX,OACC,KAAK,IAAI,UAAU,KAAA,KACnB,QAAQ,OAAO,UAAU,KAAK,IAAI,MAAM,EACvC;AAED,SAAK,IAAI,QAAQ,QAAQ,0BAA0B;KAClD,WAAW;KACX,YAAY,KAAK,IAAI;KACrB,cAAc,KAAK,IAAI,SAAS;KAChC,CAAC;AACF,SAAK,IAAI,MAAM,mBAAmB;IAClC,MAAM,QAAQ,MAAM,KAAK,cAAc;AACvC,UAAM,KAAK,IAAI,SAAS,QAAQ;KAAE;KAAO,UAAU,KAAK,KAAK;KAAE,CAAC;AAChE,WAAO;;GAGR,MAAM,KAAK,KAAK,qBAAqB,OAAO;AAC5C,OAAI,KAAK,IAAI,UACZ,MAAK,IAAI,UAAU,GAAG;AAEvB,QAAK,IAAI,QAAQ,QAAQ,eAAe;IACvC,WAAW;IACX,YAAY,KAAK,IAAI;IACrB,cAAc,KAAK,IAAI,SAAS;IAChC,CAAC;AACF,QAAK,IAAI,MAAM,iBAAiB,OAAO,OAAO,OAAO,SAAS;AAC9D,UAAO,OAAO;;AAIf,OAAK,IAAI,QAAQ,QAAQ,mBAAmB;GAC3C,WAAW;GACX,YAAY,KAAK,IAAI;GACrB,cAAc,KAAK,IAAI,SAAS;GAChC,CAAC;AACF,OAAK,IAAI,MAAM,mBAAmB;EAClC,MAAM,QAAQ,MAAM,KAAK,cAAc;EACvC,MAAM,WAAW,KAAK,KAAK;EAC3B,MAAM,OAAO,KAAK,IAAI,SAAS,QAAQ;GAAE;GAAO;GAAU,CAAC;AAC3D,MAAI,KAAK,IAAI,UACZ,MAAK,IAAI,UAAU,KAAK;MAExB,OAAM;AAEP,SAAO;;CAGR,MAAc,qBACb,MACA,QACgB;AAChB,MAAI;GACH,MAAM,OAAO,MAAM,KAAK,QAAQ,KAAK;AACrC,OAAI,CAAC,KAAM;AACX,OAAI,KAAK,IAAI,OAAO,gBAAgB,KAAK,KAAK,OAAO,iBAAiB;IAErE,MAAM,QAAQ,MAAM,gBAAgB,MAAM,KAAK,IAAI,OAAO;AAC1D,UAAM,KAAK,IAAI,SAAS,QAAQ,MAAM,MAAM;AAC5C,SAAK,IAAI,QAAQ,QAAQ,yBAAyB;KACjD,WAAW;KACX;KACA,YAAY,KAAK,IAAI;KACrB,iBAAiB,OAAO;KACxB,CAAC;AACF,SAAK,IAAI,MAAM,gBAAgB,MAAM,MAAM;cACjC,KAAK,IAAI,UAAU,KAAA,GAAW;AAExC,UAAM,KAAK,IAAI,SAAS,QAAQ,MAAM;KACrC,GAAG;KACH,UAAU,KAAK,KAAK;KACpB,CAAC;AACF,SAAK,IAAI,QAAQ,QAAQ,uBAAuB;KAC/C,WAAW;KACX;KACA,YAAY,KAAK,IAAI;KACrB,CAAC;;WAEK,KAAK;AACb,QAAK,IAAI,QAAQ,OAChB,+BACA;IACC;IACA,YAAY,KAAK,IAAI;IACrB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,CACD;;;CAIH,MAAc,qBAAqB,QAA0C;AAC5E,MAAI;GACH,MAAM,QAAQ,MAAM,KAAK,cAAc;AACvC,OACC,KAAK,IAAI,OAAO,eAAe,MAAM,KACrC,KAAK,IAAI,OAAO,eAAe,OAAO,MAAM,EAC3C;AAED,UAAM,KAAK,IAAI,SAAS,QAAQ;KAAE;KAAO,UAAU,KAAK,KAAK;KAAE,CAAC;AAChE,SAAK,IAAI,QAAQ,QAChB,4BACA;KACC,WAAW;KACX,YAAY,KAAK,IAAI;KACrB,CACD;AACD,SAAK,IAAI,MAAM,oBAAoB,MAAM;cAC/B,KAAK,IAAI,UAAU,KAAA,GAAW;AAExC,UAAM,KAAK,IAAI,SAAS,QAAQ;KAC/B,GAAG;KACH,UAAU,KAAK,KAAK;KACpB,CAAC;AACF,SAAK,IAAI,QAAQ,QAAQ,0BAA0B;KAClD,WAAW;KACX,YAAY,KAAK,IAAI;KACrB,CAAC;;WAEK,KAAK;AACb,QAAK,IAAI,QAAQ,OAChB,8BACA;IACC,YAAY,KAAK,IAAI;IACrB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,CACD;;;CAIH,eAAqC;AACpC,SAAO,gBAEL,KAAK,IAAI,OAAO,KAAK,EACpB,mBACC,KAAK,IAAI,kBAAkB,SAAS,IACjC,KAAK,IAAI,oBACT,KAAA,GACJ,CAAC,EACH;GACC,GAAG,KAAK,IAAI;GACZ,UAAU,SAAS,WAAW;AAC7B,SAAK,IAAI,QAAQ,OAAO,mBAAmB;KAAE;KAAS;KAAQ,CAAC;;GAEhE,CACD;;CAGF,MAAc,QAAQ,MAAiC;EACtD,MAAM,YAAY;GACjB,GAAG,KAAK,IAAI;GACZ,UAAU,SAAiB,WAAmB;AAC7C,SAAK,IAAI,QAAQ,OAAO,mBAAmB;KAC1C;KACA;KACA;KACA,CAAC;;GAEH;EAID,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,iBAAiB,YACpB,KAAK,IAAI,OAAO,aAAa,YAAY,SACzC,KAAA;EAEH,IAAI;EACJ,MAAM,aAAa,KAAK,IAAI,OAAO,YAAY,KAAK,KAAK,IAAI,OAAO;AACpE,MAAI,kBAAkB,WACrB,QAAO,MAAM,gBAAgB,WAAW,gBAAgB,KAAK,EAAE,UAAU;MAIzE,SAAO,MADW,gBAAgB,KAAK,IAAI,OAAO,MAAM,EAAE,UAAU,EACzD,MAAM,MAAM,EAAE,SAAS,KAAK,IAAI;AAG5C,MAAI,CAAC,KAAM,QAAO;AAClB,MACC,KAAK,IAAI,mBAAmB,SAAS,MACpC,CAAC,KAAK,UAAU,CAAC,KAAK,IAAI,mBAAmB,SAAS,KAAK,OAAO,EAEnE,QAAO;AAER,SAAO;;;AAIT,SAAS,oBACR,OACA,MACM;AACN,KAAI,CAAC,KAAM,QAAO;CAClB,IAAI,SAAS;AAEb,KAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAAG;EAC9C,MAAM,QAAQ,IAAI,IAAI,KAAK,SAAS;AACpC,WAAS,OAAO,QACd,OAAO,GAAG,WAAW,KAAA,KAAa,MAAM,IAAI,GAAG,OAAO,CACvD;;AAGF,KAAI,KAAK,KAAK;EACb,MAAM,MAAM,KAAK;AACjB,WAAS,OAAO,QAAQ,OAAO;GAC9B,MAAM,OAAQ,GAAqC;AACnD,UAAO,MAAM,QAAQ,KAAK,IAAI,KAAK,SAAS,IAAI;IAC/C;;AAGH,KAAI,KAAK,OAAO;EACf,MAAM,QAAQ,KAAK;AACnB,WAAS,OAAO,QAAQ,OACvB,OAAO,QAAQ,MAAM,CAAC,OACpB,CAAC,KAAK,WAAY,GAA+B,SAAS,MAC3D,CACD;;AAGF,KAAI,KAAK,KACR,UAAS,CAAC,GAAG,OAAO,CAAC,KAAK,eAAe,KAAK,KAAK,CAAC;CAGrD,MAAM,OAAO,KAAK,QAAQ;CAC1B,MAAM,QAAQ,KAAK;AACnB,KAAI,OAAO,KAAK,UAAU,KAAA,EACzB,UAAS,OAAO,MAAM,MAAM,UAAU,KAAA,IAAY,OAAO,QAAQ,KAAA,EAAU;AAG5E,QAAO;;AAGR,SAAS,eACR,MACyB;CACzB,MAAM,KAAK,KAAK;CAChB,MAAM,MAAM,KAAK,cAAc,QAAQ,IAAI;AAC3C,SAAQ,GAAG,MAAM;EAChB,MAAM,KAAM,EAA8B;EAC1C,MAAM,KAAM,EAA8B;AAC1C,MAAI,OAAO,GAAI,QAAO;AACtB,MAAI,OAAO,KAAA,EAAW,QAAO;AAC7B,MAAI,OAAO,KAAA,EAAW,QAAO;AAE7B,SAAQ,KAAc,KAAa,MAAM,CAAC;;;;;ACtd5C,MAAM,eAAe;CACpB,UAAU;CACV,YAAY;CACZ,gBAAgB;CAChB;;;;;;;;;AAUD,SAAgB,cACf,SACA,OAAuB,EAAE,EACa;CACtC,MAAM,WAAW,kBAAkB,KAAK,YAAY,aAAa,SAAS;CAC1E,MAAM,aAAa,KAAK,cAAc,aAAa;CACnD,MAAM,iBAAiB,KAAK,kBAAkB,aAAa;AAE3D,QAAO,OAAO,QAAoC;EAEjD,MAAM,OAAO,IADG,IAAI,IAAI,IACR,CAAC;AAEjB,MAAI,CAAC,KAAK,WAAW,SAAS,CAC7B,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;EAElD,MAAM,MAAM,KAAK,MAAM,SAAS,OAAO,IAAI;AAG3C,MAAI,IAAI,WAAW,SAAS,IAAI,WAAW,GAAG,WAAW,GAAG,EAAE;GAC7D,MAAM,OAAO,IAAI,MAAM,WAAW,SAAS,EAAE;AAC7C,OAAI,CAAC,KAAM,QAAO,IAAI,SAAS,eAAe,EAAE,QAAQ,KAAK,CAAC;GAC9D,MAAM,SAAS,MAAM,QAAQ,WAAW,IAAI,KAAK;AACjD,OAAI,CAAC,OAAQ,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;GAC9D,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAI,OAAO,YAAa,SAAQ,IAAI,gBAAgB,OAAO,YAAY;AACvE,WAAQ,IAAI,iBAAiB,sCAAsC;AACnE,UAAO,IAAI,SAAS,OAAO,MAAM,EAAE,SAAS,CAAC;;AAI9C,MAAI,IAAI,WAAW,UAAU,QAAQ,gBAAgB;GACpD,MAAM,QAAQ,MAAM,QAAQ,aAAa,KAAK,KAAK,cAAc;AACjE,OAAI,CAAC,MACJ,QAAO,IAAI,SAAS,KAAK,UAAU;IAAE,IAAI;IAAO,QAAQ;IAAW,CAAC,EAAE;IACrE,QAAQ;IACR,SAAS,EAAE,gBAAgB,oBAAoB;IAC/C,CAAC;AAEH,SAAM,QAAQ,WAAW,MAAM;AAC/B,UAAO,IAAI,SAAS,KAAK,UAAU;IAAE,IAAI;IAAM;IAAO,CAAC,EAAE;IACxD,QAAQ;IACR,SAAS,EAAE,gBAAgB,oBAAoB;IAC/C,CAAC;;AAGH,SAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;;;AAInD,SAAS,kBAAkB,GAAmB;AAC7C,QAAO,EAAE,SAAS,IAAI,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG;;;;;;;;;;;;;;;;;AC5D3C,SAAgB,WACf,OAA0B,EAAE,EACmB;AAC/C,KAAI,KAAK,UAAU,WAClB,QAAO;EAAE,OAAO,KAAA;EAAW,UAAU,KAAK;EAAU;AAErD,QAAO;EACN,OAAO,KAAK,SAAS;GACpB,UAAU,qBAAqB;GAC/B,OAAO,kBAAkB;GACzB,OAAO,KAAK;GACZ;EACD,UAAU,KAAK;EACf;;;;ACjBF,MAAM,2BAA2B;AAmBjC,SAAS,qBACR,OAE4B;AAC5B,KAAI,CAAC,SAAS,UAAU,cAAc,CAAC,MAAM,SAC5C,QAAO,mBAAmB;AAE3B,QAAO,MAAM;;AAGd,SAAS,kBAAkB,OAAmD;AAC7E,KAAI,CAAC,SAAS,UAAU,cAAc,CAAC,MAAM,MAC5C,QAAO,gBAAgB;AAExB,QAAO,MAAM;;AAGd,SAAS,WAAW,OAAoD;AACvE,KAAI,CAAC,SAAS,UAAU,WAAY,QAAO,KAAA;AAC3C,QAAO,MAAM;;AAGd,SAAS,wBAAwB,OAAyC;AACzE,KAAI,CAAC,SAAS,UAAU,WAAY,QAAO;AAC3C,QAAO,CAAC,CAAC,MAAM;;;;;;AAOhB,SAAS,mBAER,MACA,YAC0B;CAC1B,MAAM,WAAW,SAAyB,GAAG,WAAW,GAAG;CAM3D,IAAI,WAAqC;CACzC,IAAI,kBAAkB;AAEtB,QAAO;EACN,MAAM,GAAG,KAAK,KAAK,GAAG;EACtB,SAAS,YAAY;AACpB,OAAI,CAAC,iBAAiB;AACrB,sBAAkB;AAClB,eAAY,MAAM,KAAK,SAAS;;AAEjC,UAAO;;EAER,UAAU,SAAS;AAClB,cAAW;AACX,qBAAkB;AAClB,UAAO,QAAQ,SAAS;;EAEzB,UAAU,SAAS,KAAK,QAAQ,QAAQ,KAAK,CAAC;EAC9C,UAAU,MAAM,SAAS,KAAK,QAAQ,QAAQ,KAAK,EAAE,KAAK;EAC1D,MAAM,WAAW,OAAO;AACvB,cAAW;AACX,qBAAkB;AAClB,OAAI,CAAC,KAAK,WAAY;AACtB,OAAI,UAAU,MACb,QAAO,KAAK,WAAW,EAAE,YAAY,CAAC;AAEvC,OAAI,UAAU,MAEb,QAAO,KAAK,WAAW;IACtB,YAAY,MAAM;IAClB,MAAM,QAAQ,MAAM,KAAK;IACzB,CAAC;AAEH,UAAO,KAAK,WAAW,MAAM;;EAE9B;;;;;;;AAQF,SAAS,cACR,MACsB;AACtB,KAAI,KAAK,WAAW,WACnB,QAAO;EAAE,GAAG;EAAM,OAAO,KAAA;EAAW;AAErC,KAAI,KAAK,WAAW,QAAQ;EAC3B,MAAM,eAAe,WAAW,EAAE,OAAO,KAAK,OAAO,CAAC;AACtD,SAAO;GACN,GAAG;GACH,OAAO,KAAK,SAAS,aAAa;GAClC,UAAU,KAAK,YAAY,aAAa;GACxC;;AAEF,QAAO;;;;;;;;;;;;;;;;;;;;AAqBR,SAAgB,UACf,MACe;AACf,KAAI,CAAC,KAAK,eAAe,OAAO,KAAK,KAAK,YAAY,CAAC,WAAW,EACjE,OAAM,IAAI,SAAS;EAClB,MAAM;EACN,SACC;EACD,SAAS,EAAE,WAAW,aAAa;EACnC,CAAC;AAIH,MAAK,MAAM,CAAC,MAAM,QAAQ,OAAO,QAAQ,KAAK,eAAe,EAAE,CAAC,EAAE;EACjE,MAAM,IAAI;AACV,MAAI,KAAK,CAAC,EAAE,KACX,OAAM,IAAI,SAAS;GAClB,MAAM;GACN,SAAS,sBAAsB,KAAK;GACpC,SAAS;IAAE,WAAW;IAAa,YAAY;IAAM;GACrD,CAAC;;CAIJ,MAAM,WAAW,cAAc,KAAK;CAEpC,MAAM,eAAe,qBAAqB,SAAS,MAAM;CACzD,MAAM,WAAW,kBAAkB,SAAS,MAAM;CAClD,MAAM,gBAAgB,wBAAwB,SAAS,MAAM;CAC7D,MAAM,QAAQ,WAAW,SAAS,MAAM;CACxC,MAAM,iBACL,KAAK,SAAS,kBAAkB;CACjC,MAAM,gBAAgB,KAAK;CAC3B,MAAM,aAAqC,SAAS;CACpD,MAAM,YAAY,KAAK;CACvB,MAAM,SAA6B,aAClC,KAAK,WAAW,EAAE,EAClB,KAAK,OACL;CACD,MAAM,QAAmC,WACxC,KAAK,WAAW,EAAE,EAClB,KAAK,OACL,OACA;CACD,MAAM,gBAAgB,KAAK,aAAa,iBAAiB;CACzD,MAAM,cAA2B;EAChC,GAAG;EACH,GAAI,KAAK,eAAe,EAAE;EAC1B;CAED,MAAM,kBAAkB,OAAO,KAAK,KAAK,YAAY;CAGrD,MAAM,cAAqD,EAAE;CAE7D,MAAM,eAA4C,EAAE;AACpD,MAAK,MAAM,QAAQ,iBAAiB;EACnC,MAAM,SAAS,KAAK,YAAY;EAChC,MAAM,cAAc,mBAAoC,cAAc,KAAK;AAC3E,eAAa,KAAK,YAAY;EAC9B,MAAM,YAA4C;GACjD;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACD,MAAM,MAAM,KAAK,cAAc;AAqB/B,cAAY,QAAQ,IAAI,qBAAqB;GAnB5C,YAAY;GACZ;GACA,UAAU;GACV,QAAQ;GACR;GACA;GACA;GAEA,mBAAmB,KAAK,oBACrB,CAAC,GAAG,IAAI,kBAAkB,GAC1B,EAAE;GACL,oBAAoB,KAAK,qBACtB,CAAC,GAAG,IAAI,mBAAmB,GAC3B,EAAE;GACL;GACA;GACA;GACA,WAAW,KAAK;GAE+B,CAAC;;CAGlD,MAAM,YAA6B;EAClC,cAAc;EACd,MAAM,YAAY,OAAwC;AACzD,WAAQ,QAAQ,kBAAkB;IACjC,WAAW;IACX,cAAc,aAAa;IAC3B,CAAC;AAGF,QAAK,MAAM,SAAS,cAAc;AACjC,QAAI,CAAC,MAAM,WAAY;AACvB,UAAM,MAAM,WAAW,SAAS,MAAM;;;EAGxC,SAAS,aAA8B;AACtC,UAAO,cACN;IACC,YAAY;IACZ,cAAc,OAAO,KAAK,kBAAkB;AAE3C,UAAK,MAAM,QAAQ,iBAAiB;MACnC,MAAM,KAAK,KAAK,YAAY;AAC5B,UAAI,GAAG,aACN,KAAI;AAIH,cAAO,MAHa,GAAG,aAAa,IAAI,OAAO,EAAE,EAChD,QAAQ,eACR,CAAC;eAEM,KAAK;AACb,eAAQ,OAAO,mBAAmB;QACjC,YAAY;QACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;QACvD,CAAC;;;AAKL,SAAI;MACH,MAAM,OAAQ,MAAM,IAAI,MAAM;AAI9B,UAAI,KAAK,QAAQ,KAAK,WACrB,QAAO;OAAE,YAAY,KAAK;OAAY,MAAM,KAAK;OAAM;AAExD,UAAI,KAAK,WACR,QAAO,EAAE,YAAY,KAAK,YAAY;aAEhC;AAGR,YAAO;;IAER,aAAa,UAAU,UAAU,YAAY,MAAM;IACnD,EACD,YACA;;EAEF,gBAAgB,MAAM;AACrB,UAAO,SAAS,IAAI,KAAK;;EAE1B;AAED,QAAO,OAAO,OACb,OAAO,OAAO,KAAK,EACnB,aACA,UACA;;;;AC7TF,SAAgB,aACf,QACe;AACf,QAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/preset-node.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n\tconst data = new TextEncoder().encode(input);\n\tconst hash = await crypto.subtle.digest(\"SHA-256\", data);\n\treturn Array.from(new Uint8Array(hash))\n\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t.join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n\tif (ttlMs === undefined) return false;\n\treturn Date.now() - cachedAt > ttlMs;\n}\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheAdapter, Logger, StorageBinary } from \"./types/index\";\n\n/** レスポンスヘッダまたはURLの拡張子からContent-Typeを推測する。 */\nfunction inferContentType(\n\turl: string,\n\tresponseContentType: string | null,\n): string {\n\tif (responseContentType?.startsWith(\"image/\")) {\n\t\treturn responseContentType.split(\";\")[0].trim();\n\t}\n\tif (url.includes(\".png\")) return \"image/png\";\n\tif (url.includes(\".gif\")) return \"image/gif\";\n\tif (url.includes(\".webp\")) return \"image/webp\";\n\treturn \"image/jpeg\";\n}\n\n/**\n * Notion画像URLをfetchしてImageCacheAdapterにキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n\tcache: ImageCacheAdapter,\n\tnotionUrl: string,\n\timageProxyBase: string,\n\tlogger?: Logger,\n): Promise<string> {\n\tconst hash = await sha256Hex(notionUrl);\n\tconst proxyUrl = `${imageProxyBase}/${hash}`;\n\n\tconst existing = await cache.get(hash);\n\tif (existing) {\n\t\tlogger?.debug?.(\"画像キャッシュヒット\", {\n\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\tcacheAdapter: cache.name,\n\t\t\timageHash: hash,\n\t\t});\n\t\treturn proxyUrl;\n\t}\n\n\tlogger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n\t\toperation: \"fetchAndCacheImage\",\n\t\tcacheAdapter: cache.name,\n\t\timageHash: hash,\n\t});\n\n\ttry {\n\t\tconst response = await fetch(notionUrl, {\n\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"cache/image_fetch_failed\",\n\t\t\t\tmessage: `Failed to fetch Notion image: HTTP ${response.status}`,\n\t\t\t\tcontext: {\n\t\t\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\t\t\tnotionUrl,\n\t\t\t\t\thttpStatus: response.status,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst data = await response.arrayBuffer();\n\t\tconst contentType = inferContentType(\n\t\t\tnotionUrl,\n\t\t\tresponse.headers.get(\"content-type\"),\n\t\t);\n\t\tawait cache.set(hash, data, contentType);\n\t\tlogger?.debug?.(\"画像をキャッシュに保存\", {\n\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\tcacheAdapter: cache.name,\n\t\t\timageHash: hash,\n\t\t});\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"cache/io_failed\",\n\t\t\tmessage: \"Failed to fetch or cache Notion image.\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"fetchAndCacheImage\", notionUrl },\n\t\t});\n\t}\n\n\treturn proxyUrl;\n}\n\n/**\n * ImageCacheAdapter と imageProxyBase から cacheImage 関数を構築するファクトリ。\n *\n * 返り値の関数は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheAdapter} に保存後、フロントエンドへの配信用プロキシ URL を返す。\n * Content-Type はレスポンスヘッダまたは URL の拡張子から推測する。\n * タイムアウトは 10 秒固定。\n */\nexport function buildCacheImageFn(\n\tcache: ImageCacheAdapter,\n\timageProxyBase: string,\n\tlogger?: Logger,\n): (notionUrl: string) => Promise<string> {\n\treturn (notionUrl) =>\n\t\tfetchAndCacheImage(cache, notionUrl, imageProxyBase, logger);\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n\tBaseContentItem,\n\tCachedItem,\n\tCMSHooks,\n\tContentConfig,\n\tDataSource,\n\tImageCacheAdapter,\n\tLogger,\n\tRendererFn,\n} from \"./types/index\";\n\n/** `buildCachedItem` に必要な CMS の依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n\tsource: DataSource<T>;\n\trendererFn: RendererFn | undefined;\n\timgCache: ImageCacheAdapter;\n\thasImageCache: boolean;\n\timageProxyBase: string;\n\tcontentConfig: ContentConfig | undefined;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n}\n\n/**\n * コンテンツアイテムをソースから Markdown ロード → blocks 生成 → HTML レンダリング\n * → フック適用まで実行し、キャッシュ保存用の `CachedItem` を返す。\n */\nexport async function buildCachedItem<T extends BaseContentItem>(\n\titem: T,\n\tctx: RenderContext<T>,\n): Promise<CachedItem<T>> {\n\tconst start = Date.now();\n\tctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n\t\tslug: item.slug,\n\t\tpageId: item.id,\n\t});\n\tctx.hooks.onRenderStart?.(item.slug);\n\n\tlet markdown: string;\n\ttry {\n\t\tmarkdown = await ctx.source.loadMarkdown(item);\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"source/load_markdown_failed\",\n\t\t\tmessage: \"Failed to load markdown from source.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:loadMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tlet blocks: ContentBlock[] = [];\n\ttry {\n\t\tblocks = await ctx.source.loadBlocks(item);\n\t} catch (err) {\n\t\tctx.logger?.warn?.(\"loadBlocks に失敗したため raw フォールバック\", {\n\t\t\tslug: item.slug,\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\tblocks = [];\n\t}\n\n\tconst cacheImage = ctx.hasImageCache\n\t\t? buildCacheImageFn(ctx.imgCache, ctx.imageProxyBase, ctx.logger)\n\t\t: undefined;\n\n\tlet html: string;\n\tconst rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\ttry {\n\t\thtml = await rendererFn(markdown, {\n\t\t\timageProxyBase: ctx.imageProxyBase,\n\t\t\tcacheImage,\n\t\t\tremarkPlugins: ctx.contentConfig?.remarkPlugins,\n\t\t\trehypePlugins: ctx.contentConfig?.rehypePlugins,\n\t\t});\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage: \"Failed to render markdown.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:renderMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tif (ctx.hooks.afterRender) {\n\t\thtml = await ctx.hooks.afterRender(html, item);\n\t}\n\n\tlet result: CachedItem<T> = {\n\t\thtml,\n\t\tblocks,\n\t\tmarkdown,\n\t\titem,\n\t\tnotionUpdatedAt: ctx.source.getLastModified(item),\n\t\tcachedAt: Date.now(),\n\t};\n\n\tif (ctx.hooks.beforeCache) {\n\t\tresult = (await ctx.hooks.beforeCache(result)) as CachedItem<T>;\n\t}\n\n\tconst durationMs = Date.now() - start;\n\tctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n\t\tslug: item.slug,\n\t\tdurationMs,\n\t});\n\tctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n\treturn result;\n}\n\n/**\n * renderer オプション未指定時のフォールバック。\n * @notion-headless-cms/renderer を動的 import する。\n * createCMS({ renderer }) で明示注入された場合はこのパスを通らない。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n\ttry {\n\t\tconst mod = await import(\"@notion-headless-cms/renderer\");\n\t\treturn mod.renderMarkdown as RendererFn;\n\t} catch (err) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage:\n\t\t\t\t\"renderer オプションが未指定で @notion-headless-cms/renderer が見つかりません。\" +\n\t\t\t\t\" createCMS({ renderer }) でレンダラーを注入するか、@notion-headless-cms/renderer をインストールしてください。\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"loadDefaultRenderer\" },\n\t\t});\n\t}\n}\n","export interface RetryConfig {\n\tretryOn: number[];\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n\t/** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n\tjitter?: boolean;\n\tonRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n\tretryOn: [429, 502, 503],\n\tmaxRetries: 4,\n\tbaseDelayMs: 1000,\n\tjitter: 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\tfn: () => Promise<T>,\n\tconfig: RetryConfig,\n): Promise<T> {\n\tlet lastError: unknown;\n\tfor (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (err) {\n\t\t\tconst status = (err as { status?: number }).status;\n\t\t\tif (status === undefined || !config.retryOn.includes(status)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tlastError = err;\n\t\t\tif (attempt < config.maxRetries) {\n\t\t\t\tconfig.onRetry?.(attempt + 1, status);\n\t\t\t\tconst jitterFactor =\n\t\t\t\t\tconfig.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n\t\t\t\tconst delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t\t}\n\t\t}\n\t}\n\tthrow lastError;\n}\n","import { isStale } from \"./cache\";\nimport type { ContentBlock, ContentResult } from \"./content/blocks\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItem } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n\tAdjacencyOptions,\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tCMSHooks,\n\tCollectionClient,\n\tDataSource,\n\tDocumentCacheAdapter,\n\tGetListOptions,\n\tItemWithContent,\n\tLogger,\n\tSortOption,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n */\nexport function collectionKey(collection: string, slug?: string): string {\n\treturn slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n\tcollection: string;\n\tsource: DataSource<T>;\n\tdocCache: DocumentCacheAdapter<T>;\n\trender: RenderContext<T>;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n\tttlMs: number | undefined;\n\tpublishedStatuses: string[];\n\taccessibleStatuses: string[];\n\tretryConfig: RetryConfig;\n\tmaxConcurrent: number;\n\twaitUntil: ((p: Promise<unknown>) => void) | undefined;\n\t/**\n\t * slug として使うフィールド名。\n\t * `createCMS({ collections })` で指定した値。\n\t * 設定時は `source.properties[slugField].notion` を Notion プロパティ名として\n\t * `findByProp` を呼び出す。\n\t */\n\tslugField?: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n\timplements CollectionClient<T>\n{\n\tconstructor(private readonly ctx: CollectionContext<T>) {}\n\n\t// ── 基本取得 ──────────────────────────────────────────────────────────\n\n\tasync getItem(slug: string): Promise<ItemWithContent<T> | null> {\n\t\tconst cached = await this.ctx.docCache.getItem(slug);\n\t\tif (cached) {\n\t\t\tif (\n\t\t\t\tthis.ctx.ttlMs !== undefined &&\n\t\t\t\tisStale(cached.cachedAt, this.ctx.ttlMs)\n\t\t\t) {\n\t\t\t\t// TTL 設定あり + 期限切れ: ブロッキングフェッチ(stale を返さない)\n\t\t\t\tthis.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n\t\t\t\t\toperation: \"getItem\",\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t\t});\n\t\t\t\tthis.ctx.hooks.onCacheMiss?.(slug);\n\t\t\t\tconst item = await this.findRaw(slug);\n\t\t\t\tif (!item) return null;\n\t\t\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\t\t\tawait this.ctx.docCache.setItem(slug, entry);\n\t\t\t\treturn this.attachContent(entry.item, entry);\n\t\t\t}\n\t\t\t// TTL 未設定 or 期限内: キャッシュを即時返却し、バックグラウンドで差分チェック\n\t\t\tconst bg = this.checkAndUpdateItemBg(slug, cached);\n\t\t\tif (this.ctx.waitUntil) {\n\t\t\t\tthis.ctx.waitUntil(bg);\n\t\t\t}\n\t\t\tthis.ctx.logger?.debug?.(\"キャッシュヒット\", {\n\t\t\t\toperation: \"getItem\",\n\t\t\t\tslug,\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t\tcachedAt: cached.cachedAt,\n\t\t\t});\n\t\t\tthis.ctx.hooks.onCacheHit?.(slug, cached);\n\t\t\treturn this.attachContent(cached.item, cached);\n\t\t}\n\n\t\t// キャッシュなし: 同期フェッチ\n\t\tthis.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n\t\t\toperation: \"getItem\",\n\t\t\tslug,\n\t\t\tcollection: this.ctx.collection,\n\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t});\n\t\tthis.ctx.hooks.onCacheMiss?.(slug);\n\t\tconst item = await this.findRaw(slug);\n\t\tif (!item) {\n\t\t\tthis.ctx.logger?.debug?.(\"アイテムが見つかりません\", {\n\t\t\t\toperation: \"getItem\",\n\t\t\t\tslug,\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\n\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\tconst save = this.ctx.docCache.setItem(slug, entry);\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\n\t\treturn this.attachContent(entry.item, entry);\n\t}\n\n\tasync getList(opts?: GetListOptions<T>): Promise<T[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn applyGetListOptions(items, opts);\n\t}\n\n\t// ── SSG / ナビゲーション ─────────────────────────────────────────────\n\n\tasync getStaticParams(): Promise<{ slug: string }[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => ({ slug: item.slug }));\n\t}\n\n\tasync getStaticPaths(): Promise<string[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => item.slug);\n\t}\n\n\tasync adjacent(\n\t\tslug: string,\n\t\topts?: AdjacencyOptions<T>,\n\t): Promise<{ prev: T | null; next: T | null }> {\n\t\tconst items = applyGetListOptions(await this.fetchList(), {\n\t\t\tsort: opts?.sort,\n\t\t});\n\t\tconst index = items.findIndex((it) => it.slug === slug);\n\t\tif (index === -1) return { prev: null, next: null };\n\t\treturn {\n\t\t\tprev: index > 0 ? (items[index - 1] ?? null) : null,\n\t\t\tnext: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n\t\t};\n\t}\n\n\t// ── キャッシュ ────────────────────────────────────────────────────────\n\n\tasync revalidate(scope?: \"all\" | { slug: string }): Promise<void> {\n\t\tthis.ctx.logger?.debug?.(\"キャッシュを無効化\", {\n\t\t\toperation: \"revalidate\",\n\t\t\tcollection: this.ctx.collection,\n\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\tslug: typeof scope === \"object\" ? scope.slug : undefined,\n\t\t});\n\t\tif (!this.ctx.docCache.invalidate) return;\n\t\tif (scope === undefined || scope === \"all\") {\n\t\t\tawait this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n\t\t} else {\n\t\t\tawait this.ctx.docCache.invalidate({\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tslug: scope.slug,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync prefetch(opts?: {\n\t\tconcurrency?: number;\n\t\tonProgress?: (done: number, total: number) => void;\n\t}): Promise<{ ok: number; failed: number }> {\n\t\tconst items = await this.fetchListRaw();\n\t\tconst concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n\t\tlet ok = 0;\n\t\tlet failed = 0;\n\n\t\tfor (let i = 0; i < items.length; i += concurrency) {\n\t\t\tconst chunk = items.slice(i, i + concurrency);\n\t\t\tawait Promise.all(\n\t\t\t\tchunk.map(async (item) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rendered = await buildCachedItem(item, this.ctx.render);\n\t\t\t\t\t\tawait this.ctx.docCache.setItem(item.slug, rendered);\n\t\t\t\t\t\tok++;\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tfailed++;\n\t\t\t\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\t\t\t\"prefetch: アイテムの事前レンダリングに失敗\",\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\t\t\tpageId: item.id,\n\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t\topts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n\t\t}\n\n\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\treturn { ok, failed };\n\t}\n\n\t// ── 内部 ──────────────────────────────────────────────────────────────\n\n\tprivate attachContent(item: T, cached: CachedItem<T>): ItemWithContent<T> {\n\t\tconst ctx = this.ctx;\n\t\tlet blocksCache: ContentBlock[] | undefined;\n\t\tlet htmlCache: string | undefined = cached.html;\n\t\tlet markdownCache: string | undefined;\n\n\t\tconst content: ContentResult = {\n\t\t\tget blocks(): ContentBlock[] {\n\t\t\t\tif (!blocksCache) {\n\t\t\t\t\t// lazy: キャッシュに blocks が保存されていない場合、簡易な single-raw に落とす\n\t\t\t\t\tblocksCache = [\n\t\t\t\t\t\t{ type: \"raw\", html: cached.html } satisfies ContentBlock,\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\treturn blocksCache;\n\t\t\t},\n\t\t\tasync html(): Promise<string> {\n\t\t\t\tif (htmlCache !== undefined) return htmlCache;\n\t\t\t\thtmlCache = cached.html;\n\t\t\t\treturn htmlCache;\n\t\t\t},\n\t\t\tasync markdown(): Promise<string> {\n\t\t\t\tif (markdownCache !== undefined) return markdownCache;\n\t\t\t\tmarkdownCache = await ctx.source.loadMarkdown(item);\n\t\t\t\treturn markdownCache;\n\t\t\t},\n\t\t};\n\n\t\tif (cached.blocks) blocksCache = cached.blocks;\n\n\t\treturn Object.assign(Object.create(null) as object, item, {\n\t\t\tcontent,\n\t\t}) as ItemWithContent<T>;\n\t}\n\n\tprivate async fetchList(): Promise<T[]> {\n\t\tconst cached = await this.ctx.docCache.getList();\n\t\tif (cached) {\n\t\t\tif (\n\t\t\t\tthis.ctx.ttlMs !== undefined &&\n\t\t\t\tisStale(cached.cachedAt, this.ctx.ttlMs)\n\t\t\t) {\n\t\t\t\t// TTL 設定あり + 期限切れ: ブロッキングフェッチ(stale を返さない)\n\t\t\t\tthis.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n\t\t\t\t\toperation: \"getList\",\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t\t});\n\t\t\t\tthis.ctx.hooks.onListCacheMiss?.();\n\t\t\t\tconst items = await this.fetchListRaw();\n\t\t\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\t\t\treturn items;\n\t\t\t}\n\t\t\t// TTL 未設定 or 期限内: キャッシュを即時返却し、バックグラウンドで差分チェック\n\t\t\tconst bg = this.checkAndUpdateListBg(cached);\n\t\t\tif (this.ctx.waitUntil) {\n\t\t\t\tthis.ctx.waitUntil(bg);\n\t\t\t}\n\t\t\tthis.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n\t\t\t\toperation: \"getList\",\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t\t});\n\t\t\tthis.ctx.hooks.onListCacheHit?.(cached);\n\t\t\treturn cached.items;\n\t\t}\n\n\t\t// キャッシュなし: 同期フェッチ\n\t\tthis.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n\t\t\toperation: \"getList\",\n\t\t\tcollection: this.ctx.collection,\n\t\t\tcacheAdapter: this.ctx.docCache.name,\n\t\t});\n\t\tthis.ctx.hooks.onListCacheMiss?.();\n\t\tconst items = await this.fetchListRaw();\n\t\tconst cachedAt = Date.now();\n\t\tconst save = this.ctx.docCache.setList({ items, cachedAt });\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\t\treturn items;\n\t}\n\n\tprivate async checkAndUpdateItemBg(\n\t\tslug: string,\n\t\tcached: CachedItem<T>,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst item = await this.findRaw(slug);\n\t\t\tif (!item) return;\n\t\t\tif (this.ctx.source.getLastModified(item) !== cached.notionUpdatedAt) {\n\t\t\t\t// 更新あり: 再レンダリングしてキャッシュを差し替える\n\t\t\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\t\t\tawait this.ctx.docCache.setItem(slug, entry);\n\t\t\t\tthis.ctx.logger?.debug?.(\"SWR: 差分を検出、キャッシュを差し替え\", {\n\t\t\t\t\toperation: \"getItem:bg\",\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\tnotionUpdatedAt: cached.notionUpdatedAt,\n\t\t\t\t});\n\t\t\t\tthis.ctx.hooks.onCacheRevalidated?.(slug, entry);\n\t\t\t} else if (this.ctx.ttlMs !== undefined) {\n\t\t\t\t// 変更なし + TTL あり: cachedAt をリセットして次回の期限切れを先送りする\n\t\t\t\tawait this.ctx.docCache.setItem(slug, {\n\t\t\t\t\t...cached,\n\t\t\t\t\tcachedAt: Date.now(),\n\t\t\t\t});\n\t\t\t\tthis.ctx.logger?.debug?.(\"SWR: 差分なし、TTL をリセット\", {\n\t\t\t\t\toperation: \"getItem:bg\",\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n\t\t\t\t{\n\t\t\t\t\tslug,\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n\t\ttry {\n\t\t\tconst items = await this.fetchListRaw();\n\t\t\tif (\n\t\t\t\tthis.ctx.source.getListVersion(items) !==\n\t\t\t\tthis.ctx.source.getListVersion(cached.items)\n\t\t\t) {\n\t\t\t\t// 更新あり: リストを差し替える\n\t\t\t\tconst listEntry = { items, cachedAt: Date.now() };\n\t\t\t\tawait this.ctx.docCache.setList(listEntry);\n\t\t\t\tthis.ctx.logger?.debug?.(\n\t\t\t\t\t\"SWR: リスト差分を検出、キャッシュを差し替え\",\n\t\t\t\t\t{\n\t\t\t\t\t\toperation: \"getList:bg\",\n\t\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthis.ctx.hooks.onListCacheRevalidated?.(listEntry);\n\t\t\t} else if (this.ctx.ttlMs !== undefined) {\n\t\t\t\t// 変更なし + TTL あり: cachedAt をリセットする\n\t\t\t\tawait this.ctx.docCache.setList({\n\t\t\t\t\t...cached,\n\t\t\t\t\tcachedAt: Date.now(),\n\t\t\t\t});\n\t\t\t\tthis.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n\t\t\t\t\toperation: \"getList:bg\",\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\"SWR: リストのバックグラウンド差分チェックに失敗\",\n\t\t\t\t{\n\t\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate fetchListRaw(): Promise<T[]> {\n\t\treturn withRetry(\n\t\t\t() =>\n\t\t\t\tthis.ctx.source.list({\n\t\t\t\t\tpublishedStatuses:\n\t\t\t\t\t\tthis.ctx.publishedStatuses.length > 0\n\t\t\t\t\t\t\t? this.ctx.publishedStatuses\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t}),\n\t\t\t{\n\t\t\t\t...this.ctx.retryConfig,\n\t\t\t\tonRetry: (attempt, status) => {\n\t\t\t\t\tthis.ctx.logger?.warn?.(\"getList() リトライ中\", { attempt, status });\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate async findRaw(slug: string): Promise<T | null> {\n\t\tconst retryOpts = {\n\t\t\t...this.ctx.retryConfig,\n\t\t\tonRetry: (attempt: number, status: number) => {\n\t\t\t\tthis.ctx.logger?.warn?.(\"getItem() リトライ中\", {\n\t\t\t\t\tattempt,\n\t\t\t\t\tstatus,\n\t\t\t\t\tslug,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\n\t\t// slug フィールドが指定され、DataSource が findByProp を持つ場合は\n\t\t// Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n\t\tconst slugField = this.ctx.slugField;\n\t\tconst notionPropName = slugField\n\t\t\t? this.ctx.source.properties?.[slugField]?.notion\n\t\t\t: undefined;\n\n\t\tlet item: T | null;\n\t\tconst findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n\t\tif (notionPropName && findByProp) {\n\t\t\titem = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n\t\t} else {\n\t\t\t// フォールバック: list して線形探索\n\t\t\tconst all = await withRetry(() => this.ctx.source.list(), retryOpts);\n\t\t\titem = all.find((i) => i.slug === slug) ?? null;\n\t\t}\n\n\t\tif (!item) return null;\n\t\tif (\n\t\t\tthis.ctx.accessibleStatuses.length > 0 &&\n\t\t\t(!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\treturn item;\n\t}\n}\n\nfunction applyGetListOptions<T extends BaseContentItem>(\n\titems: T[],\n\topts?: GetListOptions<T>,\n): T[] {\n\tif (!opts) return items;\n\tlet result = items;\n\n\tif (opts.statuses && opts.statuses.length > 0) {\n\t\tconst allow = new Set(opts.statuses);\n\t\tresult = result.filter(\n\t\t\t(it) => it.status !== undefined && allow.has(it.status),\n\t\t);\n\t}\n\n\tif (opts.tag) {\n\t\tconst tag = opts.tag;\n\t\tresult = result.filter((it) => {\n\t\t\tconst tags = (it as unknown as { tags?: unknown }).tags;\n\t\t\treturn Array.isArray(tags) && tags.includes(tag);\n\t\t});\n\t}\n\n\tif (opts.where) {\n\t\tconst where = opts.where;\n\t\tresult = result.filter((it) =>\n\t\t\tObject.entries(where).every(\n\t\t\t\t([key, value]) => (it as Record<string, unknown>)[key] === value,\n\t\t\t),\n\t\t);\n\t}\n\n\tif (opts.sort) {\n\t\tresult = [...result].sort(makeComparator(opts.sort));\n\t}\n\n\tconst skip = opts.skip ?? 0;\n\tconst limit = opts.limit;\n\tif (skip > 0 || limit !== undefined) {\n\t\tresult = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n\t}\n\n\treturn result;\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n\tsort: SortOption<T>,\n): (a: T, b: T) => number {\n\tconst by = sort.by;\n\tconst dir = sort.direction === \"asc\" ? 1 : -1;\n\treturn (a, b) => {\n\t\tconst av = (a as Record<string, unknown>)[by];\n\t\tconst bv = (b as Record<string, unknown>)[by];\n\t\tif (av === bv) return 0;\n\t\tif (av === undefined) return 1;\n\t\tif (bv === undefined) return -1;\n\t\t// biome-ignore lint/suspicious/noExplicitAny: 汎用比較\n\t\treturn (av as any) > (bv as any) ? dir : -dir;\n\t};\n}\n","import type { ImageCacheAdapter, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n\t/** マウントするベースパス。デフォルト `/api/cms`。 */\n\tbasePath?: string;\n\t/** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n\timagesPath?: string;\n\t/** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n\trevalidatePath?: string;\n\t/** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n\twebhookSecret?: string;\n\t/** デフォルト実装を無効化する場合 true。 */\n\tdisabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n\timageCache: ImageCacheAdapter;\n\t/** コレクション名で DataSource を取り出し parseWebhook にフォワードする。 */\n\tparseWebhook(\n\t\treq: Request,\n\t\twebhookSecret: string | undefined,\n\t): Promise<InvalidateScope | null>;\n\trevalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n\tbasePath: \"/api/cms\",\n\timagesPath: \"/images\",\n\trevalidatePath: \"/revalidate\",\n} as const;\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` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n\tadapter: HandlerAdapter,\n\topts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n\tconst basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n\tconst imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n\tconst revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n\treturn async (req: Request): Promise<Response> => {\n\t\tconst url = new URL(req.url);\n\t\tconst path = url.pathname;\n\n\t\tif (!path.startsWith(basePath)) {\n\t\t\treturn new Response(\"Not Found\", { status: 404 });\n\t\t}\n\t\tconst rel = path.slice(basePath.length) || \"/\";\n\n\t\t// 画像: GET {basePath}/images/:hash\n\t\tif (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n\t\t\tconst hash = rel.slice(imagesPath.length + 1);\n\t\t\tif (!hash) return new Response(\"Bad Request\", { status: 400 });\n\t\t\tconst object = await adapter.imageCache.get(hash);\n\t\t\tif (!object) return new Response(\"Not Found\", { status: 404 });\n\t\t\tconst headers = new Headers();\n\t\t\tif (object.contentType) headers.set(\"content-type\", object.contentType);\n\t\t\theaders.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n\t\t\treturn new Response(object.data, { headers });\n\t\t}\n\n\t\t// Revalidate: POST {basePath}/revalidate\n\t\tif (req.method === \"POST\" && rel === revalidatePath) {\n\t\t\tconst scope = await adapter.parseWebhook(req, opts.webhookSecret);\n\t\t\tif (!scope) {\n\t\t\t\treturn new Response(JSON.stringify({ ok: false, reason: \"invalid\" }), {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait adapter.revalidate(scope);\n\t\t\treturn new Response(JSON.stringify({ ok: true, scope }), {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t};\n}\n\nfunction trimTrailingSlash(s: string): string {\n\treturn s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { memoryDocumentCache, memoryImageCache } from \"./cache/memory\";\nimport type { CacheConfig, CreateCMSOptions, RendererFn } from \"./types/index\";\n\n/** `nodePreset()` のオプション。 */\nexport interface NodePresetOptions {\n\t/**\n\t * キャッシュ設定。\n\t * - 省略時: memoryDocumentCache + memoryImageCache をデフォルト有効化\n\t * - `\"disabled\"`: キャッシュを完全無効化\n\t * - オブジェクト: 任意の cache adapter を差し込む\n\t */\n\tcache?: CacheConfig | \"disabled\";\n\t/** SWR の TTL (ミリ秒)。`cache` をオブジェクトで渡した場合はそちらが優先される。 */\n\tttlMs?: number;\n\t/** カスタムレンダラー。未指定時は core が @notion-headless-cms/renderer を動的ロード。 */\n\trenderer?: RendererFn;\n}\n\n/**\n * Node.js ランタイム向けの `createCMS` オプションプリセット。\n * メモリキャッシュをデフォルト有効にした `{ cache, renderer }` を返す。\n *\n * @example\n * import { createCMS, nodePreset } from \"@notion-headless-cms/core\";\n * import { cmsDataSources } from \"./generated/cms-schema\";\n *\n * const cms = createCMS({\n * ...nodePreset({ ttlMs: 5 * 60_000 }),\n * dataSources: cmsDataSources,\n * });\n */\nexport function nodePreset(\n\topts: NodePresetOptions = {},\n): Pick<CreateCMSOptions, \"cache\" | \"renderer\"> {\n\tif (opts.cache === \"disabled\") {\n\t\treturn { cache: undefined, renderer: opts.renderer };\n\t}\n\treturn {\n\t\tcache: opts.cache ?? {\n\t\t\tdocument: memoryDocumentCache(),\n\t\t\timage: memoryImageCache(),\n\t\t\tttlMs: opts.ttlMs,\n\t\t},\n\t\trenderer: opts.renderer,\n\t};\n}\n","import { noopDocumentCache, noopImageCache } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport { nodePreset } from \"./preset-node\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n\tBaseContentItem,\n\tCacheConfig,\n\tCachedItemList,\n\tCMSHooks,\n\tCollectionClient,\n\tCollectionSemantics,\n\tCreateCMSOptions,\n\tDataSource,\n\tDataSourceMap,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tInferDataSourceItem,\n\tInvalidateScope,\n\tLogger,\n\tLogLevel,\n\tRendererFn,\n} from \"./types/index\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<D>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<D extends DataSourceMap> = {\n\t[K in keyof D]: CollectionClient<InferDataSourceItem<D[K]>>;\n} & CMSGlobalOps<D>;\n\n/** `CMSClient` のグローバル名前空間。`$` プレフィックス。 */\nexport interface CMSGlobalOps<D extends DataSourceMap> {\n\t/** 登録されているコレクション名の一覧。 */\n\treadonly $collections: readonly (keyof D & string)[];\n\t/** 全コレクションまたは特定コレクションのキャッシュを無効化する。 */\n\t$revalidate(scope?: InvalidateScope): Promise<void>;\n\t/** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n\t$handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n\t/** ハッシュキーでキャッシュ画像を取得する。 */\n\t$getCachedImage(hash: string): ReturnType<ImageCacheAdapter[\"get\"]>;\n}\n\nfunction resolveDocumentCache(\n\tcache: CacheConfig | undefined,\n\t// biome-ignore lint/suspicious/noExplicitAny: 横断的に利用\n): DocumentCacheAdapter<any> {\n\tif (!cache || cache === \"disabled\" || !cache.document) {\n\t\treturn noopDocumentCache();\n\t}\n\treturn cache.document;\n}\n\nfunction resolveImageCache(cache: CacheConfig | undefined): ImageCacheAdapter {\n\tif (!cache || cache === \"disabled\" || !cache.image) {\n\t\treturn noopImageCache();\n\t}\n\treturn cache.image;\n}\n\nfunction resolveTtl(cache: CacheConfig | undefined): number | undefined {\n\tif (!cache || cache === \"disabled\") return undefined;\n\treturn cache.ttlMs;\n}\n\nfunction hasImageCacheConfigured(cache: CacheConfig | undefined): boolean {\n\tif (!cache || cache === \"disabled\") return false;\n\treturn !!cache.image;\n}\n\n/**\n * `{collection}:{slug}` キー空間で動作するコレクション別キャッシュビューを生成する。\n * 単一の `DocumentCacheAdapter` に複数コレクションを同居させるためのアダプタ。\n */\nfunction scopeDocumentCache<T extends BaseContentItem>(\n\t// biome-ignore lint/suspicious/noExplicitAny: 共有ストレージのため\n\tbase: DocumentCacheAdapter<any>,\n\tcollection: string,\n): DocumentCacheAdapter<T> {\n\tconst itemKey = (slug: string): string => `${collection}:${slug}`;\n\t// リストはコレクション別にクロージャ変数で管理する。\n\t// base.getList/setList はコレクション名前空間を持たないため、\n\t// 複数コレクションが同じ base を共有すると上書きし合うバグが起きる。\n\t// 初回アクセスのみ base から読み込むことで pre-populate されたキャッシュを活かしつつ、\n\t// 以降の読み書きはコレクション固有のスロットに限定する。\n\tlet listSlot: CachedItemList<T> | null = null;\n\tlet listInitialized = false;\n\n\treturn {\n\t\tname: `${base.name}@${collection}`,\n\t\tgetList: async () => {\n\t\t\tif (!listInitialized) {\n\t\t\t\tlistInitialized = true;\n\t\t\t\tlistSlot = (await base.getList()) as CachedItemList<T> | null;\n\t\t\t}\n\t\t\treturn listSlot;\n\t\t},\n\t\tsetList: (data) => {\n\t\t\tlistSlot = data as CachedItemList<T>;\n\t\t\tlistInitialized = true;\n\t\t\treturn Promise.resolve();\n\t\t},\n\t\tgetItem: (slug) => base.getItem(itemKey(slug)),\n\t\tsetItem: (slug, data) => base.setItem(itemKey(slug), data),\n\t\tasync invalidate(scope) {\n\t\t\tlistSlot = null;\n\t\t\tlistInitialized = true; // 無効化後は base を再読みしない\n\t\t\tif (!base.invalidate) return;\n\t\t\tif (scope === \"all\") {\n\t\t\t\treturn base.invalidate({ collection });\n\t\t\t}\n\t\t\tif (\"slug\" in scope) {\n\t\t\t\t// アイテムは `{collection}:{slug}` で保存されるため正しいキーに変換する\n\t\t\t\treturn base.invalidate({\n\t\t\t\t\tcollection: scope.collection,\n\t\t\t\t\tslug: itemKey(scope.slug),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn base.invalidate(scope);\n\t\t},\n\t};\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n\tdebug: 0,\n\tinfo: 1,\n\twarn: 2,\n\terror: 3,\n};\n\n/** `logger` から `minLevel` 未満のレベルを除いた新しい Logger を返す。 */\nfunction applyLogLevel(\n\tlogger: Logger | undefined,\n\tminLevel: LogLevel,\n): Logger | undefined {\n\tif (!logger) return undefined;\n\tconst minOrder = LOG_LEVEL_ORDER[minLevel];\n\tconst filtered: Logger = {};\n\tfor (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n\t\tif (LOG_LEVEL_ORDER[level] >= minOrder) {\n\t\t\tfiltered[level] = logger[level];\n\t\t}\n\t}\n\treturn filtered;\n}\n\n/**\n * `preset` オプションを解決して `cache` / `renderer` のデフォルトを補完する内部関数。\n * 明示的な `cache` / `renderer` がある場合はそちらが優先される。\n * `preset` 未指定時は opts をそのまま返す。\n */\nfunction resolvePreset<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CreateCMSOptions<D> {\n\tif (opts.preset === \"disabled\") {\n\t\treturn { ...opts, cache: undefined };\n\t}\n\tif (opts.preset === \"node\") {\n\t\tconst presetResult = nodePreset({ ttlMs: opts.ttlMs });\n\t\treturn {\n\t\t\t...opts,\n\t\t\tcache: opts.cache ?? presetResult.cache,\n\t\t\trenderer: opts.renderer ?? presetResult.renderer,\n\t\t};\n\t}\n\treturn opts;\n}\n\n/**\n * 複数の DataSource を束ねた CMS クライアントを生成する。\n *\n * @example\n * // Node.js(preset を使った簡潔な記法)\n * const cms = createCMS({ dataSources: cmsDataSources, preset: \"node\", ttlMs: 5 * 60_000 });\n *\n * @example\n * // 従来の spread パターン(引き続き動作する)\n * const cms = createCMS({ ...nodePreset({ ttlMs: 5 * 60_000 }), dataSources: cmsDataSources });\n *\n * @example\n * // キャッシュを細かく指定する場合\n * const cms = createCMS({\n * dataSources,\n * cache: { document, image, ttlMs: 60_000 },\n * });\n */\nexport function createCMS<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CMSClient<D> {\n\tif (!opts.dataSources || Object.keys(opts.dataSources).length === 0) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"core/config_invalid\",\n\t\t\tmessage:\n\t\t\t\t\"createCMS: dataSources に少なくとも1つのコレクションを指定してください。\",\n\t\t\tcontext: { operation: \"createCMS\" },\n\t\t});\n\t}\n\n\t// collections が指定されたコレクションは slug が必須。\n\tfor (const [name, col] of Object.entries(opts.collections ?? {})) {\n\t\tconst c = col as CollectionSemantics | undefined;\n\t\tif (c && !c.slug) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"core/config_invalid\",\n\t\t\t\tmessage: `createCMS: コレクション \"${name}\" の collections.slug は必須です。slug として使うフィールド名を指定してください。`,\n\t\t\t\tcontext: { operation: \"createCMS\", collection: name },\n\t\t\t});\n\t\t}\n\t}\n\n\tconst resolved = resolvePreset(opts);\n\n\tconst baseDocCache = resolveDocumentCache(resolved.cache);\n\tconst imgCache = resolveImageCache(resolved.cache);\n\tconst hasImageCache = hasImageCacheConfigured(resolved.cache);\n\tconst ttlMs = resolveTtl(resolved.cache);\n\tconst imageProxyBase =\n\t\topts.content?.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n\tconst contentConfig = opts.content;\n\tconst rendererFn: RendererFn | undefined = resolved.renderer;\n\tconst waitUntil = opts.waitUntil;\n\tconst baseLogger: Logger | undefined = mergeLoggers(\n\t\topts.plugins ?? [],\n\t\topts.logger,\n\t);\n\tconst logger = opts.logLevel\n\t\t? applyLogLevel(baseLogger, opts.logLevel)\n\t\t: baseLogger;\n\tconst hooks: CMSHooks<BaseContentItem> = mergeHooks(\n\t\topts.plugins ?? [],\n\t\topts.hooks,\n\t\tlogger,\n\t);\n\tconst maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n\tconst retryConfig: RetryConfig = {\n\t\t...DEFAULT_RETRY_CONFIG,\n\t\t...(opts.rateLimiter ?? {}),\n\t};\n\n\tconst collectionNames = Object.keys(opts.dataSources) as (keyof D & string)[];\n\n\t// biome-ignore lint/suspicious/noExplicitAny: 各 T を保持\n\tconst collections: Record<string, CollectionClient<any>> = {};\n\t// biome-ignore lint/suspicious/noExplicitAny: 横断的に利用\n\tconst scopedCaches: DocumentCacheAdapter<any>[] = [];\n\tfor (const name of collectionNames) {\n\t\tconst source = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\tconst scopedCache = scopeDocumentCache<BaseContentItem>(baseDocCache, name);\n\t\tscopedCaches.push(scopedCache);\n\t\tconst col = opts.collections?.[name] as CollectionSemantics | undefined;\n\t\tconst colHooks = col?.hooks as CMSHooks<BaseContentItem> | undefined;\n\t\tconst collectionHooks: CMSHooks<BaseContentItem> = colHooks\n\t\t\t? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n\t\t\t: hooks;\n\t\tconst renderCtx: RenderContext<BaseContentItem> = {\n\t\t\tsource,\n\t\t\trendererFn,\n\t\t\timgCache,\n\t\t\thasImageCache,\n\t\t\timageProxyBase,\n\t\t\tcontentConfig,\n\t\t\thooks: collectionHooks,\n\t\t\tlogger,\n\t\t};\n\t\tconst ctx: CollectionContext<BaseContentItem> = {\n\t\t\tcollection: name,\n\t\t\tsource,\n\t\t\tdocCache: scopedCache,\n\t\t\trender: renderCtx,\n\t\t\thooks: collectionHooks,\n\t\t\tlogger,\n\t\t\tttlMs,\n\t\t\t// 公開条件は CollectionSemantics(createCMS の collections オプション)が権威\n\t\t\tpublishedStatuses: col?.publishedStatuses\n\t\t\t\t? [...col.publishedStatuses]\n\t\t\t\t: [],\n\t\t\taccessibleStatuses: col?.accessibleStatuses\n\t\t\t\t? [...col.accessibleStatuses]\n\t\t\t\t: [],\n\t\t\tretryConfig,\n\t\t\tmaxConcurrent,\n\t\t\twaitUntil,\n\t\t\tslugField: col?.slug,\n\t\t};\n\t\tcollections[name] = new CollectionClientImpl(ctx);\n\t}\n\n\tconst globalOps: CMSGlobalOps<D> = {\n\t\t$collections: collectionNames,\n\t\tasync $revalidate(scope?: InvalidateScope): Promise<void> {\n\t\t\tlogger?.debug?.(\"グローバルキャッシュを無効化\", {\n\t\t\t\toperation: \"$revalidate\",\n\t\t\t\tcacheAdapter: baseDocCache.name,\n\t\t\t});\n\t\t\t// baseDocCache を直接呼ばず各スコープキャッシュ経由で呼ぶ。\n\t\t\t// 直接呼ぶと scopeDocumentCache の listSlot がクリアされず stale になる。\n\t\t\tfor (const cache of scopedCaches) {\n\t\t\t\tif (!cache.invalidate) continue;\n\t\t\t\tawait cache.invalidate(scope ?? \"all\");\n\t\t\t}\n\t\t},\n\t\t$handler(handlerOpts?: HandlerOptions) {\n\t\t\treturn createHandler(\n\t\t\t\t{\n\t\t\t\t\timageCache: imgCache,\n\t\t\t\t\tparseWebhook: async (req, webhookSecret) => {\n\t\t\t\t\t\t// 各 DataSource の parseWebhook を順に試す\n\t\t\t\t\t\tfor (const name of collectionNames) {\n\t\t\t\t\t\t\tconst ds = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\t\t\t\t\t\tif (ds.parseWebhook) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst scope = await ds.parseWebhook(req.clone(), {\n\t\t\t\t\t\t\t\t\t\tsecret: webhookSecret,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\treturn scope;\n\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\tlogger?.warn?.(\"parseWebhook 失敗\", {\n\t\t\t\t\t\t\t\t\t\tcollection: name,\n\t\t\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// フォールバック: { slug } だけの汎用 JSON body\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst body = (await req.json()) as {\n\t\t\t\t\t\t\t\tslug?: string;\n\t\t\t\t\t\t\t\tcollection?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif (body.slug && body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection, slug: body.slug };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t},\n\t\t\t\t\trevalidate: (scope) => globalOps.$revalidate(scope),\n\t\t\t\t},\n\t\t\t\thandlerOpts,\n\t\t\t);\n\t\t},\n\t\t$getCachedImage(hash) {\n\t\t\treturn imgCache.get(hash);\n\t\t},\n\t};\n\n\treturn Object.assign(\n\t\tObject.create(null) as object,\n\t\tcollections,\n\t\tglobalOps,\n\t) as CMSClient<D>;\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\tname: string;\n\thooks?: CMSHooks<T>;\n\tlogger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n\tplugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n\treturn plugin;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notion-headless-cms/core",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
5
5
  "keywords": [
6
6
  "notion",