@notion-headless-cms/core 0.3.20 → 0.3.21

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/README.md CHANGED
@@ -136,4 +136,3 @@ import { memoryCache } from "@notion-headless-cms/core/cache/memory";
136
136
 
137
137
  - [クイックスタート](../../docs/quickstart.md)
138
138
  - [CMS メソッド一覧](../../docs/api/cms-methods.md)
139
- - [v2.0 移行ガイド](../../docs/migration/v2.0.md)
package/dist/html.d.mts CHANGED
@@ -2,20 +2,26 @@
2
2
  interface NotionRevalidatorScriptOptions {
3
3
  /**
4
4
  * 再検証のトリガー。既定値: "visibility"
5
- * - "visibility": タブ可視化 (`visibilitychange` で hidden→visible) の度に reload
5
+ * - "visibility": `visibilitychange` で hidden→visible の度に reload
6
6
  * - "focus": ウィンドウ focus の度に reload (visibility より発火が頻繁)
7
7
  */
8
8
  on?: "visibility" | "focus";
9
- /**
10
- * `<script nonce="...">` を出力したい場合 (CSP 対応)。
11
- * 英数字・`-` / `_` / `+` / `/` / `=` のみ許可。属性値ブレイクアウトを防ぐため
12
- * 不正な文字が含まれていれば throw する。
13
- */
9
+ /** CSP 対応の `nonce` 値。base64 / base64url 由来の文字のみ許可、不正値は throw する。 */
14
10
  nonce?: string;
15
11
  }
16
12
  /**
17
- * React を使わないページに埋め込むための `<script>...</script>` 文字列を返す。
18
- * 返り値は外部入力を埋め込まない(nonce は厳格に検証)safe な内容。
13
+ * React を使わないテンプレート (Astro / Hono / Express など) 向けに、
14
+ * SWR 再検証用の `<script>...</script>` 文字列を返す。
15
+ *
16
+ * タブ可視化や focus を検知して `location.reload()` を呼び、サーバ側で
17
+ * `waitUntil` 配線済みの SWR bg が更新したキャッシュを次回訪問時に取得する。
18
+ * クエリ無し・別 API fetch 無し。
19
+ *
20
+ * @example
21
+ * // Astro
22
+ * <Fragment set:html={notionRevalidatorScript()} />
23
+ * // Hono
24
+ * c.html(h`...${raw(notionRevalidatorScript())}`);
19
25
  */
20
26
  declare function notionRevalidatorScript(opts?: NotionRevalidatorScriptOptions): string;
21
27
  //#endregion
package/dist/html.mjs CHANGED
@@ -1,8 +1,18 @@
1
1
  //#region src/html.ts
2
2
  const NONCE_PATTERN = /^[A-Za-z0-9+/=_-]+$/;
3
3
  /**
4
- * React を使わないページに埋め込むための `<script>...<\/script>` 文字列を返す。
5
- * 返り値は外部入力を埋め込まない(nonce は厳格に検証)safe な内容。
4
+ * React を使わないテンプレート (Astro / Hono / Express など) 向けに、
5
+ * SWR 再検証用の `<script>...<\/script>` 文字列を返す。
6
+ *
7
+ * タブ可視化や focus を検知して `location.reload()` を呼び、サーバ側で
8
+ * `waitUntil` 配線済みの SWR bg が更新したキャッシュを次回訪問時に取得する。
9
+ * クエリ無し・別 API fetch 無し。
10
+ *
11
+ * @example
12
+ * // Astro
13
+ * <Fragment set:html={notionRevalidatorScript()} />
14
+ * // Hono
15
+ * c.html(h`...${raw(notionRevalidatorScript())}`);
6
16
  */
7
17
  function notionRevalidatorScript(opts = {}) {
8
18
  const body = (opts.on ?? "visibility") === "focus" ? "let l=false;addEventListener(\"focus\",()=>{if(l)location.reload();l=true});" : "document.addEventListener(\"visibilitychange\",()=>{if(document.visibilityState===\"visible\")location.reload()});";
package/dist/html.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"html.mjs","names":[],"sources":["../src/html.ts"],"sourcesContent":["// React を使わないテンプレート (Astro / Hono / Express など) で SWR 体験を\n// 成立させるための、ゼロ依存の小さな <script> 文字列ジェネレータ。\n//\n// 使い方 (Astro):\n// import { notionRevalidatorScript } from \"@notion-headless-cms/core/html\";\n// <Fragment set:html={notionRevalidatorScript()} />\n//\n// 使い方 (Hono):\n// import { raw, html as h } from \"hono/html\";\n// c.html(h`...${raw(notionRevalidatorScript())}`);\n//\n// 仕組み: タブ可視化 (visibilitychange で hidden→visible) を検知して\n// `location.reload()` で同じ URL を再リクエストする。サーバ側は\n// `cloudflarePreset` 等で `waitUntil` が配線されていれば、前回訪問時に\n// SWR bg が KV を最新化済み → 再取得で新内容が返る。クエリ無し・別 API fetch 無し。\n\nexport interface NotionRevalidatorScriptOptions {\n /**\n * 再検証のトリガー。既定値: \"visibility\"\n * - \"visibility\": タブ可視化 (`visibilitychange` で hidden→visible) の度に reload\n * - \"focus\": ウィンドウ focus の度に reload (visibility より発火が頻繁)\n */\n on?: \"visibility\" | \"focus\";\n /**\n * `<script nonce=\"...\">` を出力したい場合 (CSP 対応)。\n * 英数字・`-` / `_` / `+` / `/` / `=` のみ許可。属性値ブレイクアウトを防ぐため\n * 不正な文字が含まれていれば throw する。\n */\n nonce?: string;\n}\n\n// CSP nonce は本来 base64 / base64url 由来のランダム文字列。属性値の\n// クォート・タグブレイクアウトを未然に防ぐため、最小限の許可セットで検証する。\nconst NONCE_PATTERN = /^[A-Za-z0-9+/=_-]+$/;\n\n/**\n * React を使わないページに埋め込むための `<script>...</script>` 文字列を返す。\n * 返り値は外部入力を埋め込まない(nonce は厳格に検証)safe な内容。\n */\nexport function notionRevalidatorScript(\n opts: NotionRevalidatorScriptOptions = {},\n): string {\n const trigger = opts.on ?? \"visibility\";\n const body =\n trigger === \"focus\"\n ? // 初回ロード時の focus は無視するため、loaded フラグで初回をスキップ。\n 'let l=false;addEventListener(\"focus\",()=>{if(l)location.reload();l=true});'\n : 'document.addEventListener(\"visibilitychange\",()=>{if(document.visibilityState===\"visible\")location.reload()});';\n let nonceAttr = \"\";\n if (opts.nonce !== undefined) {\n if (!NONCE_PATTERN.test(opts.nonce)) {\n throw new Error(\n \"notionRevalidatorScript: nonce に不正な文字が含まれています。base64 / base64url 由来の英数字のみ受け付けます。\",\n );\n }\n nonceAttr = ` nonce=\"${opts.nonce}\"`;\n }\n return `<script${nonceAttr}>(()=>{${body}})();</script>`;\n}\n"],"mappings":";AAiCA,MAAM,gBAAgB;;;;;AAMtB,SAAgB,wBACd,OAAuC,EAAE,EACjC;CAER,MAAM,QADU,KAAK,MAAM,kBAEb,UAER,iFACA;CACN,IAAI,YAAY;AAChB,KAAI,KAAK,UAAU,KAAA,GAAW;AAC5B,MAAI,CAAC,cAAc,KAAK,KAAK,MAAM,CACjC,OAAM,IAAI,MACR,mFACD;AAEH,cAAY,WAAW,KAAK,MAAM;;AAEpC,QAAO,UAAU,UAAU,SAAS,KAAK"}
1
+ {"version":3,"file":"html.mjs","names":[],"sources":["../src/html.ts"],"sourcesContent":["export interface NotionRevalidatorScriptOptions {\n /**\n * 再検証のトリガー。既定値: \"visibility\"\n * - \"visibility\": `visibilitychange` で hidden→visible の度に reload\n * - \"focus\": ウィンドウ focus の度に reload (visibility より発火が頻繁)\n */\n on?: \"visibility\" | \"focus\";\n /** CSP 対応の `nonce` 値。base64 / base64url 由来の文字のみ許可、不正値は throw する。 */\n nonce?: string;\n}\n\n// 属性値ブレイクアウトを防ぐため、CSP nonce base64 / base64url 範囲に限定して検証する\nconst NONCE_PATTERN = /^[A-Za-z0-9+/=_-]+$/;\n\n/**\n * React を使わないテンプレート (Astro / Hono / Express など) 向けに、\n * SWR 再検証用の `<script>...</script>` 文字列を返す。\n *\n * タブ可視化や focus を検知して `location.reload()` を呼び、サーバ側で\n * `waitUntil` 配線済みの SWR bg が更新したキャッシュを次回訪問時に取得する。\n * クエリ無し・別 API fetch 無し。\n *\n * @example\n * // Astro\n * <Fragment set:html={notionRevalidatorScript()} />\n * // Hono\n * c.html(h`...${raw(notionRevalidatorScript())}`);\n */\nexport function notionRevalidatorScript(\n opts: NotionRevalidatorScriptOptions = {},\n): string {\n const trigger = opts.on ?? \"visibility\";\n const body =\n trigger === \"focus\"\n ? // 初回ロード直後の focus は無視するため、loaded フラグで初回をスキップ\n 'let l=false;addEventListener(\"focus\",()=>{if(l)location.reload();l=true});'\n : 'document.addEventListener(\"visibilitychange\",()=>{if(document.visibilityState===\"visible\")location.reload()});';\n let nonceAttr = \"\";\n if (opts.nonce !== undefined) {\n if (!NONCE_PATTERN.test(opts.nonce)) {\n throw new Error(\n \"notionRevalidatorScript: nonce に不正な文字が含まれています。base64 / base64url 由来の英数字のみ受け付けます。\",\n );\n }\n nonceAttr = ` nonce=\"${opts.nonce}\"`;\n }\n return `<script${nonceAttr}>(()=>{${body}})();</script>`;\n}\n"],"mappings":";AAYA,MAAM,gBAAgB;;;;;;;;;;;;;;;AAgBtB,SAAgB,wBACd,OAAuC,EAAE,EACjC;CAER,MAAM,QADU,KAAK,MAAM,kBAEb,UAER,iFACA;CACN,IAAI,YAAY;AAChB,KAAI,KAAK,UAAU,KAAA,GAAW;AAC5B,MAAI,CAAC,cAAc,KAAK,KAAK,MAAM,CACjC,OAAM,IAAI,MACR,mFACD;AAEH,cAAY,WAAW,KAAK,MAAM;;AAEpC,QAAO,UAAU,UAAU,SAAS,KAAK"}
package/dist/index.d.mts CHANGED
@@ -345,28 +345,19 @@ interface HandlerAdapter {
345
345
  declare function createHandler(adapter: HandlerAdapter, opts?: HandlerOptions): (req: Request) => Promise<Response>;
346
346
  //#endregion
347
347
  //#region src/cms.d.ts
348
- /** `CMSClient<C>` — コレクション別アクセス + グローバル操作の合成型。 */
348
+ /** コレクション別アクセス + グローバル操作の合成型。 */
349
349
  type CMSClient<C extends CollectionsConfig> = { [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>> } & CMSGlobalOps;
350
- /** `CMSClient` のグローバル名前空間。 */
351
350
  interface CMSGlobalOps {
352
- /** 登録されているコレクション名の一覧。 */
353
351
  readonly collections: readonly string[];
354
- /** 全コレクションまたは特定スコープのキャッシュを無効化する。 */
355
352
  invalidate(scope?: InvalidateScope): Promise<void>;
356
- /** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */
353
+ /** Web Standard Request/Response ベースのルートハンドラ (画像プロキシ + webhook) */
357
354
  handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;
358
- /** ハッシュキーでキャッシュ画像を取得する。 */
359
355
  getCachedImage(hash: string): Promise<StorageBinary | null>;
360
356
  /**
361
- * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む関数。
362
- * 画像キャッシュが未設定 (noop) の場合は `undefined`。react-renderer の
363
- * `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う。
357
+ * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む。
358
+ * 画像キャッシュが未設定 (noop) の場合は `undefined`。
364
359
  */
365
360
  readonly cacheImage: ((url: string) => Promise<string>) | undefined;
366
- /**
367
- * 画像プロキシのベース URL (`createClient({ imageProxyBase })`)。
368
- * デフォルト `/api/images`。
369
- */
370
361
  readonly imageProxyBase: string;
371
362
  }
372
363
  /**
@@ -427,14 +418,12 @@ declare function withRetry<T>(fn: () => Promise<T>, config: RetryConfig): Promis
427
418
  //#endregion
428
419
  //#region src/collection.d.ts
429
420
  /**
430
- * コレクション別キャッシュキーを生成する。
431
- * item: `{collection}:{slug}` / list: `{collection}`
421
+ * コレクション別キャッシュキーを生成する (item: `{collection}:{slug}` / list: `{collection}`)。
432
422
  *
433
- * (Cache adapter 内部のキー戦略はアダプタごとに異なるが、
434
- * 表示や再計算用に core 側でも公開ヘルパーを提供する)
423
+ * cache adapter は内部で独自のキー戦略を持つが、ログ出力や差分再計算で
424
+ * 同一表現が必要になるため core 側にも公開する。
435
425
  */
436
426
  declare function collectionKey(collection: string, slug?: string): string;
437
- /** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */
438
427
  interface CollectionContext<T extends BaseContentItem> {
439
428
  collection: string;
440
429
  source: DataSource<T>;
@@ -450,13 +439,11 @@ interface CollectionContext<T extends BaseContentItem> {
450
439
  maxConcurrent: number;
451
440
  waitUntil: ((p: Promise<unknown>) => void) | undefined;
452
441
  /**
453
- * slug として使うフィールド名 (CLI 生成の `CollectionDef.slugField`)。
454
- * `source.properties[slugField].notion` を Notion プロパティ名として
455
- * `findByProp` を呼び出す。
442
+ * slug として使うフィールド名。`source.properties[slugField].notion`
443
+ * Notion プロパティ名として `findByProp` を呼び出す。
456
444
  */
457
445
  slugField: string;
458
446
  }
459
- /** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */
460
447
  declare class CollectionClientImpl<T extends BaseContentItem> implements CollectionClient<T> {
461
448
  private readonly ctx;
462
449
  readonly cache: CollectionCacheOps<T>;
@@ -474,12 +461,9 @@ declare class CollectionClientImpl<T extends BaseContentItem> implements Collect
474
461
  private warmImpl;
475
462
  private persistMeta;
476
463
  private invalidateContentEntry;
477
- /**
478
- * 本文キャッシュをロードする。キャッシュが無いか、メタとの整合性が取れない場合は
479
- * 再生成して書き戻す。
480
- */
464
+ /** 本文キャッシュ。メタとの整合 (`notionUpdatedAt`) が崩れていれば再生成して書き戻す。 */
481
465
  private loadOrBuildContent;
482
- /** メタ既知の状態で本文だけバックグラウンド再生成する。エラーは onSwrError フックに通知する。 */
466
+ /** メタ既知の状態で本文だけ再生成する。エラーは onSwrError フックに通知して握り潰す。 */
483
467
  private rebuildContentBg;
484
468
  private attachLazyContent;
485
469
  private fetchList;
package/dist/index.mjs CHANGED
@@ -323,16 +323,14 @@ async function withRetry(fn, config) {
323
323
  //#endregion
324
324
  //#region src/collection.ts
325
325
  /**
326
- * コレクション別キャッシュキーを生成する。
327
- * item: `{collection}:{slug}` / list: `{collection}`
326
+ * コレクション別キャッシュキーを生成する (item: `{collection}:{slug}` / list: `{collection}`)。
328
327
  *
329
- * (Cache adapter 内部のキー戦略はアダプタごとに異なるが、
330
- * 表示や再計算用に core 側でも公開ヘルパーを提供する)
328
+ * cache adapter は内部で独自のキー戦略を持つが、ログ出力や差分再計算で
329
+ * 同一表現が必要になるため core 側にも公開する。
331
330
  */
332
331
  function collectionKey(collection, slug) {
333
332
  return slug ? `${collection}:${slug}` : collection;
334
333
  }
335
- /** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */
336
334
  var CollectionClientImpl = class {
337
335
  cache;
338
336
  constructor(ctx) {
@@ -492,21 +490,17 @@ var CollectionClientImpl = class {
492
490
  kind: "content"
493
491
  });
494
492
  }
495
- /**
496
- * 本文キャッシュをロードする。キャッシュが無いか、メタとの整合性が取れない場合は
497
- * 再生成して書き戻す。
498
- */
493
+ /** 本文キャッシュ。メタとの整合 (`notionUpdatedAt`) が崩れていれば再生成して書き戻す。 */
499
494
  async loadOrBuildContent(slug, item) {
500
495
  const expected = this.ctx.source.getLastModified(item);
501
496
  const cached = await this.ctx.docCache.getContent(this.ctx.collection, slug);
502
- const needsNotionBlocksBackfill = this.ctx.source.loadNotionBlocks !== void 0 && cached !== null && cached.notionBlocks === void 0;
503
- if (cached && cached.notionUpdatedAt === expected && !needsNotionBlocksBackfill) return cached;
497
+ if (cached && cached.notionUpdatedAt === expected) return cached;
504
498
  const fresh = await buildCachedItemContent(item, this.ctx.render);
505
499
  await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);
506
500
  this.ctx.hooks.onContentRevalidated?.(slug, fresh);
507
501
  return fresh;
508
502
  }
509
- /** メタ既知の状態で本文だけバックグラウンド再生成する。エラーは onSwrError フックに通知する。 */
503
+ /** メタ既知の状態で本文だけ再生成する。エラーは onSwrError フックに通知して握り潰す。 */
510
504
  async rebuildContentBg(slug, item) {
511
505
  try {
512
506
  const fresh = await buildCachedItemContent(item, this.ctx.render);
@@ -757,7 +751,7 @@ function applyListOptions(items, opts) {
757
751
  if (skip > 0 || limit !== void 0) result = result.slice(skip, limit !== void 0 ? skip + limit : void 0);
758
752
  return result;
759
753
  }
760
- /** publishedAt 降順、未設定の場合は lastEditedTime 降順でソートする。 */
754
+ /** publishedAt 降順 (未設定なら lastEditedTime 降順) でソートする。 */
761
755
  function sortByPublishedAtDesc(items) {
762
756
  return [...items].sort((a, b) => {
763
757
  const av = a.publishedAt ?? a.lastEditedTime;
@@ -796,10 +790,7 @@ const DEFAULT_OPTS = {
796
790
  imagesPath: "/images",
797
791
  revalidatePath: "/revalidate"
798
792
  };
799
- /**
800
- * CMSError のコードから HTTP ステータスコードを返す。
801
- * 既知の webhook エラーコードのみ対応し、それ以外は null を返す。
802
- */
793
+ /** Webhook 系の CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */
803
794
  function webhookErrorStatus(code) {
804
795
  if (code === "webhook/signature_invalid") return 401;
805
796
  if (code === "webhook/not_implemented") return 501;
@@ -876,10 +867,7 @@ function trimTrailingSlash(s) {
876
867
  //#region src/cms.ts
877
868
  const DEFAULT_IMAGE_PROXY_BASE = "/api/images";
878
869
  /**
879
- * `cache` オプションから document / image オペレーションを解決する。
880
- *
881
- * - 各 adapter の `handles` を見て先勝ち (最初に見つかったもの) で振り分ける
882
- * - 未指定なら両方 noop
870
+ * adapter の `handles` を見て先勝ちで document / image を割り当てる。未指定は両方 noop。
883
871
  */
884
872
  function resolveCache(cache) {
885
873
  const adapters = cache ?? [];
@@ -915,7 +903,6 @@ const LOG_LEVEL_ORDER = {
915
903
  warn: 2,
916
904
  error: 3
917
905
  };
918
- /** `logger` から `minLevel` 未満のレベルを除いた新しい Logger を返す。 */
919
906
  function applyLogLevel(logger, minLevel) {
920
907
  if (!logger) return void 0;
921
908
  const minOrder = LOG_LEVEL_ORDER[minLevel];
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/cache/noop.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/renderer` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/renderer\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/renderer のロードにも失敗しました。\" +\n \" createClient の renderer オプションを指定するか、@notion-headless-cms/renderer をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n onRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n config.onRetry?.(attempt + 1, status);\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n *\n * (Cache adapter 内部のキー戦略はアダプタごとに異なるが、\n * 表示や再計算用に core 側でも公開ヘルパーを提供する)\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名 (CLI 生成の `CollectionDef.slugField`)。\n * `source.properties[slugField].notion` を Notion プロパティ名として\n * `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n // bypassCache: 強制ブロッキング取得\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: キャッシュ即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n // メタ未キャッシュ: 同期フェッチ (保存はバックグラウンド可)\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /**\n * 本文キャッシュをロードする。キャッシュが無いか、メタとの整合性が取れない場合は\n * 再生成して書き戻す。\n */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n // notionBlocks 対応より前のキャッシュは notionBlocks を持たないため、\n // source が loadNotionBlocks を実装していて cached に欠けている場合は\n // 再生成して埋める (後方互換のための lazy backfill)。\n const needsNotionBlocksBackfill =\n this.ctx.source.loadNotionBlocks !== undefined &&\n cached !== null &&\n cached.notionBlocks === undefined;\n if (\n cached &&\n cached.notionUpdatedAt === expected &&\n !needsNotionBlocksBackfill\n ) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけバックグラウンド再生成する。エラーは onSwrError フックに通知する。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // 同一インスタンス内で本文ロードを集約する (複数呼び出しでも 1 回の I/O)\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => (await loadPayload()).notionBlocks,\n }) as ItemWithContent<T>;\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n // SWR: 即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n // 未キャッシュ: 同期フェッチ\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else if (this.ctx.ttlMs !== undefined) {\n // 変更なし + TTL あり: cachedAt をリセットして次回の期限切れを先送りする\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、TTL をリセット\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", { attempt, status });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n });\n },\n };\n\n // slugField から Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // フォールバック: list して線形探索\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順、未設定の場合は lastEditedTime 降順でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n // lastEditedTime は必須なので av/bv は常に truthy\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/**\n * CMSError のコードから HTTP ステータスコードを返す。\n * 既知の webhook エラーコードのみ対応し、それ以外は null を返す。\n */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n // 画像: GET {basePath}/images/:hash\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n // Revalidate: POST {basePath}/revalidate/:collection\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateClientOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\nimport type {\n CMSAdapter,\n CMSSources,\n MergeSourceCollections,\n} from \"./types/sources\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<C>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/** `CMSClient` のグローバル名前空間。 */\nexport interface CMSGlobalOps {\n /** 登録されているコレクション名の一覧。 */\n readonly collections: readonly string[];\n /** 全コレクションまたは特定スコープのキャッシュを無効化する。 */\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n /** ハッシュキーでキャッシュ画像を取得する。 */\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む関数。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。react-renderer の\n * `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n /**\n * 画像プロキシのベース URL (`createClient({ imageProxyBase })`)。\n * デフォルト `/api/images`。\n */\n readonly imageProxyBase: string;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n img: ImageCacheOps;\n imgName: string;\n hasImg: boolean;\n}\n\n/**\n * `cache` オプションから document / image オペレーションを解決する。\n *\n * - 各 adapter の `handles` を見て先勝ち (最初に見つかったもの) で振り分ける\n * - 未指定なら両方 noop\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgFound = true;\n }\n }\n\n return { doc, docName, img, imgName, hasImg: imgFound };\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/** `logger` から `minLevel` 未満のレベルを除いた新しい Logger を返す。 */\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。\n *\n * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createClient`\n * (低レベルのこの関数をラップしたもの) を経由する。\n *\n * @example\n * createClient({\n * collections: {\n * posts: {\n * source: createNotionCollection({ token, dataSourceId, properties }),\n * slugField: \"slug\",\n * statusField: \"status\",\n * publishedStatuses: [\"公開済み\"],\n * }\n * },\n * cache: [memoryCache()],\n * swr: { ttlMs: 5 * 60_000 },\n * });\n */\nexport function createClient<\n C extends CollectionsConfig = CollectionsConfig,\n S extends CMSSources = CMSSources,\n>(\n opts: CreateClientOptions<C, S>,\n): CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : C\n> {\n // sources が指定されていれば各 adapter の collections をマージする (後勝ち)\n let mergedFromSources: CollectionsConfig | undefined;\n if (opts.sources) {\n mergedFromSources = {};\n for (const adapter of Object.values(\n opts.sources as unknown as Record<string, CMSAdapter | undefined>,\n )) {\n if (adapter) Object.assign(mergedFromSources, adapter.collections);\n }\n }\n const collectionsInput: CollectionsConfig | undefined =\n mergedFromSources ?? opts.collections;\n\n if (!collectionsInput || Object.keys(collectionsInput).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createClient: sources または collections に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createClient\" },\n });\n }\n\n for (const [name, def] of Object.entries(collectionsInput)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createClient\", collection: name },\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createClient\", collection: name },\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const logger = opts.logLevel\n ? applyLogLevel(baseLogger, opts.logLevel)\n : baseLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: (keyof C & string)[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(collectionsInput)) {\n collectionNames.push(name as keyof C & string);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = collectionsInput[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : C\n >;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/cache/noop.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/renderer` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/renderer\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/renderer のロードにも失敗しました。\" +\n \" createClient の renderer オプションを指定するか、@notion-headless-cms/renderer をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n onRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n config.onRetry?.(attempt + 1, status);\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する (item: `{collection}:{slug}` / list: `{collection}`)。\n *\n * 各 cache adapter は内部で独自のキー戦略を持つが、ログ出力や差分再計算で\n * 同一表現が必要になるため core 側にも公開する。\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名。`source.properties[slugField].notion` を\n * Notion プロパティ名として `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n // TTL 切れはブロッキングで再取得する (stale を返さない要件)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: 即時返却しつつバックグラウンドで差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n // 保存だけはバックグラウンド可: ユーザー向けレスポンスを早めに返す\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /** 本文キャッシュ。メタとの整合 (`notionUpdatedAt`) が崩れていれば再生成して書き戻す。 */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n if (cached && cached.notionUpdatedAt === expected) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけ再生成する。エラーは onSwrError フックに通知して握り潰す。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // html() / markdown() / blocks() を同じアイテムから複数回呼んでも I/O は 1 回に集約する\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => (await loadPayload()).notionBlocks,\n }) as ItemWithContent<T>;\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n // TTL 切れはブロッキングで再取得する (find と同じ理由)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else if (this.ctx.ttlMs !== undefined) {\n // Notion 側に変化が無いので cachedAt だけ更新し TTL 期限切れを先送りする\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、TTL をリセット\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", { attempt, status });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n });\n },\n };\n\n // PropertyMap が解決できる場合は単一プロパティ filter で 1 ページだけ取得する (高速)\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // PropertyMap 未提供 / findByProp 未実装の DataSource 向けフォールバック\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順 (未設定なら lastEditedTime 降順) でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/** Webhook 系の CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateClientOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\nimport type {\n CMSAdapter,\n CMSSources,\n MergeSourceCollections,\n} from \"./types/sources\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\nexport interface CMSGlobalOps {\n readonly collections: readonly string[];\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard な Request/Response ベースのルートハンドラ (画像プロキシ + webhook)。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n readonly imageProxyBase: string;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n img: ImageCacheOps;\n imgName: string;\n hasImg: boolean;\n}\n\n/**\n * adapter の `handles` を見て先勝ちで document / image を割り当てる。未指定は両方 noop。\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgFound = true;\n }\n }\n\n return { doc, docName, img, imgName, hasImg: imgFound };\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。\n *\n * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createClient`\n * (低レベルのこの関数をラップしたもの) を経由する。\n *\n * @example\n * createClient({\n * collections: {\n * posts: {\n * source: createNotionCollection({ token, dataSourceId, properties }),\n * slugField: \"slug\",\n * statusField: \"status\",\n * publishedStatuses: [\"公開済み\"],\n * }\n * },\n * cache: [memoryCache()],\n * swr: { ttlMs: 5 * 60_000 },\n * });\n */\nexport function createClient<\n C extends CollectionsConfig = CollectionsConfig,\n S extends CMSSources = CMSSources,\n>(\n opts: CreateClientOptions<C, S>,\n): CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : C\n> {\n // sources を渡された場合は後勝ちで collections にマージする\n let mergedFromSources: CollectionsConfig | undefined;\n if (opts.sources) {\n mergedFromSources = {};\n for (const adapter of Object.values(\n opts.sources as unknown as Record<string, CMSAdapter | undefined>,\n )) {\n if (adapter) Object.assign(mergedFromSources, adapter.collections);\n }\n }\n const collectionsInput: CollectionsConfig | undefined =\n mergedFromSources ?? opts.collections;\n\n if (!collectionsInput || Object.keys(collectionsInput).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createClient: sources または collections に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createClient\" },\n });\n }\n\n for (const [name, def] of Object.entries(collectionsInput)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createClient\", collection: name },\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createClient\", collection: name },\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const logger = opts.logLevel\n ? applyLogLevel(baseLogger, opts.logLevel)\n : baseLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: (keyof C & string)[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(collectionsInput)) {\n collectionNames.push(name as keyof C & string);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = collectionsInput[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : C\n >;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notion-headless-cms/core",
3
- "version": "0.3.20",
3
+ "version": "0.3.21",
4
4
  "description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
5
5
  "keywords": [
6
6
  "notion",