@notion-headless-cms/core 0.3.16 → 0.3.17

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
@@ -71,6 +71,8 @@ CLI 生成の `createCMS` ラッパーで返される items は、スキーマ
71
71
  - `cms.$revalidate(scope?)` — `"all" | { collection } | { collection, slug }`
72
72
  - `cms.$handler(opts?)` — Web Standard `(req) => Response` を返す (画像プロキシ + webhook)
73
73
  - `cms.$getCachedImage(hash)` — 画像キャッシュ直アクセス
74
+ - `cms.cacheImage(url)` — Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式に変換しキャッシュへ書き込む。画像キャッシュ未設定なら `undefined`。`@notion-headless-cms/react-renderer/server` の `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う
75
+ - `cms.imageProxyBase` — 画像プロキシのベース URL (`createCMS({ imageProxyBase })`、デフォルト `/api/images`)
74
76
 
75
77
  ### cache アダプタ
76
78
  - `memoryDocumentCache({ maxItems? })` — LRU 対応インメモリキャッシュ
@@ -1,2 +1,2 @@
1
- import { i as memoryCache, n as MemoryDocumentOptions, r as MemoryImageOptions, t as MemoryCacheOptions } from "../memory-BKDsuGVN.mjs";
1
+ import { i as memoryCache, n as MemoryDocumentOptions, r as MemoryImageOptions, t as MemoryCacheOptions } from "../memory-B7w5TQd_.mjs";
2
2
  export { MemoryCacheOptions, MemoryDocumentOptions, MemoryImageOptions, memoryCache };
@@ -128,6 +128,12 @@ interface CachedItemContent {
128
128
  html: string;
129
129
  markdown: string;
130
130
  blocks: ContentBlock[];
131
+ /**
132
+ * Notion API 由来のブロックツリー(`BlockObjectResponse + children`)。
133
+ * react-renderer など Notion 固有形式を消費する利用側のために保持する。
134
+ * core はゼロ依存ルールに従い `@notionhq/client` の型を import しないため `unknown[]` で扱う。
135
+ */
136
+ notionBlocks?: unknown[];
131
137
  /** メタデータ整合性検証用に同じ値を保持する。 */
132
138
  notionUpdatedAt: string;
133
139
  cachedAt: number;
@@ -153,4 +159,4 @@ interface CMSSchemaProperties {
153
159
  }
154
160
  //#endregion
155
161
  export { CachedItemMeta as a, ContentResult as c, CachedItemList as i, ImageRef as l, CMSSchemaProperties as n, StorageBinary as o, CachedItemContent as r, ContentBlock as s, BaseContentItem as t, InlineNode as u };
156
- //# sourceMappingURL=content-D7PfY-bV.d.mts.map
162
+ //# sourceMappingURL=content-CuJcHvgF.d.mts.map
@@ -1,4 +1,4 @@
1
- import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-D7PfY-bV.mjs";
1
+ import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-CuJcHvgF.mjs";
2
2
  import { n as CMSError } from "./errors-CC_x98vG.mjs";
3
3
 
4
4
  //#region src/types/hooks.d.ts
@@ -85,4 +85,4 @@ declare function mergeLoggers(plugins: Array<{
85
85
  }>, directLogger?: Logger): Logger | undefined;
86
86
  //#endregion
87
87
  export { Logger as a, definePlugin as i, mergeLoggers as n, CMSHooks as o, CMSPlugin as r, MaybePromise as s, mergeHooks as t };
88
- //# sourceMappingURL=hooks-C4HRqHLo.d.mts.map
88
+ //# sourceMappingURL=hooks-DFQ-tq_l.d.mts.map
package/dist/hooks.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as mergeLoggers, t as mergeHooks } from "./hooks-C4HRqHLo.mjs";
1
+ import { n as mergeLoggers, t as mergeHooks } from "./hooks-DFQ-tq_l.mjs";
2
2
  export { mergeHooks, mergeLoggers };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { a as CachedItemMeta, c as ContentResult, i as CachedItemList, l as ImageRef, n as CMSSchemaProperties, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem, u as InlineNode } from "./content-D7PfY-bV.mjs";
2
- import { a as CacheAdapter, c as DataSource, d as PropertyDef, f as PropertyMap, i as memoryCache, l as InvalidateKind, o as DocumentCacheOps, p as WebhookConfig, s as ImageCacheOps, t as MemoryCacheOptions, u as InvalidateScope } from "./memory-BKDsuGVN.mjs";
1
+ import { a as CachedItemMeta, c as ContentResult, i as CachedItemList, l as ImageRef, n as CMSSchemaProperties, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem, u as InlineNode } from "./content-CuJcHvgF.mjs";
2
+ import { a as CacheAdapter, c as DataSource, d as PropertyDef, f as PropertyMap, i as memoryCache, l as InvalidateKind, o as DocumentCacheOps, p as WebhookConfig, s as ImageCacheOps, t as MemoryCacheOptions, u as InvalidateScope } from "./memory-B7w5TQd_.mjs";
3
3
  import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-CC_x98vG.mjs";
4
- import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-C4HRqHLo.mjs";
4
+ import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-DFQ-tq_l.mjs";
5
5
 
6
6
  //#region src/types/collection.d.ts
7
7
  /**
@@ -52,6 +52,13 @@ type ItemWithContent<T extends BaseContentItem> = T & {
52
52
  /** HTML 文字列を返す。 */html(): Promise<string>; /** Markdown 文字列を返す。 */
53
53
  markdown(): Promise<string>; /** コンテンツ AST(ContentBlock 配列)を返す。 */
54
54
  blocks(): Promise<ContentBlock[]>;
55
+ /**
56
+ * Notion API のブロックツリー(`BlockObjectResponse + children`)を返す。
57
+ * DataSource が `loadNotionBlocks` を実装している場合のみ非 undefined。
58
+ * react-renderer 等の Notion 固有 renderer に渡すために使う。
59
+ * core はゼロ依存ルールに従い `unknown[]` 型として扱う(利用側でキャスト)。
60
+ */
61
+ notionBlocks(): Promise<unknown[] | undefined>;
55
62
  };
56
63
  /** `cache.warm()` のオプション。 */
57
64
  interface WarmOptions {
@@ -320,6 +327,17 @@ interface CMSGlobalOps {
320
327
  handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;
321
328
  /** ハッシュキーでキャッシュ画像を取得する。 */
322
329
  getCachedImage(hash: string): Promise<StorageBinary | null>;
330
+ /**
331
+ * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む関数。
332
+ * 画像キャッシュが未設定 (noop) の場合は `undefined`。react-renderer の
333
+ * `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う。
334
+ */
335
+ readonly cacheImage: ((url: string) => Promise<string>) | undefined;
336
+ /**
337
+ * 画像プロキシのベース URL (`createCMS({ imageProxyBase })`)。
338
+ * デフォルト `/api/images`。
339
+ */
340
+ readonly imageProxyBase: string;
323
341
  }
324
342
  /**
325
343
  * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。
package/dist/index.mjs CHANGED
@@ -226,6 +226,22 @@ async function buildCachedItemContent(item, ctx) {
226
226
  }
227
227
  });
228
228
  }
229
+ let notionBlocks;
230
+ if (ctx.source.loadNotionBlocks) try {
231
+ notionBlocks = await ctx.source.loadNotionBlocks(item);
232
+ } catch (err) {
233
+ if (isCMSError(err)) throw err;
234
+ throw new CMSError({
235
+ code: "source/load_blocks_failed",
236
+ message: "Failed to load Notion blocks from source.",
237
+ cause: err,
238
+ context: {
239
+ operation: "buildCachedItemContent:loadNotionBlocks",
240
+ pageId: item.id,
241
+ slug: item.slug
242
+ }
243
+ });
244
+ }
229
245
  const cacheImage = ctx.hasImageCache ? buildCacheImageFn(ctx.imgCache, ctx.imgCacheName, ctx.imageProxyBase, ctx.logger) : void 0;
230
246
  const rendererFn = ctx.rendererFn ?? await loadDefaultRenderer();
231
247
  let html;
@@ -254,6 +270,7 @@ async function buildCachedItemContent(item, ctx) {
254
270
  html,
255
271
  blocks,
256
272
  markdown,
273
+ notionBlocks,
257
274
  notionUpdatedAt: ctx.source.getLastModified(item),
258
275
  cachedAt: Date.now()
259
276
  };
@@ -482,7 +499,8 @@ var CollectionClientImpl = class {
482
499
  async loadOrBuildContent(slug, item) {
483
500
  const expected = this.ctx.source.getLastModified(item);
484
501
  const cached = await this.ctx.docCache.getContent(this.ctx.collection, slug);
485
- if (cached && cached.notionUpdatedAt === expected) return cached;
502
+ const needsNotionBlocksBackfill = this.ctx.source.loadNotionBlocks !== void 0 && cached !== null && cached.notionBlocks === void 0;
503
+ if (cached && cached.notionUpdatedAt === expected && !needsNotionBlocksBackfill) return cached;
486
504
  const fresh = await buildCachedItemContent(item, this.ctx.render);
487
505
  await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);
488
506
  this.ctx.hooks.onContentRevalidated?.(slug, fresh);
@@ -528,7 +546,8 @@ var CollectionClientImpl = class {
528
546
  return Object.assign(Object.create(null), item, {
529
547
  html: async () => (await loadPayload()).html,
530
548
  markdown: async () => (await loadPayload()).markdown,
531
- blocks: async () => (await loadPayload()).blocks
549
+ blocks: async () => (await loadPayload()).blocks,
550
+ notionBlocks: async () => (await loadPayload()).notionBlocks
532
551
  });
533
552
  }
534
553
  async fetchList() {
@@ -1007,6 +1026,8 @@ function createCMS(opts) {
1007
1026
  }
1008
1027
  const globalOps = {
1009
1028
  collections: collectionNames,
1029
+ cacheImage: cacheRes.hasImg ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger) : void 0,
1030
+ imageProxyBase,
1010
1031
  async invalidate(scope) {
1011
1032
  logger?.debug?.("グローバルキャッシュを無効化", {
1012
1033
  operation: "invalidate",
@@ -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 * 何もキャッシュしないアダプタ。`createCMS({ 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 \" createCMS の 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 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 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 の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\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 if (cached && cached.notionUpdatedAt === expected) return cached;\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 }) 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 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 CreateCMSOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\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\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` の `createCMS`\n * (低レベルのこの関数をラップしたもの) を経由する。\n *\n * @example\n * createCMS({\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 createCMS<C extends CollectionsConfig>(\n opts: CreateCMSOptions<C>,\n): CMSClient<C> {\n if (!opts.collections || Object.keys(opts.collections).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createCMS: collections に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createCMS\" },\n });\n }\n\n for (const [name, def] of Object.entries(opts.collections)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createCMS\", collection: name },\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createCMS\", 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(opts.collections)) {\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 globalOps: CMSGlobalOps = {\n collections: collectionNames,\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 = opts.collections[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<C>;\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 * 何もキャッシュしないアダプタ。`createCMS({ 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 \" createCMS の 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 の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\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 CreateCMSOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\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 (`createCMS({ 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` の `createCMS`\n * (低レベルのこの関数をラップしたもの) を経由する。\n *\n * @example\n * createCMS({\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 createCMS<C extends CollectionsConfig>(\n opts: CreateCMSOptions<C>,\n): CMSClient<C> {\n if (!opts.collections || Object.keys(opts.collections).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createCMS: collections に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createCMS\" },\n });\n }\n\n for (const [name, def] of Object.entries(opts.collections)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createCMS\", collection: name },\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createCMS\", 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(opts.collections)) {\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 = opts.collections[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<C>;\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,4 +1,4 @@
1
- import { a as CachedItemMeta, i as CachedItemList, l as ImageRef, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem } from "./content-D7PfY-bV.mjs";
1
+ import { a as CachedItemMeta, i as CachedItemList, l as ImageRef, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem } from "./content-CuJcHvgF.mjs";
2
2
 
3
3
  //#region src/types/data-source.d.ts
4
4
  /**
@@ -68,6 +68,13 @@ interface DataSource<T extends BaseContentItem = BaseContentItem> {
68
68
  loadBlocks(item: T): Promise<ContentBlock[]>;
69
69
  /** アイテム本文を Markdown 文字列で返す (html() 生成の元ソース)。 */
70
70
  loadMarkdown(item: T): Promise<string>;
71
+ /**
72
+ * Notion API のブロックツリー(`BlockObjectResponse + children`)を返す。
73
+ * 実装している場合のみ core がコンテンツキャッシュへ保存し、
74
+ * react-renderer など Notion 固有形式を消費する利用側で再利用できるようにする。
75
+ * core はゼロ依存ルールに従い `unknown[]` 型として扱う。
76
+ */
77
+ loadNotionBlocks?(item: T): Promise<unknown[]>;
71
78
  /** SWR 鮮度判定用。item の最終更新タイムスタンプ。 */
72
79
  getLastModified(item: T): string;
73
80
  /** リスト全体のバージョン文字列 (例: 最新 last_edited_time)。 */
@@ -143,4 +150,4 @@ interface MemoryCacheOptions extends MemoryDocumentOptions, MemoryImageOptions {
143
150
  declare function memoryCache(options?: MemoryCacheOptions): CacheAdapter;
144
151
  //#endregion
145
152
  export { CacheAdapter as a, DataSource as c, PropertyDef as d, PropertyMap as f, memoryCache as i, InvalidateKind as l, MemoryDocumentOptions as n, DocumentCacheOps as o, WebhookConfig as p, MemoryImageOptions as r, ImageCacheOps as s, MemoryCacheOptions as t, InvalidateScope as u };
146
- //# sourceMappingURL=memory-BKDsuGVN.d.mts.map
153
+ //# sourceMappingURL=memory-B7w5TQd_.d.mts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notion-headless-cms/core",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
5
5
  "keywords": [
6
6
  "notion",