@notion-headless-cms/core 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -268,6 +268,76 @@ interface CMSGlobalOps {
268
268
  */
269
269
  declare function createClient<S extends CMSSources = CMSSources>(opts: CreateClientOptions<S>): CMSClient<MergeSourceCollections<S> extends CollectionsConfig ? MergeSourceCollections<S> : CollectionsConfig>;
270
270
  //#endregion
271
+ //#region src/page-index.d.ts
272
+ /** 逆引きの解決先。Notion ページ ID から自サイトの位置を引く。 */
273
+ interface PageIndexEntry {
274
+ /** 所属コレクション名。 */
275
+ collection: string;
276
+ /** URL キー。 */
277
+ slug: string;
278
+ /** ページ名(表示テキスト用)。 */
279
+ title?: string | null;
280
+ }
281
+ /** `buildPageIndex` が返す逆引きマップ。キーは `normalizePageId` 済みの pageId。 */
282
+ type PageIndex = Map<string, PageIndexEntry>;
283
+ /**
284
+ * `buildPageIndex` / `buildPageLinkMap` が必要とする最小の CMS 形状。
285
+ * `collections` でコレクション名を列挙し、各名でコレクションクライアントへアクセスする。
286
+ * `CMSClient` はコレクションを自身に spread しているため構造的に満たす。
287
+ */
288
+ interface PageIndexSource {
289
+ readonly collections: readonly string[];
290
+ }
291
+ interface BuildPageIndexOptions {
292
+ /** 走査対象のコレクション名。未指定なら `source.collections` 全件。 */
293
+ collections?: readonly string[];
294
+ }
295
+ /**
296
+ * Notion ページ ID を比較用に正規化する。
297
+ * Notion の ID は文脈によりダッシュの有無・大文字小文字が揺れるため、
298
+ * ダッシュ除去 + 小文字化して突き合わせる。
299
+ *
300
+ * react-renderer 側も同一実装で `pageLinks` を引くため、変更時は両方を揃えること。
301
+ */
302
+ declare function normalizePageId(id: string): string;
303
+ /**
304
+ * 全コレクションを `list()` で走査し、pageId → {collection, slug, title} の逆引きマップを構築する。
305
+ * Notion 内部リンク(link_to_page / page mention など)を自サイト URL へ解決するための材料。
306
+ *
307
+ * `list()` は SWR ドキュメントキャッシュ経由のためウォーム後は安価。
308
+ */
309
+ declare function buildPageIndex(source: PageIndexSource, opts?: BuildPageIndexOptions): Promise<PageIndex>;
310
+ /** 解決済みの内部リンク。`href` は構築時に決まり、`title` は表示テキスト用。 */
311
+ interface ResolvedPageLink {
312
+ href: string;
313
+ title?: string | null;
314
+ }
315
+ /**
316
+ * 正規化済み pageId → 解決済みリンクのプレーンマップ。
317
+ * 関数ではなくプレーンオブジェクトなので、loader / RSC(Server Component → Client
318
+ * Component)境界を越えて `<NotionRenderer pageLinks={...} />` にそのまま渡せる。
319
+ */
320
+ type PageLinkMap = Record<string, ResolvedPageLink>;
321
+ interface BuildPageLinkMapOptions extends BuildPageIndexOptions {
322
+ /**
323
+ * エントリから URL を組み立てる関数。既定は `/${collection}/${slug}`。
324
+ * 単一コレクションで `/${slug}` にしたい場合などに上書きする。
325
+ */
326
+ url?: (entry: PageIndexEntry, pageId: string) => string;
327
+ /** 事前構築済みインデックス。指定するとリクエストごとの再構築を省ける。 */
328
+ index?: PageIndex;
329
+ }
330
+ /**
331
+ * Notion 内部リンクを「正規化 pageId → {href, title}」のプレーンマップに解決する。
332
+ * サーバ側(loader / RSC / route handler)で 1 回構築し、`<NotionRenderer pageLinks={...} />`
333
+ * に渡す。プレーンオブジェクトなのでシリアライズ境界(RSC / loader)を越えられる。
334
+ *
335
+ * @example
336
+ * const pageLinks = await buildPageLinkMap(cms);
337
+ * <NotionRenderer blocks={blocks} pageLinks={pageLinks} />;
338
+ */
339
+ declare function buildPageLinkMap(source: PageIndexSource, opts?: BuildPageLinkMapOptions): Promise<PageLinkMap>;
340
+ //#endregion
271
341
  //#region src/retry.d.ts
272
342
  interface RetryConfig {
273
343
  retryOn: number[];
@@ -293,5 +363,5 @@ declare const DEFAULT_RETRY_CONFIG: RetryConfig;
293
363
  */
294
364
  declare function withRetry<T>(fn: () => Promise<T>, config: RetryConfig): Promise<T>;
295
365
  //#endregion
296
- export { type AdjacencyOptions, type BaseContentItem, type BuiltInCMSErrorCode, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchemaProperties, type CMSSources, type CacheAdapter, type CacheAdapterStats, type CacheAreaStats, type CachedItemContent, type CachedItemList, type CachedItemMeta, type CheckResult, type CollectionCacheOps, type CollectionClient, type ContentBlock, type ContentConfig, type ContentResult, type CreateClientOptions, DEFAULT_RATE_LIMITER, DEFAULT_RETRY_CONFIG, type DataSource, type DocumentCacheOps, type FindOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheOps, type ImageRef, type InlineNode, type InvalidateKind, type InvalidateScope, type ItemWithContent, type ListOptions, type LogContext, type LogLevel, type Logger, type MaybePromise, type MemoryCacheOptions, type NodePresetOptions, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type RetryConfig, type SWRConfig, type SortOption, type StorageBinary, type StrictCollectionDef, type WarmOptions, type WarmResult, type WebhookConfig, createClient, createHandler, defineCollection, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, nodePreset, noopDocOps, noopImgOps, sha256Hex, withRetry };
366
+ export { type AdjacencyOptions, type BaseContentItem, type BuildPageIndexOptions, type BuildPageLinkMapOptions, type BuiltInCMSErrorCode, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchemaProperties, type CMSSources, type CacheAdapter, type CacheAdapterStats, type CacheAreaStats, type CachedItemContent, type CachedItemList, type CachedItemMeta, type CheckResult, type CollectionCacheOps, type CollectionClient, type ContentBlock, type ContentConfig, type ContentResult, type CreateClientOptions, DEFAULT_RATE_LIMITER, DEFAULT_RETRY_CONFIG, type DataSource, type DocumentCacheOps, type FindOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheOps, type ImageRef, type InlineNode, type InvalidateKind, type InvalidateScope, type ItemWithContent, type ListOptions, type LogContext, type LogLevel, type Logger, type MaybePromise, type MemoryCacheOptions, type NodePresetOptions, type PageIndex, type PageIndexEntry, type PageIndexSource, type PageLinkMap, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type ResolvedPageLink, type RetryConfig, type SWRConfig, type SortOption, type StorageBinary, type StrictCollectionDef, type WarmOptions, type WarmResult, type WebhookConfig, buildPageIndex, buildPageLinkMap, createClient, createHandler, defineCollection, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, nodePreset, noopDocOps, noopImgOps, normalizePageId, sha256Hex, withRetry };
297
367
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1131,6 +1131,69 @@ function createClient(opts) {
1131
1131
  return Object.assign(Object.create(null), collections, globalOps);
1132
1132
  }
1133
1133
  //#endregion
1134
+ //#region src/page-index.ts
1135
+ /**
1136
+ * Notion ページ ID を比較用に正規化する。
1137
+ * Notion の ID は文脈によりダッシュの有無・大文字小文字が揺れるため、
1138
+ * ダッシュ除去 + 小文字化して突き合わせる。
1139
+ *
1140
+ * react-renderer 側も同一実装で `pageLinks` を引くため、変更時は両方を揃えること。
1141
+ */
1142
+ function normalizePageId(id) {
1143
+ return id.replace(/-/g, "").toLowerCase();
1144
+ }
1145
+ function asCollectionClient(source, name) {
1146
+ const client = source[name];
1147
+ if (!client || typeof client.list !== "function") return void 0;
1148
+ return client;
1149
+ }
1150
+ /**
1151
+ * 全コレクションを `list()` で走査し、pageId → {collection, slug, title} の逆引きマップを構築する。
1152
+ * Notion 内部リンク(link_to_page / page mention など)を自サイト URL へ解決するための材料。
1153
+ *
1154
+ * `list()` は SWR ドキュメントキャッシュ経由のためウォーム後は安価。
1155
+ */
1156
+ async function buildPageIndex(source, opts) {
1157
+ const names = opts?.collections ?? source.collections;
1158
+ const index = /* @__PURE__ */ new Map();
1159
+ if (!names || typeof names[Symbol.iterator] !== "function") return index;
1160
+ for (const name of names) {
1161
+ const client = asCollectionClient(source, name);
1162
+ if (!client) continue;
1163
+ const items = await client.list();
1164
+ if (!Array.isArray(items)) continue;
1165
+ for (const item of items) {
1166
+ const key = normalizePageId(item.id);
1167
+ if (!index.has(key)) index.set(key, {
1168
+ collection: name,
1169
+ slug: item.slug,
1170
+ title: item.title
1171
+ });
1172
+ }
1173
+ }
1174
+ return index;
1175
+ }
1176
+ const defaultUrl = (entry) => `/${entry.collection}/${entry.slug}`;
1177
+ /**
1178
+ * Notion 内部リンクを「正規化 pageId → {href, title}」のプレーンマップに解決する。
1179
+ * サーバ側(loader / RSC / route handler)で 1 回構築し、`<NotionRenderer pageLinks={...} />`
1180
+ * に渡す。プレーンオブジェクトなのでシリアライズ境界(RSC / loader)を越えられる。
1181
+ *
1182
+ * @example
1183
+ * const pageLinks = await buildPageLinkMap(cms);
1184
+ * <NotionRenderer blocks={blocks} pageLinks={pageLinks} />;
1185
+ */
1186
+ async function buildPageLinkMap(source, opts) {
1187
+ const index = opts?.index ?? await buildPageIndex(source, opts);
1188
+ const toUrl = opts?.url ?? defaultUrl;
1189
+ const map = {};
1190
+ for (const [key, entry] of index) map[key] = {
1191
+ href: toUrl(entry, key),
1192
+ title: entry.title
1193
+ };
1194
+ return map;
1195
+ }
1196
+ //#endregion
1134
1197
  //#region src/types/config.ts
1135
1198
  /**
1136
1199
  * `RateLimiterConfig` のデフォルト値 (Issue #313 / M2)。
@@ -1173,6 +1236,6 @@ function definePlugin(plugin) {
1173
1236
  return plugin;
1174
1237
  }
1175
1238
  //#endregion
1176
- export { CMSError, DEFAULT_RATE_LIMITER, DEFAULT_RETRY_CONFIG, createClient, createHandler, defineCollection, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, nodePreset, noopDocOps, noopImgOps, sha256Hex, withRetry };
1239
+ export { CMSError, DEFAULT_RATE_LIMITER, DEFAULT_RETRY_CONFIG, buildPageIndex, buildPageLinkMap, createClient, createHandler, defineCollection, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, nodePreset, noopDocOps, noopImgOps, normalizePageId, sha256Hex, withRetry };
1177
1240
 
1178
1241
  //# sourceMappingURL=index.mjs.map
@@ -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/config.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/markdown-html` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/markdown-html\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/markdown-html のロードにも失敗しました。\" +\n \" createClient の renderer オプションを指定するか、@notion-headless-cms/markdown-html をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n // markdown 戦略のように loadNotionBlocks 未対応の場合は `source/blocks_unsupported`\n // を吸収して undefined にする (ページ描画は markdown 経路で代替する想定)。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err) && err.is(\"source/blocks_unsupported\")) {\n notionBlocks = undefined;\n } else if (isCMSError(err)) {\n throw err;\n } else {\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n /**\n * リトライ前に呼ばれるフック。`attempt` は 1 始まり、`status` はリトライ対象の HTTP ステータス、\n * `delayMs` は次回試行までの実際の待機時間 (ジッター反映後)。\n * 既存呼び出しは `attempt` / `status` だけで動くよう `delayMs` は省略可。\n */\n onRetry?: (attempt: number, status: number, delayMs?: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n // onRetry には attempt / status と次回待機時間 (ms) を渡す。\n // 呼び出し側のロガーが `LogContext.backoffMs` として記録できる。\n config.onRetry?.(attempt + 1, status, delay);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する (item: `{collection}:{slug}` / list: `{collection}`)。\n *\n * 各 cache adapter は内部で独自のキー戦略を持つが、ログ出力や差分再計算で\n * 同一表現が必要になるため core 側にも公開する。\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名。`source.properties[slugField].notion` を\n * Notion プロパティ名として `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n // TTL 切れはブロッキングで再取得する (stale を返さない要件)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: 即時返却しつつバックグラウンドで差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n // 保存だけはバックグラウンド可: ユーザー向けレスポンスを早めに返す\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async peekVersion(\n slug: string,\n ): Promise<{ notionUpdatedAt: string; cachedAt: number } | null> {\n const meta = await this.ctx.docCache.getMeta<T>(this.ctx.collection, slug);\n if (!meta) return null;\n return { notionUpdatedAt: meta.notionUpdatedAt, cachedAt: meta.cachedAt };\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /** 本文キャッシュ。メタとの整合 (`notionUpdatedAt`) が崩れていれば再生成して書き戻す。 */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n if (cached && cached.notionUpdatedAt === expected) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけ再生成する。エラーは onSwrError フックに通知して握り潰す。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // html() / markdown() / blocks() を同じアイテムから複数回呼んでも I/O は 1 回に集約する\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => {\n const notionBlocks = (await loadPayload()).notionBlocks;\n // markdown 戦略やフェッチャ未設定だと常に undefined になり、\n // React レンダリング側で原因が分からない無言失敗になるため一度だけ案内する。\n if (notionBlocks === undefined) this.warnMissingNotionBlocks();\n return notionBlocks;\n },\n }) as ItemWithContent<T>;\n }\n\n // notionBlocks() が undefined を返したときの案内を CollectionClient 単位で一度だけ出す。\n private notionBlocksWarned = false;\n\n private warnMissingNotionBlocks(): void {\n if (this.notionBlocksWarned) return;\n this.notionBlocksWarned = true;\n this.ctx.logger?.warn?.(\n \"notionBlocks() が undefined を返しました。BlockObjectResponse ツリーは blocks 戦略でのみ得られます (notionSource の fetch 未指定の既定でも有効)。markdownFetcher を使用中の場合は markdown→React の Renderer を使うか、blocksFetcher() に切り替えてください。\",\n { collection: this.ctx.collection, operation: \"notionBlocks\" },\n );\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n // TTL 切れはブロッキングで再取得する (find と同じ理由)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else {\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、cachedAt を更新\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status, delayMs) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", {\n attempt,\n status,\n backoffMs: delayMs,\n });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number, delayMs?: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n backoffMs: delayMs,\n });\n },\n };\n\n // PropertyMap が解決できる場合は単一プロパティ filter で 1 ページだけ取得する (高速)\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // PropertyMap 未提供 / findByProp 未実装の DataSource 向けフォールバック\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順 (未設定なら lastEditedTime 降順) でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/** Webhook 系の CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers, withTraceId } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CacheAdapterStats,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateClientOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\nimport type {\n CMSAdapter,\n CMSSources,\n MergeSourceCollections,\n} from \"./types/sources\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/**\n * `cms.stats()` が返す集約済みキャッシュ統計。\n * 各 adapter の `stats()` 戻り値をそのまま配列で保持しつつ、ヒット率を算出する。\n */\nexport interface CMSStats {\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n traceId: string;\n /** ドキュメントキャッシュの集計 (`handles: [\"document\"]` の adapter の `stats()` 戻り値)。 */\n document?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n /** 0〜1。`hits + misses === 0` のときは 0。 */\n hitRate: number;\n };\n /** 画像キャッシュの集計 (`handles: [\"image\"]` の adapter の `stats()` 戻り値)。 */\n image?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n hitRate: number;\n };\n}\n\nexport interface CMSGlobalOps {\n readonly collections: readonly string[];\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n readonly traceId: string;\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard な Request/Response ベースのルートハンドラ (画像プロキシ + webhook)。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n readonly imageProxyBase: string;\n /**\n * ドキュメント / 画像キャッシュのヒット・ミス・サイズを集約して返す。\n * 各キャッシュアダプタの `stats()` を呼ぶだけで副作用はない。\n * `stats()` を実装していない adapter は集計から除外される (noop など)。\n */\n stats(): Promise<CMSStats>;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n docAdapter: CacheAdapter | undefined;\n img: ImageCacheOps;\n imgName: string;\n imgAdapter: CacheAdapter | undefined;\n hasImg: boolean;\n}\n\n/**\n * adapter の `handles` を見て先勝ちで document / image を割り当てる。未指定は両方 noop。\n * `cms.stats()` から元 adapter の `stats()` を呼びたいので、解決元 adapter も保持する。\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let docAdapter: CacheAdapter | undefined;\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let imgAdapter: CacheAdapter | undefined;\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docAdapter = adapter;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgAdapter = adapter;\n imgFound = true;\n }\n }\n\n return {\n doc,\n docName,\n docAdapter,\n img,\n imgName,\n imgAdapter,\n hasImg: imgFound,\n };\n}\n\n/**\n * 衝突しにくい短い trace ID を生成する。`{epoch36}-{rand36}` の 10〜12 文字程度。\n * core はゼロ依存ルールに従い node:crypto を静的 import しないため、Math.random ベースで十分。\n */\nfunction generateTraceId(): string {\n const t = Date.now().toString(36);\n const r = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, \"0\");\n return `${t}-${r}`;\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * CMS クライアントを生成する。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n *\n * const posts = await cms.posts.list();\n */\nexport function createClient<S extends CMSSources = CMSSources>(\n opts: CreateClientOptions<S>,\n): CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n> {\n // sources の各アダプタが持つ collections をマージする\n const collectionsInput: CollectionsConfig = {};\n if (opts.sources) {\n for (const adapter of Object.values(\n opts.sources as unknown as Record<string, CMSAdapter | undefined>,\n )) {\n if (adapter) Object.assign(collectionsInput, adapter.collections);\n }\n }\n\n if (Object.keys(collectionsInput).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createClient: sources に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createClient\" },\n nextSteps: [\n \"notionSource({ schema, token }) を sources.notion に渡す\",\n \"`nhc generate` でスキーマを生成してから import する\",\n ],\n docsUrl:\n \"https://github.com/kjfsm/notion-headless-cms/blob/main/docs/quickstart.md\",\n });\n }\n\n for (const [name, def] of Object.entries(collectionsInput)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\"notionSource(...) を sources に渡しているか確認する\"],\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\n `nhc.config.ts の ${name} コレクションに slugField を設定する`,\n ],\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n // plugin logger と createClient 引数の logger を合成し、その上でクライアント単位の\n // traceId をログコンテキストに自動付与する。\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const traceId = generateTraceId();\n const tracedLogger = withTraceId(baseLogger, traceId);\n const logger = opts.logLevel\n ? applyLogLevel(tracedLogger, opts.logLevel)\n : tracedLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: string[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(collectionsInput)) {\n collectionNames.push(name);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n traceId,\n async stats(): Promise<CMSStats> {\n const stats: CMSStats = { traceId };\n // doc / img それぞれ、resolveCache が選んだ adapter にだけ stats() を要求する。\n // 同じ adapter が両方を担当している場合 (memoryCache など) は 1 回呼ぶだけで足りる。\n const adapterCache = new Map<CacheAdapter, Promise<CacheAdapterStats>>();\n const ensure = (\n adapter: CacheAdapter | undefined,\n ): Promise<CacheAdapterStats> | undefined => {\n if (!adapter?.stats) return undefined;\n const cached = adapterCache.get(adapter);\n if (cached) return cached;\n const fresh = adapter.stats();\n adapterCache.set(adapter, fresh);\n return fresh;\n };\n const docPromise = ensure(cacheRes.docAdapter);\n const imgPromise = ensure(cacheRes.imgAdapter);\n const computeHitRate = (h: number, m: number): number =>\n h + m === 0 ? 0 : h / (h + m);\n if (docPromise) {\n const docStats = await docPromise;\n if (docStats.doc) {\n stats.document = {\n adapter: docStats.name ?? cacheRes.docName,\n hits: docStats.doc.hits,\n misses: docStats.doc.misses,\n entries: docStats.doc.entries,\n sizeBytes: docStats.doc.sizeBytes,\n hitRate: computeHitRate(docStats.doc.hits, docStats.doc.misses),\n };\n }\n }\n if (imgPromise) {\n const imgStats = await imgPromise;\n if (imgStats.img) {\n stats.image = {\n adapter: imgStats.name ?? cacheRes.imgName,\n hits: imgStats.img.hits,\n misses: imgStats.img.misses,\n entries: imgStats.img.entries,\n sizeBytes: imgStats.img.sizeBytes,\n hitRate: computeHitRate(imgStats.img.hits, imgStats.img.misses),\n };\n }\n }\n return stats;\n },\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = collectionsInput[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n >;\n}\n","import type { CacheAdapter } from \"./cache\";\nimport type { BaseContentItem } from \"./content\";\nimport type { DataSource } from \"./data-source\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\nimport type { CMSPlugin } from \"./plugin\";\nimport type { CMSSources } from \"./sources\";\n\n/** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/**\n * renderer プラグインの不透明型。\n * core は unified / remark / rehype に依存せず、このリストをそのまま renderer に渡すだけ。\n */\nexport type RendererPluginList = unknown[];\n\n/**\n * render() オプション。core は renderer の実装を知らず、この型だけを扱う。\n * @notion-headless-cms/markdown-html の renderMarkdown() はこのシグネチャと構造的に互換。\n */\nexport interface RenderOptions {\n imageProxyBase?: string;\n cacheImage?: (url: string) => Promise<string>;\n remarkPlugins?: RendererPluginList;\n rehypePlugins?: RendererPluginList;\n}\n\n/** カスタムレンダラー関数の型。デフォルトは @notion-headless-cms/markdown-html の renderMarkdown。 */\nexport type RendererFn = (\n markdown: string,\n opts?: RenderOptions,\n) => Promise<string>;\n\n/** レンダリング・コンテンツ処理設定。 */\nexport interface ContentConfig {\n /** 追加する remark プラグイン。 */\n remarkPlugins?: RendererPluginList;\n /** 追加する rehype プラグイン。 */\n rehypePlugins?: RendererPluginList;\n}\n\n/** SWR(Stale-While-Revalidate)設定。 */\nexport interface SWRConfig {\n /** SWR の有効期間 (ミリ秒)。未設定時は TTL なし(失効まで stale を返す)。 */\n ttlMs?: number;\n}\n\n/**\n * `RateLimiterConfig` のデフォルト値 (Issue #313 / M2)。\n * 型からだけでは見えない既定値を表面化することで、IDE 補完と\n * preset の対称化 (`nodePreset` / `cloudflarePreset` / `nextPreset`) で\n * 同じ既定が適用されることを保証する。\n */\nexport const DEFAULT_RATE_LIMITER: Required<RateLimiterConfig> = {\n maxConcurrent: 3,\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n};\n\n/** レートリミット・リトライ設定。既定値は {@link DEFAULT_RATE_LIMITER}。 */\nexport interface RateLimiterConfig {\n /** 同時実行数の上限。デフォルト: 3 */\n maxConcurrent?: number;\n /** リトライ対象の HTTP ステータスコード。デフォルト: [429, 502, 503] */\n retryOn?: number[];\n /** 最大リトライ回数。デフォルト: 4 */\n maxRetries?: number;\n /** リトライ時の基準待機時間(ミリ秒)。デフォルト: 1000 */\n baseDelayMs?: number;\n}\n\n/**\n * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createClient` に渡される。\n *\n * `source` は notion-orm 等の DataSource 実装。\n * `slugField` / `statusField` は TS フィールド名 (DataSource の `properties` キーと一致)。\n */\nexport interface CollectionDef<T extends BaseContentItem = BaseContentItem> {\n /** Notion etc. のデータソース実装。 */\n source: DataSource<T>;\n /** slug として使う TS フィールド名 (必須)。`source.properties[slugField]` で Notion プロパティ名を解決する。 */\n slugField: string;\n /** ステータスとして使う TS フィールド名。 */\n statusField?: string;\n /** 公開扱いするステータス値。`list()` のデフォルト絞り込みに使う。 */\n publishedStatuses?: readonly string[];\n /** アクセス許可するステータス値。`get()` の閲覧可否判定に使う。 */\n accessibleStatuses?: readonly string[];\n /** コレクション固有のライフサイクルフック。グローバル hooks の後に実行される。 */\n hooks?: CMSHooks<T>;\n}\n\n/**\n * `CollectionDef` の strict 版。`slugField` / `statusField` が `keyof T & string` で\n * 型ガードされており、誤フィールド名は型エラーになる (Issue #314 / M3)。\n * CLI 生成スキーマや `defineCollection<T>()` 経由で利用される。\n */\nexport interface StrictCollectionDef<T extends BaseContentItem>\n extends Omit<CollectionDef<T>, \"slugField\" | \"statusField\"> {\n /** slug として使う TS フィールド名。`keyof T` で型ガードされる。 */\n slugField: keyof T & string;\n /** ステータスとして使う TS フィールド名。`keyof T` で型ガードされる。 */\n statusField?: keyof T & string;\n}\n\n/**\n * 型推論ヘルパー: `T` を明示してコレクション定義を作る。`slugField` / `statusField` は\n * `keyof T & string` で補完・型ガードされ、誤フィールド名 (例: `\"slag\"`) で型エラーになる\n * (Issue #314 / M3)。CLI 生成 `nhc.schema.ts` で利用される。\n *\n * @example\n * ```ts\n * type PostItem = BaseContentItem & { authorName?: string };\n * const posts = defineCollection<PostItem>({\n * source: notionSource(...),\n * slugField: \"slug\", // OK\n * statusField: \"status\", // OK\n * // statusField: \"stat\", // 型エラー\n * });\n * ```\n */\nexport function defineCollection<T extends BaseContentItem>(\n def: StrictCollectionDef<T>,\n): CollectionDef<T> {\n // StrictCollectionDef は CollectionDef の slugField/statusField を絞ったサブ型なので\n // 構造的に CollectionDef<T> へ代入可能。型システム上は再構成のため as 経由。\n return def as unknown as CollectionDef<T>;\n}\n\n/**\n * `createClient({ collections })` の map 型。\n * キーがコレクション名、値が `CollectionDef<T>`。\n */\nexport type CollectionsConfig = Record<string, CollectionDef<BaseContentItem>>;\n\n/** `CollectionsConfig` から各 T を抽出するユーティリティ型。 */\nexport type InferCollectionItem<C> =\n C extends CollectionDef<infer T> ? T : BaseContentItem;\n\n/**\n * `createClient()` の入力。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n */\nexport interface CreateClientOptions<S extends CMSSources = CMSSources> {\n /** データソースアダプター (`@notion-headless-cms/notion-source` 等) のマップ。 */\n sources?: S;\n /**\n * キャッシュアダプタ (配列)。未指定時はキャッシュなし。\n * - `memoryCache()` のように doc + image 両方を担当するもの\n * - `r2Cache()` (image のみ)、`kvCache()` (doc のみ) のように片側のみ担当するもの\n * - 複数 adapter を配列で組み合わせると、各 adapter の `handles` で振り分けられる\n */\n cache?: readonly CacheAdapter[];\n /** SWR(Stale-While-Revalidate)設定。 */\n swr?: SWRConfig;\n /**\n * Markdown→HTML レンダラー。\n * 省略時は `@notion-headless-cms/markdown-html` の `renderMarkdown` を動的 import で使用する。\n * カスタム実装も `RendererFn` 型を満たせば使用可能。\n */\n renderer?: RendererFn;\n /** 画像プロキシのベース URL。デフォルト `/api/images`。 */\n imageProxyBase?: string;\n /** Cloudflare Workers の `waitUntil` に相当する非同期処理の登録関数。 */\n waitUntil?: (p: Promise<unknown>) => void;\n /** ライフサイクルフック (全コレクション共通)。 */\n hooks?: CMSHooks<BaseContentItem>;\n /** プラグイン配列。 */\n plugins?: CMSPlugin<BaseContentItem>[];\n /** ロガー。 */\n logger?: Logger;\n /** ログレベルの下限。指定したレベル未満のログを内部で抑制する。 */\n logLevel?: LogLevel;\n /** レートリミット・リトライ設定。 */\n rateLimiter?: RateLimiterConfig;\n /** レンダリング・コンテンツ処理設定。 */\n content?: ContentConfig;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/cache/noop.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/cms.ts","../src/page-index.ts","../src/types/config.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/markdown-html` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/markdown-html\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/markdown-html のロードにも失敗しました。\" +\n \" createClient の renderer オプションを指定するか、@notion-headless-cms/markdown-html をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n // markdown 戦略のように loadNotionBlocks 未対応の場合は `source/blocks_unsupported`\n // を吸収して undefined にする (ページ描画は markdown 経路で代替する想定)。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err) && err.is(\"source/blocks_unsupported\")) {\n notionBlocks = undefined;\n } else if (isCMSError(err)) {\n throw err;\n } else {\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n /**\n * リトライ前に呼ばれるフック。`attempt` は 1 始まり、`status` はリトライ対象の HTTP ステータス、\n * `delayMs` は次回試行までの実際の待機時間 (ジッター反映後)。\n * 既存呼び出しは `attempt` / `status` だけで動くよう `delayMs` は省略可。\n */\n onRetry?: (attempt: number, status: number, delayMs?: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n // onRetry には attempt / status と次回待機時間 (ms) を渡す。\n // 呼び出し側のロガーが `LogContext.backoffMs` として記録できる。\n config.onRetry?.(attempt + 1, status, delay);\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する (item: `{collection}:{slug}` / list: `{collection}`)。\n *\n * 各 cache adapter は内部で独自のキー戦略を持つが、ログ出力や差分再計算で\n * 同一表現が必要になるため core 側にも公開する。\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名。`source.properties[slugField].notion` を\n * Notion プロパティ名として `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n // TTL 切れはブロッキングで再取得する (stale を返さない要件)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: 即時返却しつつバックグラウンドで差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n // 保存だけはバックグラウンド可: ユーザー向けレスポンスを早めに返す\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async peekVersion(\n slug: string,\n ): Promise<{ notionUpdatedAt: string; cachedAt: number } | null> {\n const meta = await this.ctx.docCache.getMeta<T>(this.ctx.collection, slug);\n if (!meta) return null;\n return { notionUpdatedAt: meta.notionUpdatedAt, cachedAt: meta.cachedAt };\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /** 本文キャッシュ。メタとの整合 (`notionUpdatedAt`) が崩れていれば再生成して書き戻す。 */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n if (cached && cached.notionUpdatedAt === expected) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけ再生成する。エラーは onSwrError フックに通知して握り潰す。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // html() / markdown() / blocks() を同じアイテムから複数回呼んでも I/O は 1 回に集約する\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => {\n const notionBlocks = (await loadPayload()).notionBlocks;\n // markdown 戦略やフェッチャ未設定だと常に undefined になり、\n // React レンダリング側で原因が分からない無言失敗になるため一度だけ案内する。\n if (notionBlocks === undefined) this.warnMissingNotionBlocks();\n return notionBlocks;\n },\n }) as ItemWithContent<T>;\n }\n\n // notionBlocks() が undefined を返したときの案内を CollectionClient 単位で一度だけ出す。\n private notionBlocksWarned = false;\n\n private warnMissingNotionBlocks(): void {\n if (this.notionBlocksWarned) return;\n this.notionBlocksWarned = true;\n this.ctx.logger?.warn?.(\n \"notionBlocks() が undefined を返しました。BlockObjectResponse ツリーは blocks 戦略でのみ得られます (notionSource の fetch 未指定の既定でも有効)。markdownFetcher を使用中の場合は markdown→React の Renderer を使うか、blocksFetcher() に切り替えてください。\",\n { collection: this.ctx.collection, operation: \"notionBlocks\" },\n );\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n // TTL 切れはブロッキングで再取得する (find と同じ理由)\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else {\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、cachedAt を更新\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status, delayMs) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", {\n attempt,\n status,\n backoffMs: delayMs,\n });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number, delayMs?: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n backoffMs: delayMs,\n });\n },\n };\n\n // PropertyMap が解決できる場合は単一プロパティ filter で 1 ページだけ取得する (高速)\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // PropertyMap 未提供 / findByProp 未実装の DataSource 向けフォールバック\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順 (未設定なら lastEditedTime 降順) でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/** Webhook 系の CMSError コードを HTTP ステータスへ写像する。未対応コードは null。 */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers, withTraceId } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CacheAdapterStats,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateClientOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\nimport type {\n CMSAdapter,\n CMSSources,\n MergeSourceCollections,\n} from \"./types/sources\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/**\n * `cms.stats()` が返す集約済みキャッシュ統計。\n * 各 adapter の `stats()` 戻り値をそのまま配列で保持しつつ、ヒット率を算出する。\n */\nexport interface CMSStats {\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n traceId: string;\n /** ドキュメントキャッシュの集計 (`handles: [\"document\"]` の adapter の `stats()` 戻り値)。 */\n document?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n /** 0〜1。`hits + misses === 0` のときは 0。 */\n hitRate: number;\n };\n /** 画像キャッシュの集計 (`handles: [\"image\"]` の adapter の `stats()` 戻り値)。 */\n image?: {\n adapter: string;\n hits: number;\n misses: number;\n entries?: number;\n sizeBytes?: number;\n hitRate: number;\n };\n}\n\nexport interface CMSGlobalOps {\n readonly collections: readonly string[];\n /** クライアント単位の trace ID (`createClient` で発行)。 */\n readonly traceId: string;\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard な Request/Response ベースのルートハンドラ (画像プロキシ + webhook)。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n readonly imageProxyBase: string;\n /**\n * ドキュメント / 画像キャッシュのヒット・ミス・サイズを集約して返す。\n * 各キャッシュアダプタの `stats()` を呼ぶだけで副作用はない。\n * `stats()` を実装していない adapter は集計から除外される (noop など)。\n */\n stats(): Promise<CMSStats>;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n docAdapter: CacheAdapter | undefined;\n img: ImageCacheOps;\n imgName: string;\n imgAdapter: CacheAdapter | undefined;\n hasImg: boolean;\n}\n\n/**\n * adapter の `handles` を見て先勝ちで document / image を割り当てる。未指定は両方 noop。\n * `cms.stats()` から元 adapter の `stats()` を呼びたいので、解決元 adapter も保持する。\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let docAdapter: CacheAdapter | undefined;\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let imgAdapter: CacheAdapter | undefined;\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docAdapter = adapter;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgAdapter = adapter;\n imgFound = true;\n }\n }\n\n return {\n doc,\n docName,\n docAdapter,\n img,\n imgName,\n imgAdapter,\n hasImg: imgFound,\n };\n}\n\n/**\n * 衝突しにくい短い trace ID を生成する。`{epoch36}-{rand36}` の 10〜12 文字程度。\n * core はゼロ依存ルールに従い node:crypto を静的 import しないため、Math.random ベースで十分。\n */\nfunction generateTraceId(): string {\n const t = Date.now().toString(36);\n const r = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, \"0\");\n return `${t}-${r}`;\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * CMS クライアントを生成する。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n *\n * const posts = await cms.posts.list();\n */\nexport function createClient<S extends CMSSources = CMSSources>(\n opts: CreateClientOptions<S>,\n): CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n> {\n // sources の各アダプタが持つ collections をマージする\n const collectionsInput: CollectionsConfig = {};\n if (opts.sources) {\n for (const adapter of Object.values(\n opts.sources as unknown as Record<string, CMSAdapter | undefined>,\n )) {\n if (adapter) Object.assign(collectionsInput, adapter.collections);\n }\n }\n\n if (Object.keys(collectionsInput).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createClient: sources に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createClient\" },\n nextSteps: [\n \"notionSource({ schema, token }) を sources.notion に渡す\",\n \"`nhc generate` でスキーマを生成してから import する\",\n ],\n docsUrl:\n \"https://github.com/kjfsm/notion-headless-cms/blob/main/docs/quickstart.md\",\n });\n }\n\n for (const [name, def] of Object.entries(collectionsInput)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\"notionSource(...) を sources に渡しているか確認する\"],\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createClient\", collection: name },\n nextSteps: [\n `nhc.config.ts の ${name} コレクションに slugField を設定する`,\n ],\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n // plugin logger と createClient 引数の logger を合成し、その上でクライアント単位の\n // traceId をログコンテキストに自動付与する。\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const traceId = generateTraceId();\n const tracedLogger = withTraceId(baseLogger, traceId);\n const logger = opts.logLevel\n ? applyLogLevel(tracedLogger, opts.logLevel)\n : tracedLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: string[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(collectionsInput)) {\n collectionNames.push(name);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n traceId,\n async stats(): Promise<CMSStats> {\n const stats: CMSStats = { traceId };\n // doc / img それぞれ、resolveCache が選んだ adapter にだけ stats() を要求する。\n // 同じ adapter が両方を担当している場合 (memoryCache など) は 1 回呼ぶだけで足りる。\n const adapterCache = new Map<CacheAdapter, Promise<CacheAdapterStats>>();\n const ensure = (\n adapter: CacheAdapter | undefined,\n ): Promise<CacheAdapterStats> | undefined => {\n if (!adapter?.stats) return undefined;\n const cached = adapterCache.get(adapter);\n if (cached) return cached;\n const fresh = adapter.stats();\n adapterCache.set(adapter, fresh);\n return fresh;\n };\n const docPromise = ensure(cacheRes.docAdapter);\n const imgPromise = ensure(cacheRes.imgAdapter);\n const computeHitRate = (h: number, m: number): number =>\n h + m === 0 ? 0 : h / (h + m);\n if (docPromise) {\n const docStats = await docPromise;\n if (docStats.doc) {\n stats.document = {\n adapter: docStats.name ?? cacheRes.docName,\n hits: docStats.doc.hits,\n misses: docStats.doc.misses,\n entries: docStats.doc.entries,\n sizeBytes: docStats.doc.sizeBytes,\n hitRate: computeHitRate(docStats.doc.hits, docStats.doc.misses),\n };\n }\n }\n if (imgPromise) {\n const imgStats = await imgPromise;\n if (imgStats.img) {\n stats.image = {\n adapter: imgStats.name ?? cacheRes.imgName,\n hits: imgStats.img.hits,\n misses: imgStats.img.misses,\n entries: imgStats.img.entries,\n sizeBytes: imgStats.img.sizeBytes,\n hitRate: computeHitRate(imgStats.img.hits, imgStats.img.misses),\n };\n }\n }\n return stats;\n },\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = collectionsInput[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : CollectionsConfig\n >;\n}\n","import type { CollectionClient } from \"./types/collection\";\nimport type { BaseContentItem } from \"./types/content\";\n\n/** 逆引きの解決先。Notion ページ ID から自サイトの位置を引く。 */\nexport interface PageIndexEntry {\n /** 所属コレクション名。 */\n collection: string;\n /** URL キー。 */\n slug: string;\n /** ページ名(表示テキスト用)。 */\n title?: string | null;\n}\n\n/** `buildPageIndex` が返す逆引きマップ。キーは `normalizePageId` 済みの pageId。 */\nexport type PageIndex = Map<string, PageIndexEntry>;\n\n/**\n * `buildPageIndex` / `buildPageLinkMap` が必要とする最小の CMS 形状。\n * `collections` でコレクション名を列挙し、各名でコレクションクライアントへアクセスする。\n * `CMSClient` はコレクションを自身に spread しているため構造的に満たす。\n */\nexport interface PageIndexSource {\n readonly collections: readonly string[];\n}\n\nexport interface BuildPageIndexOptions {\n /** 走査対象のコレクション名。未指定なら `source.collections` 全件。 */\n collections?: readonly string[];\n}\n\n/**\n * Notion ページ ID を比較用に正規化する。\n * Notion の ID は文脈によりダッシュの有無・大文字小文字が揺れるため、\n * ダッシュ除去 + 小文字化して突き合わせる。\n *\n * react-renderer 側も同一実装で `pageLinks` を引くため、変更時は両方を揃えること。\n */\nexport function normalizePageId(id: string): string {\n return id.replace(/-/g, \"\").toLowerCase();\n}\n\nfunction asCollectionClient(\n source: PageIndexSource,\n name: string,\n): CollectionClient<BaseContentItem> | undefined {\n const client = (source as unknown as Record<string, unknown>)[name] as\n | CollectionClient<BaseContentItem>\n | undefined;\n // collections に名前はあっても spread されていない / list を持たない値は無視する。\n if (!client || typeof client.list !== \"function\") return undefined;\n return client;\n}\n\n/**\n * 全コレクションを `list()` で走査し、pageId → {collection, slug, title} の逆引きマップを構築する。\n * Notion 内部リンク(link_to_page / page mention など)を自サイト URL へ解決するための材料。\n *\n * `list()` は SWR ドキュメントキャッシュ経由のためウォーム後は安価。\n */\nexport async function buildPageIndex(\n source: PageIndexSource,\n opts?: BuildPageIndexOptions,\n): Promise<PageIndex> {\n // collections を持たない / 異常な source でもクラッシュさせず空マップを返す。\n const names = opts?.collections ?? source.collections;\n const index: PageIndex = new Map();\n if (\n !names ||\n typeof (names as Iterable<string>)[Symbol.iterator] !== \"function\"\n ) {\n return index;\n }\n for (const name of names) {\n const client = asCollectionClient(source, name);\n if (!client) continue;\n const items = await client.list();\n if (!Array.isArray(items)) continue;\n for (const item of items) {\n // pageId は一意なので衝突しない前提。万一重複しても先勝ちで保持する。\n const key = normalizePageId(item.id);\n if (!index.has(key)) {\n index.set(key, {\n collection: name,\n slug: item.slug,\n title: item.title,\n });\n }\n }\n }\n return index;\n}\n\n/** 解決済みの内部リンク。`href` は構築時に決まり、`title` は表示テキスト用。 */\nexport interface ResolvedPageLink {\n href: string;\n title?: string | null;\n}\n\n/**\n * 正規化済み pageId → 解決済みリンクのプレーンマップ。\n * 関数ではなくプレーンオブジェクトなので、loader / RSC(Server Component → Client\n * Component)境界を越えて `<NotionRenderer pageLinks={...} />` にそのまま渡せる。\n */\nexport type PageLinkMap = Record<string, ResolvedPageLink>;\n\nexport interface BuildPageLinkMapOptions extends BuildPageIndexOptions {\n /**\n * エントリから URL を組み立てる関数。既定は `/${collection}/${slug}`。\n * 単一コレクションで `/${slug}` にしたい場合などに上書きする。\n */\n url?: (entry: PageIndexEntry, pageId: string) => string;\n /** 事前構築済みインデックス。指定するとリクエストごとの再構築を省ける。 */\n index?: PageIndex;\n}\n\nconst defaultUrl = (entry: PageIndexEntry): string =>\n `/${entry.collection}/${entry.slug}`;\n\n/**\n * Notion 内部リンクを「正規化 pageId → {href, title}」のプレーンマップに解決する。\n * サーバ側(loader / RSC / route handler)で 1 回構築し、`<NotionRenderer pageLinks={...} />`\n * に渡す。プレーンオブジェクトなのでシリアライズ境界(RSC / loader)を越えられる。\n *\n * @example\n * const pageLinks = await buildPageLinkMap(cms);\n * <NotionRenderer blocks={blocks} pageLinks={pageLinks} />;\n */\nexport async function buildPageLinkMap(\n source: PageIndexSource,\n opts?: BuildPageLinkMapOptions,\n): Promise<PageLinkMap> {\n const index = opts?.index ?? (await buildPageIndex(source, opts));\n const toUrl = opts?.url ?? defaultUrl;\n const map: PageLinkMap = {};\n for (const [key, entry] of index) {\n map[key] = { href: toUrl(entry, key), title: entry.title };\n }\n return map;\n}\n","import type { CacheAdapter } from \"./cache\";\nimport type { BaseContentItem } from \"./content\";\nimport type { DataSource } from \"./data-source\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\nimport type { CMSPlugin } from \"./plugin\";\nimport type { CMSSources } from \"./sources\";\n\n/** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/**\n * renderer プラグインの不透明型。\n * core は unified / remark / rehype に依存せず、このリストをそのまま renderer に渡すだけ。\n */\nexport type RendererPluginList = unknown[];\n\n/**\n * render() オプション。core は renderer の実装を知らず、この型だけを扱う。\n * @notion-headless-cms/markdown-html の renderMarkdown() はこのシグネチャと構造的に互換。\n */\nexport interface RenderOptions {\n imageProxyBase?: string;\n cacheImage?: (url: string) => Promise<string>;\n remarkPlugins?: RendererPluginList;\n rehypePlugins?: RendererPluginList;\n}\n\n/** カスタムレンダラー関数の型。デフォルトは @notion-headless-cms/markdown-html の renderMarkdown。 */\nexport type RendererFn = (\n markdown: string,\n opts?: RenderOptions,\n) => Promise<string>;\n\n/** レンダリング・コンテンツ処理設定。 */\nexport interface ContentConfig {\n /** 追加する remark プラグイン。 */\n remarkPlugins?: RendererPluginList;\n /** 追加する rehype プラグイン。 */\n rehypePlugins?: RendererPluginList;\n}\n\n/** SWR(Stale-While-Revalidate)設定。 */\nexport interface SWRConfig {\n /** SWR の有効期間 (ミリ秒)。未設定時は TTL なし(失効まで stale を返す)。 */\n ttlMs?: number;\n}\n\n/**\n * `RateLimiterConfig` のデフォルト値 (Issue #313 / M2)。\n * 型からだけでは見えない既定値を表面化することで、IDE 補完と\n * preset の対称化 (`nodePreset` / `cloudflarePreset` / `nextPreset`) で\n * 同じ既定が適用されることを保証する。\n */\nexport const DEFAULT_RATE_LIMITER: Required<RateLimiterConfig> = {\n maxConcurrent: 3,\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n};\n\n/** レートリミット・リトライ設定。既定値は {@link DEFAULT_RATE_LIMITER}。 */\nexport interface RateLimiterConfig {\n /** 同時実行数の上限。デフォルト: 3 */\n maxConcurrent?: number;\n /** リトライ対象の HTTP ステータスコード。デフォルト: [429, 502, 503] */\n retryOn?: number[];\n /** 最大リトライ回数。デフォルト: 4 */\n maxRetries?: number;\n /** リトライ時の基準待機時間(ミリ秒)。デフォルト: 1000 */\n baseDelayMs?: number;\n}\n\n/**\n * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createClient` に渡される。\n *\n * `source` は notion-orm 等の DataSource 実装。\n * `slugField` / `statusField` は TS フィールド名 (DataSource の `properties` キーと一致)。\n */\nexport interface CollectionDef<T extends BaseContentItem = BaseContentItem> {\n /** Notion etc. のデータソース実装。 */\n source: DataSource<T>;\n /** slug として使う TS フィールド名 (必須)。`source.properties[slugField]` で Notion プロパティ名を解決する。 */\n slugField: string;\n /** ステータスとして使う TS フィールド名。 */\n statusField?: string;\n /** 公開扱いするステータス値。`list()` のデフォルト絞り込みに使う。 */\n publishedStatuses?: readonly string[];\n /** アクセス許可するステータス値。`get()` の閲覧可否判定に使う。 */\n accessibleStatuses?: readonly string[];\n /** コレクション固有のライフサイクルフック。グローバル hooks の後に実行される。 */\n hooks?: CMSHooks<T>;\n}\n\n/**\n * `CollectionDef` の strict 版。`slugField` / `statusField` が `keyof T & string` で\n * 型ガードされており、誤フィールド名は型エラーになる (Issue #314 / M3)。\n * CLI 生成スキーマや `defineCollection<T>()` 経由で利用される。\n */\nexport interface StrictCollectionDef<T extends BaseContentItem>\n extends Omit<CollectionDef<T>, \"slugField\" | \"statusField\"> {\n /** slug として使う TS フィールド名。`keyof T` で型ガードされる。 */\n slugField: keyof T & string;\n /** ステータスとして使う TS フィールド名。`keyof T` で型ガードされる。 */\n statusField?: keyof T & string;\n}\n\n/**\n * 型推論ヘルパー: `T` を明示してコレクション定義を作る。`slugField` / `statusField` は\n * `keyof T & string` で補完・型ガードされ、誤フィールド名 (例: `\"slag\"`) で型エラーになる\n * (Issue #314 / M3)。CLI 生成 `nhc.schema.ts` で利用される。\n *\n * @example\n * ```ts\n * type PostItem = BaseContentItem & { authorName?: string };\n * const posts = defineCollection<PostItem>({\n * source: notionSource(...),\n * slugField: \"slug\", // OK\n * statusField: \"status\", // OK\n * // statusField: \"stat\", // 型エラー\n * });\n * ```\n */\nexport function defineCollection<T extends BaseContentItem>(\n def: StrictCollectionDef<T>,\n): CollectionDef<T> {\n // StrictCollectionDef は CollectionDef の slugField/statusField を絞ったサブ型なので\n // 構造的に CollectionDef<T> へ代入可能。型システム上は再構成のため as 経由。\n return def as unknown as CollectionDef<T>;\n}\n\n/**\n * `createClient({ collections })` の map 型。\n * キーがコレクション名、値が `CollectionDef<T>`。\n */\nexport type CollectionsConfig = Record<string, CollectionDef<BaseContentItem>>;\n\n/** `CollectionsConfig` から各 T を抽出するユーティリティ型。 */\nexport type InferCollectionItem<C> =\n C extends CollectionDef<infer T> ? T : BaseContentItem;\n\n/**\n * `createClient()` の入力。\n *\n * @example\n * import { createClient, nodePreset } from \"@notion-headless-cms/core\";\n * import { notionSource } from \"@notion-headless-cms/notion-source\";\n * import { schema } from \"./generated/nhc.schema\";\n *\n * const cms = createClient({\n * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },\n * ...nodePreset(),\n * });\n */\nexport interface CreateClientOptions<S extends CMSSources = CMSSources> {\n /** データソースアダプター (`@notion-headless-cms/notion-source` 等) のマップ。 */\n sources?: S;\n /**\n * キャッシュアダプタ (配列)。未指定時はキャッシュなし。\n * - `memoryCache()` のように doc + image 両方を担当するもの\n * - `r2Cache()` (image のみ)、`kvCache()` (doc のみ) のように片側のみ担当するもの\n * - 複数 adapter を配列で組み合わせると、各 adapter の `handles` で振り分けられる\n */\n cache?: readonly CacheAdapter[];\n /** SWR(Stale-While-Revalidate)設定。 */\n swr?: SWRConfig;\n /**\n * Markdown→HTML レンダラー。\n * 省略時は `@notion-headless-cms/markdown-html` の `renderMarkdown` を動的 import で使用する。\n * カスタム実装も `RendererFn` 型を満たせば使用可能。\n */\n renderer?: RendererFn;\n /** 画像プロキシのベース URL。デフォルト `/api/images`。 */\n imageProxyBase?: string;\n /** Cloudflare Workers の `waitUntil` に相当する非同期処理の登録関数。 */\n waitUntil?: (p: Promise<unknown>) => void;\n /** ライフサイクルフック (全コレクション共通)。 */\n hooks?: CMSHooks<BaseContentItem>;\n /** プラグイン配列。 */\n plugins?: CMSPlugin<BaseContentItem>[];\n /** ロガー。 */\n logger?: Logger;\n /** ログレベルの下限。指定したレベル未満のログを内部で抑制する。 */\n logLevel?: LogLevel;\n /** レートリミット・リトライ設定。 */\n rateLimiter?: RateLimiterConfig;\n /** レンダリング・コンテンツ処理設定。 */\n content?: ContentConfig;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notion-headless-cms/core",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
5
5
  "keywords": [
6
6
  "notion",
@@ -78,6 +78,7 @@
78
78
  "test:coverage": "vitest run --coverage --coverage.reporter=lcov --coverage.reporter=text",
79
79
  "bench": "vitest bench --run",
80
80
  "publint": "publint --strict",
81
- "attw": "attw --pack . --profile esm-only"
81
+ "attw": "attw --pack . --profile esm-only",
82
+ "release:local": "pnpm publish --no-provenance --no-git-checks"
82
83
  }
83
84
  }