@notion-headless-cms/core 0.3.17 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,111 +1,139 @@
1
1
  # @notion-headless-cms/core
2
2
 
3
- CMS エンジン本体。`createCMS` を提供し、SWR / 更新検知 / フック /
4
- リトライ / Web Standard Route Handler を内蔵する。**外部ランタイム依存ゼロ**。
3
+ CMS エンジン本体。`createClient` を提供し、SWR / 更新検知 / フック / リトライ / Web Standard Route Handler を内蔵する。**外部ランタイム依存ゼロ**。
5
4
 
6
5
  ## インストール
7
6
 
8
7
  ```bash
9
- pnpm add @notion-headless-cms/core @notion-headless-cms/notion-orm \
10
- @notion-headless-cms/renderer @notionhq/client zod \
8
+ pnpm add @notion-headless-cms/core @notion-headless-cms/notion-source \
9
+ @notionhq/client zod \
11
10
  unified remark-parse remark-gfm remark-rehype rehype-stringify
12
11
  ```
13
12
 
14
- `core` 自体は依存ゼロだが、実際に Notion を叩くには `notion-orm`、
15
- HTML 化には `renderer` が必要。CLI 生成物 (`nhc-schema.ts`) が
16
- notion-orm を自動参照する。
13
+ `core` 自体は依存ゼロ。実際に Notion を叩くには `@notion-headless-cms/notion-source`(内部で `notion-orm` / `renderer` を持つ)を追加する。CLI 生成物 (`nhc.schema.ts`) は型のみを参照する。
17
14
 
18
15
  ## 基本形
19
16
 
20
17
  ```ts
21
- import { createCMS, nodePreset } from "@notion-headless-cms/core";
22
- import { cmsDataSources } from "./generated/nhc-schema";
23
-
24
- const cms = createCMS({
25
- ...nodePreset({ ttlMs: 5 * 60_000 }),
26
- dataSources: cmsDataSources,
18
+ import { createClient, memoryCache } from "@notion-headless-cms/core";
19
+ import { notionSource } from "@notion-headless-cms/notion-source";
20
+ import { schema } from "./generated/nhc.schema";
21
+
22
+ const cms = createClient({
23
+ sources: {
24
+ notion: notionSource({
25
+ schema,
26
+ token: process.env.NOTION_TOKEN!,
27
+ publishOptions: {
28
+ posts: { publishedStatuses: ["公開済み"] },
29
+ },
30
+ }),
31
+ },
32
+ cache: [memoryCache()],
33
+ swr: { ttlMs: 5 * 60_000 },
27
34
  });
28
35
 
29
- const posts = await cms.posts.getList();
30
- const post = await cms.posts.getItem("my-post");
31
- if (post) console.log(await post.content.html());
36
+ const posts = await cms.posts.list();
37
+ const post = await cms.posts.find("my-post");
38
+ if (post) console.log(await post.render());
32
39
  ```
33
40
 
34
41
  ## 公開 API
35
42
 
36
- ### `createCMS(opts)`
37
- - `dataSources`: CLI が生成する `cmsDataSources` (必須)
38
- - `cache?`: `CacheConfig | "disabled"`
39
- - `renderer?`: `RendererFn` (未指定なら `@notion-headless-cms/renderer` を動的ロード)
40
- - `content?`: `imageProxyBase` 等
41
- - `waitUntil?`: Cloudflare Workers の `ctx.waitUntil`
42
- - `hooks?` / `plugins?` / `logger?` / `rateLimiter?`
43
+ ### `createClient(opts)`
43
44
 
44
- ### `nodePreset(opts?)`
45
- Node.js 向けプリセット。`{ cache: { document: memoryDocumentCache, image: memoryImageCache }, renderer }` を返す。
46
- - `cache?`: `CacheConfig | "disabled"` デフォルトは memory cache
47
- - `ttlMs?`: SWR TTL (ミリ秒)
48
- - `renderer?`: カスタム renderer
45
+ | オプション | 説明 |
46
+ |---|---|
47
+ | `sources` | データソースアダプターのマップ(`{ notion: notionSource(...) }` 等)。複数ソースの `collections` は自動マージされる |
48
+ | `collections` | 直接 `CollectionDef` を渡す低レベル API(通常は `sources` を使う) |
49
+ | `cache` | `readonly CacheAdapter[]` — `memoryCache()` / `cloudflareCache(env)` 等を配列で指定 |
50
+ | `swr` | `{ ttlMs?: number }` — SWR (Stale-While-Revalidate) TTL |
51
+ | `renderer` | `RendererFn` — 未指定なら `@notion-headless-cms/renderer` を動的ロード |
52
+ | `imageProxyBase` | 画像プロキシのベース URL(デフォルト `/api/images`) |
53
+ | `waitUntil` | Cloudflare Workers の `ctx.waitUntil` 相当 |
54
+ | `hooks` / `plugins` / `logger` / `logLevel` / `rateLimiter` / `content` | 高度な設定 |
49
55
 
50
- ### `BaseContentItem` — 全コンテンツの基本型
56
+ ### 拡張可能な `sources`
51
57
 
52
- CLI 生成の `createCMS` ラッパーで返される items は、スキーマで定義されたプロパティに加えて以下の自動フィールドを含む:
58
+ `@notion-headless-cms/core` は空の `CMSSources` インターフェースを公開し、各アダプターパッケージが宣言マージ(module augmentation)でキーを追加する仕組みになっている。Fastify プラグイン型拡張と同じパターン。
53
59
 
54
- - `id: string` — Notion ページ ID
55
- - `slug: string` — コレクション設定の `fields.slug` で指定したプロパティから抽出
56
- - `title?: string | null` Notion `title` 型プロパティ(自動検出)
57
- - `updatedAt: string` — Notion ページの最終編集時刻(ISO-8601、キャッシュ更新判定に使用)
58
- - `lastEditedTime?: string` — Notion の `page.last_edited_time` と同値。常にセットされるシステムフィールド(`updatedAt` と同じ値)
59
- - `status?: string | null` — `fields.status` で指定したプロパティ
60
- - `publishedAt?: string | null` — `fields.publishedAt` で指定したプロパティ
60
+ ```ts
61
+ // アダプターパッケージ側
62
+ import type { CMSAdapter } from "@notion-headless-cms/core";
63
+
64
+ declare module "@notion-headless-cms/core" {
65
+ interface CMSSources {
66
+ contentful?: CMSAdapter;
67
+ }
68
+ }
69
+ ```
70
+
71
+ 利用側は `import { contentfulSource } from "..."` するだけで `sources.contentful` キーが補完候補として現れる。
61
72
 
62
73
  ### `cms.<collection>` の主なメソッド
63
- - `getItem(slug)` — 本文込みで単件取得 (SWR)。返り値は `T & { content: { blocks, html(), markdown() } }`
64
- - `getList(opts?)` — 公開済み一覧 (本文なし)
65
- - `getStaticParams()` / `getStaticPaths()` SSG 用
66
- - `adjacent(slug, opts?)` — 前後記事ナビゲーション
67
- - `revalidate(scope?)` / `prefetch(opts?)` — コレクション別キャッシュ操作
74
+
75
+ - `find(slug)` — 本文込みで単件取得(SWR)
76
+ - `list(opts?)` — 公開済み一覧(本文なし)
77
+ - `params()` — SSG 用 slug 一覧
78
+ - `cache.adjacent(slug, opts?)` — 前後記事ナビゲーション
79
+ - `cache.warm(opts?)` / `cache.invalidate(scope?)` / `cache.checkItem(...)` — キャッシュ管理
68
80
 
69
81
  ### グローバル操作
70
- - `cms.$collections` — コレクション名一覧
71
- - `cms.$revalidate(scope?)` — `"all" | { collection } | { collection, slug }`
72
- - `cms.$handler(opts?)` — Web Standard `(req) => Response` を返す (画像プロキシ + webhook)
73
- - `cms.$getCachedImage(hash)` — 画像キャッシュ直アクセス
74
- - `cms.cacheImage(url)` — Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式に変換しキャッシュへ書き込む。画像キャッシュ未設定なら `undefined`。`@notion-headless-cms/react-renderer/server` の `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う
75
- - `cms.imageProxyBase` — 画像プロキシのベース URL (`createCMS({ imageProxyBase })`、デフォルト `/api/images`)
76
-
77
- ### cache アダプタ
78
- - `memoryDocumentCache({ maxItems? })` — LRU 対応インメモリキャッシュ
79
- - `memoryImageCache({ maxItems?, maxSizeBytes? })`
80
- - `noopDocumentCache()` / `noopImageCache()`
82
+
83
+ - `cms.collections` — 登録コレクション名一覧
84
+ - `cms.invalidate(scope?)` — `"all" | { collection } | { collection, slug }`
85
+ - `cms.handler(opts?)` — Web Standard `(req) => Response`(画像プロキシ + webhook)
86
+ - `cms.getCachedImage(hash)` — 画像キャッシュ直アクセス
87
+ - `cms.cacheImage(url)` — Notion 画像 URL `{imageProxyBase}/{sha256}` 形式に変換し、永続キャッシュへ書き込む(画像キャッシュ未設定なら `undefined`)
88
+ - `cms.imageProxyBase` — 画像プロキシのベース URL
89
+
90
+ ### `BaseContentItem`
91
+
92
+ CLI 生成物の各アイテム型は `BaseContentItem` を拡張する:
93
+
94
+ - `id: string` — Notion ページ ID
95
+ - `slug: string` — `slugField` で指定したプロパティから抽出
96
+ - `title?: string | null`
97
+ - `lastEditedTime: string` — Notion ページの最終編集時刻(変更検知に使用)
98
+ - `status?: string | null` — `statusField` で指定したプロパティ
99
+ - `publishedAt?: string` — 公開日時
100
+ - `coverImageUrl?: string | null` / `iconEmoji?: string | null` — Notion ページのカバー / アイコン
101
+
102
+ ### キャッシュアダプタ
103
+
104
+ - `memoryCache({ maxItems? })` — インプロセス LRU
105
+ - `noopDocOps` / `noopImgOps` — キャッシュ無効化用
106
+ - 別パッケージ: `@notion-headless-cms/cache/cloudflare` の `cloudflareCache(env)` / `r2Cache()` / `kvCache()`、`@notion-headless-cms/cache/next` の `nextCache()`
81
107
 
82
108
  ### エラー
83
- - `CMSError` / `isCMSError` / `isCMSErrorInNamespace(err, "core/")`
109
+
110
+ - `CMSError` / `isCMSError` / `isCMSErrorInNamespace(err, "core/")` / `matchCMSError`
84
111
  - 名前空間: `core/*` / `source/*` / `cache/*` / `renderer/*` / `cli/*`
85
112
 
86
113
  ## サブパスエクスポート
87
114
 
88
115
  ```ts
89
116
  import { CMSError } from "@notion-headless-cms/core/errors";
90
- import { memoryDocumentCache } from "@notion-headless-cms/core/cache/memory";
117
+ import { memoryCache } from "@notion-headless-cms/core/cache/memory";
91
118
  ```
92
119
 
93
120
  | サブパス | 内容 |
94
121
  |---|---|
95
122
  | `@notion-headless-cms/core` | 全エクスポート |
96
- | `@notion-headless-cms/core/errors` | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` |
123
+ | `@notion-headless-cms/core/errors` | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` / `matchCMSError` |
97
124
  | `@notion-headless-cms/core/hooks` | `mergeHooks` / `mergeLoggers` |
98
- | `@notion-headless-cms/core/cache/memory` | `memoryDocumentCache` / `memoryImageCache` |
99
- | `@notion-headless-cms/core/cache/noop` | `noopDocumentCache` / `noopImageCache` |
125
+ | `@notion-headless-cms/core/cache/memory` | `memoryCache` |
126
+ | `@notion-headless-cms/core/cache/noop` | `noopDocOps` / `noopImgOps` |
100
127
 
101
128
  ## ランタイム別レシピ
102
129
 
103
130
  - [Node.js スクリプト](../../docs/recipes/nodejs-script.md)
104
- - [Cloudflare Workers + R2 + KV](../../docs/recipes/cloudflare-workers.md) (`cloudflarePreset` は `cache-r2` パッケージ)
131
+ - [Cloudflare Workers + R2 + KV](../../docs/recipes/cloudflare-workers.md)
105
132
  - [Next.js App Router](../../docs/recipes/nextjs-app-router.md)
133
+ - [カスタムデータソース](../../docs/recipes/custom-source.md)
106
134
 
107
135
  ## 詳細ドキュメント
108
136
 
109
137
  - [クイックスタート](../../docs/quickstart.md)
110
138
  - [CMS メソッド一覧](../../docs/api/cms-methods.md)
111
- - [v0.2 → v0.3 移行ガイド](../../docs/migration/v0.3.md)
139
+ - [v2.0 移行ガイド](../../docs/migration/v2.0.md)
@@ -1,2 +1,2 @@
1
- import { i as memoryCache, n as MemoryDocumentOptions, r as MemoryImageOptions, t as MemoryCacheOptions } from "../memory-B7w5TQd_.mjs";
1
+ import { i as memoryCache, n as MemoryDocumentOptions, r as MemoryImageOptions, t as MemoryCacheOptions } from "../memory-BT9rLPr1.mjs";
2
2
  export { MemoryCacheOptions, MemoryDocumentOptions, MemoryImageOptions, memoryCache };
@@ -83,7 +83,7 @@ interface ImageRef {
83
83
  * title: string;
84
84
  * author: string;
85
85
  * }
86
- * createCMS<Post>({ source: createNotionCollection({ ... }) })
86
+ * createClient<Post>({ source: createNotionCollection({ ... }) })
87
87
  */
88
88
  interface BaseContentItem {
89
89
  /** ページ ID(変更検知に必須)。 */
@@ -159,4 +159,4 @@ interface CMSSchemaProperties {
159
159
  }
160
160
  //#endregion
161
161
  export { CachedItemMeta as a, ContentResult as c, CachedItemList as i, ImageRef as l, CMSSchemaProperties as n, StorageBinary as o, CachedItemContent as r, ContentBlock as s, BaseContentItem as t, InlineNode as u };
162
- //# sourceMappingURL=content-CuJcHvgF.d.mts.map
162
+ //# sourceMappingURL=content-DwsfWZao.d.mts.map
@@ -1,4 +1,4 @@
1
- import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-CuJcHvgF.mjs";
1
+ import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
2
  import { n as CMSError } from "./errors-CC_x98vG.mjs";
3
3
 
4
4
  //#region src/types/hooks.d.ts
@@ -85,4 +85,4 @@ declare function mergeLoggers(plugins: Array<{
85
85
  }>, directLogger?: Logger): Logger | undefined;
86
86
  //#endregion
87
87
  export { Logger as a, definePlugin as i, mergeLoggers as n, CMSHooks as o, CMSPlugin as r, MaybePromise as s, mergeHooks as t };
88
- //# sourceMappingURL=hooks-DFQ-tq_l.d.mts.map
88
+ //# sourceMappingURL=hooks-C6F2PG8x.d.mts.map
package/dist/hooks.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as mergeLoggers, t as mergeHooks } from "./hooks-DFQ-tq_l.mjs";
1
+ import { n as mergeLoggers, t as mergeHooks } from "./hooks-C6F2PG8x.mjs";
2
2
  export { mergeHooks, mergeLoggers };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { a as CachedItemMeta, c as ContentResult, i as CachedItemList, l as ImageRef, n as CMSSchemaProperties, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem, u as InlineNode } from "./content-CuJcHvgF.mjs";
2
- import { a as CacheAdapter, c as DataSource, d as PropertyDef, f as PropertyMap, i as memoryCache, l as InvalidateKind, o as DocumentCacheOps, p as WebhookConfig, s as ImageCacheOps, t as MemoryCacheOptions, u as InvalidateScope } from "./memory-B7w5TQd_.mjs";
1
+ import { a as CachedItemMeta, c as ContentResult, i as CachedItemList, l as ImageRef, n as CMSSchemaProperties, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem, u as InlineNode } from "./content-DwsfWZao.mjs";
2
+ import { a as CacheAdapter, c as DataSource, d as PropertyDef, f as PropertyMap, i as memoryCache, l as InvalidateKind, o as DocumentCacheOps, p as WebhookConfig, s as ImageCacheOps, t as MemoryCacheOptions, u as InvalidateScope } from "./memory-BT9rLPr1.mjs";
3
3
  import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-CC_x98vG.mjs";
4
- import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-DFQ-tq_l.mjs";
4
+ import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-C6F2PG8x.mjs";
5
5
 
6
6
  //#region src/types/collection.d.ts
7
7
  /**
@@ -62,7 +62,7 @@ type ItemWithContent<T extends BaseContentItem> = T & {
62
62
  };
63
63
  /** `cache.warm()` のオプション。 */
64
64
  interface WarmOptions {
65
- /** 並列度。デフォルトは createCMS の rateLimiter.maxConcurrent。 */
65
+ /** 並列度。デフォルトは createClient の rateLimiter.maxConcurrent。 */
66
66
  concurrency?: number;
67
67
  /** 進捗コールバック。 */
68
68
  onProgress?: (done: number, total: number) => void;
@@ -136,6 +136,31 @@ interface CollectionClient<T extends BaseContentItem = BaseContentItem> {
136
136
  cache: CollectionCacheOps<T>;
137
137
  }
138
138
  //#endregion
139
+ //#region src/types/sources.d.ts
140
+ /**
141
+ * CMS データソースアダプターのインターフェース。
142
+ * 各アダプターパッケージ (`@notion-headless-cms/notion-source` 等) が実装し、
143
+ * `createClient({ sources: { ... } })` に渡される。
144
+ */
145
+ interface CMSAdapter<C extends CollectionsConfig = CollectionsConfig> {
146
+ readonly collections: C;
147
+ }
148
+ /**
149
+ * アダプターパッケージが宣言マージで拡張する空インターフェース。
150
+ * import するだけでキーが補完候補に現れる (Fastify プラグインと同じパターン)。
151
+ *
152
+ * @example
153
+ * declare module "@notion-headless-cms/core" {
154
+ * interface CMSSources {
155
+ * notion?: CMSAdapter;
156
+ * }
157
+ * }
158
+ */
159
+ interface CMSSources {}
160
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
161
+ /** 全ソースの collections を交差型でマージする。 */
162
+ type MergeSourceCollections<S extends CMSSources> = UnionToIntersection<{ [K in keyof S]: S[K] extends CMSAdapter<infer C> ? C : never }[keyof S]>;
163
+ //#endregion
139
164
  //#region src/types/config.d.ts
140
165
  /** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */
141
166
  type LogLevel = "debug" | "info" | "warn" | "error";
@@ -182,7 +207,7 @@ interface RateLimiterConfig {
182
207
  baseDelayMs?: number;
183
208
  }
184
209
  /**
185
- * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createCMS` に渡される。
210
+ * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createClient` に渡される。
186
211
  *
187
212
  * `source` は notion-orm 等の DataSource 実装。
188
213
  * `slugField` / `statusField` は TS フィールド名 (DataSource の `properties` キーと一致)。
@@ -202,18 +227,18 @@ interface CollectionDef<T extends BaseContentItem = BaseContentItem> {
202
227
  hooks?: CMSHooks<T>;
203
228
  }
204
229
  /**
205
- * `createCMS({ collections })` の map 型。
230
+ * `createClient({ collections })` の map 型。
206
231
  * キーがコレクション名、値が `CollectionDef<T>`。
207
232
  */
208
233
  type CollectionsConfig = Record<string, CollectionDef<BaseContentItem>>;
209
234
  /** `CollectionsConfig` から各 T を抽出するユーティリティ型。 */
210
235
  type InferCollectionItem<C> = C extends CollectionDef<infer T> ? T : BaseContentItem;
211
236
  /**
212
- * `createCMS()` の入力。
213
- * 通常は CLI が生成した `nhc.ts` の `createCMS` がこの型をラップする。
237
+ * `createClient()` の入力。
238
+ * 通常は CLI が生成した `nhc.ts` の `createClient` がこの型をラップする。
214
239
  *
215
240
  * @example
216
- * createCMS({
241
+ * createClient({
217
242
  * collections: {
218
243
  * posts: {
219
244
  * source: createNotionCollection({ token, dataSourceId, properties }),
@@ -226,9 +251,14 @@ type InferCollectionItem<C> = C extends CollectionDef<infer T> ? T : BaseContent
226
251
  * swr: { ttlMs: 5 * 60_000 },
227
252
  * });
228
253
  */
229
- interface CreateCMSOptions<C extends CollectionsConfig = CollectionsConfig> {
230
- /** コレクション定義のマップ。 */
231
- collections: C;
254
+ interface CreateClientOptions<C extends CollectionsConfig = CollectionsConfig, S extends CMSSources = CMSSources> {
255
+ /**
256
+ * データソースアダプター (`@notion-headless-cms/notion-source` 等) のマップ。
257
+ * `sources` を指定する場合 `collections` は不要。両方指定された場合は `sources` が優先される。
258
+ */
259
+ sources?: S;
260
+ /** コレクション定義のマップ。`sources` を使う場合は不要。 */
261
+ collections?: C;
232
262
  /**
233
263
  * キャッシュアダプタ (配列)。未指定時はキャッシュなし。
234
264
  * - `memoryCache()` のように doc + image 両方を担当するもの
@@ -273,7 +303,7 @@ declare function isStale(cachedAt: number, ttlMs?: number): boolean;
273
303
  //#endregion
274
304
  //#region src/cache/noop.d.ts
275
305
  /**
276
- * 何もキャッシュしないアダプタ。`createCMS({ cache })` 未指定時の内部デフォルト。
306
+ * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。
277
307
  * テストでも使える。
278
308
  */
279
309
  declare const noopDocOps: DocumentCacheOps;
@@ -334,7 +364,7 @@ interface CMSGlobalOps {
334
364
  */
335
365
  readonly cacheImage: ((url: string) => Promise<string>) | undefined;
336
366
  /**
337
- * 画像プロキシのベース URL (`createCMS({ imageProxyBase })`)。
367
+ * 画像プロキシのベース URL (`createClient({ imageProxyBase })`)。
338
368
  * デフォルト `/api/images`。
339
369
  */
340
370
  readonly imageProxyBase: string;
@@ -342,11 +372,11 @@ interface CMSGlobalOps {
342
372
  /**
343
373
  * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。
344
374
  *
345
- * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createCMS`
375
+ * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createClient`
346
376
  * (低レベルのこの関数をラップしたもの) を経由する。
347
377
  *
348
378
  * @example
349
- * createCMS({
379
+ * createClient({
350
380
  * collections: {
351
381
  * posts: {
352
382
  * source: createNotionCollection({ token, dataSourceId, properties }),
@@ -359,7 +389,7 @@ interface CMSGlobalOps {
359
389
  * swr: { ttlMs: 5 * 60_000 },
360
390
  * });
361
391
  */
362
- declare function createCMS<C extends CollectionsConfig>(opts: CreateCMSOptions<C>): CMSClient<C>;
392
+ declare function createClient<C extends CollectionsConfig = CollectionsConfig, S extends CMSSources = CMSSources>(opts: CreateClientOptions<C, S>): CMSClient<MergeSourceCollections<S> extends CollectionsConfig ? MergeSourceCollections<S> : C>;
363
393
  //#endregion
364
394
  //#region src/rendering.d.ts
365
395
  /** 本文レンダリングに必要な依存を束ねたコンテキスト。 */
@@ -426,7 +456,7 @@ interface CollectionContext<T extends BaseContentItem> {
426
456
  */
427
457
  slugField: string;
428
458
  }
429
- /** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */
459
+ /** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */
430
460
  declare class CollectionClientImpl<T extends BaseContentItem> implements CollectionClient<T> {
431
461
  private readonly ctx;
432
462
  readonly cache: CollectionCacheOps<T>;
@@ -459,5 +489,5 @@ declare class CollectionClientImpl<T extends BaseContentItem> implements Collect
459
489
  private fetchRaw;
460
490
  }
461
491
  //#endregion
462
- export { type AdjacencyOptions, type BaseContentItem, type BuiltInCMSErrorCode, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchemaProperties, type CacheAdapter, type CachedItemContent, type CachedItemList, type CachedItemMeta, type CheckResult, type CollectionCacheOps, type CollectionClient, CollectionClientImpl, type CollectionContext, type CollectionDef, type CollectionsConfig, type ContentBlock, type ContentConfig, type ContentResult, type CreateCMSOptions, DEFAULT_RETRY_CONFIG, type DataSource, type DocumentCacheOps, type FindOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheOps, type ImageRef, type InferCollectionItem, type InlineNode, type InvalidateKind, type InvalidateScope, type ItemWithContent, type ListOptions, type LogLevel, type Logger, type MaybePromise, type MemoryCacheOptions, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type RetryConfig, type SWRConfig, type SortOption, type StorageBinary, type WarmOptions, type WarmResult, type WebhookConfig, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, noopDocOps, noopImgOps, sha256Hex, withRetry };
492
+ export { type AdjacencyOptions, type BaseContentItem, type BuiltInCMSErrorCode, type CMSAdapter, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchemaProperties, type CMSSources, type CacheAdapter, type CachedItemContent, type CachedItemList, type CachedItemMeta, type CheckResult, type CollectionCacheOps, type CollectionClient, CollectionClientImpl, type CollectionContext, type CollectionDef, type CollectionsConfig, type ContentBlock, type ContentConfig, type ContentResult, type CreateClientOptions, DEFAULT_RETRY_CONFIG, type DataSource, type DocumentCacheOps, type FindOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheOps, type ImageRef, type InferCollectionItem, type InlineNode, type InvalidateKind, type InvalidateScope, type ItemWithContent, type ListOptions, type LogLevel, type Logger, type MaybePromise, type MemoryCacheOptions, type MergeSourceCollections, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type RetryConfig, type SWRConfig, type SortOption, type StorageBinary, type WarmOptions, type WarmResult, type WebhookConfig, collectionKey, createClient, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, noopDocOps, noopImgOps, sha256Hex, withRetry };
463
493
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -52,7 +52,7 @@ const noopImg = {
52
52
  }
53
53
  };
54
54
  /**
55
- * 何もキャッシュしないアダプタ。`createCMS({ cache })` 未指定時の内部デフォルト。
55
+ * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。
56
56
  * テストでも使える。
57
57
  */
58
58
  const noopDocOps = noopDoc;
@@ -168,7 +168,7 @@ async function loadDefaultRenderer() {
168
168
  } catch {
169
169
  throw new CMSError({
170
170
  code: "core/config_invalid",
171
- message: "renderer が未指定で、@notion-headless-cms/renderer のロードにも失敗しました。 createCMS の renderer オプションを指定するか、@notion-headless-cms/renderer をインストールしてください。",
171
+ message: "renderer が未指定で、@notion-headless-cms/renderer のロードにも失敗しました。 createClient の renderer オプションを指定するか、@notion-headless-cms/renderer をインストールしてください。",
172
172
  context: { operation: "loadDefaultRenderer" }
173
173
  });
174
174
  }
@@ -332,7 +332,7 @@ async function withRetry(fn, config) {
332
332
  function collectionKey(collection, slug) {
333
333
  return slug ? `${collection}:${slug}` : collection;
334
334
  }
335
- /** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */
335
+ /** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */
336
336
  var CollectionClientImpl = class {
337
337
  cache;
338
338
  constructor(ctx) {
@@ -931,11 +931,11 @@ function applyLogLevel(logger, minLevel) {
931
931
  /**
932
932
  * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。
933
933
  *
934
- * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createCMS`
934
+ * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createClient`
935
935
  * (低レベルのこの関数をラップしたもの) を経由する。
936
936
  *
937
937
  * @example
938
- * createCMS({
938
+ * createClient({
939
939
  * collections: {
940
940
  * posts: {
941
941
  * source: createNotionCollection({ token, dataSourceId, properties }),
@@ -948,26 +948,32 @@ function applyLogLevel(logger, minLevel) {
948
948
  * swr: { ttlMs: 5 * 60_000 },
949
949
  * });
950
950
  */
951
- function createCMS(opts) {
952
- if (!opts.collections || Object.keys(opts.collections).length === 0) throw new CMSError({
951
+ function createClient(opts) {
952
+ let mergedFromSources;
953
+ if (opts.sources) {
954
+ mergedFromSources = {};
955
+ for (const adapter of Object.values(opts.sources)) if (adapter) Object.assign(mergedFromSources, adapter.collections);
956
+ }
957
+ const collectionsInput = mergedFromSources ?? opts.collections;
958
+ if (!collectionsInput || Object.keys(collectionsInput).length === 0) throw new CMSError({
953
959
  code: "core/config_invalid",
954
- message: "createCMS: collections に少なくとも 1 つのコレクションを指定してください。",
955
- context: { operation: "createCMS" }
960
+ message: "createClient: sources または collections に少なくとも 1 つのコレクションを指定してください。",
961
+ context: { operation: "createClient" }
956
962
  });
957
- for (const [name, def] of Object.entries(opts.collections)) {
963
+ for (const [name, def] of Object.entries(collectionsInput)) {
958
964
  if (!def.source) throw new CMSError({
959
965
  code: "core/config_invalid",
960
- message: `createCMS: コレクション "${name}" の source は必須です。`,
966
+ message: `createClient: コレクション "${name}" の source は必須です。`,
961
967
  context: {
962
- operation: "createCMS",
968
+ operation: "createClient",
963
969
  collection: name
964
970
  }
965
971
  });
966
972
  if (!def.slugField) throw new CMSError({
967
973
  code: "core/config_invalid",
968
- message: `createCMS: コレクション "${name}" の slugField は必須です。`,
974
+ message: `createClient: コレクション "${name}" の slugField は必須です。`,
969
975
  context: {
970
- operation: "createCMS",
976
+ operation: "createClient",
971
977
  collection: name
972
978
  }
973
979
  });
@@ -988,7 +994,7 @@ function createCMS(opts) {
988
994
  };
989
995
  const collectionNames = [];
990
996
  const collections = {};
991
- for (const [name, def] of Object.entries(opts.collections)) {
997
+ for (const [name, def] of Object.entries(collectionsInput)) {
992
998
  collectionNames.push(name);
993
999
  const source = def.source;
994
1000
  const colHooks = def.hooks;
@@ -1039,7 +1045,7 @@ function createCMS(opts) {
1039
1045
  return createHandler({
1040
1046
  imageCache: cacheRes.img,
1041
1047
  async parseWebhookFor(collection, req, webhookSecret) {
1042
- const def = opts.collections[collection];
1048
+ const def = collectionsInput[collection];
1043
1049
  if (!def) throw new CMSError({
1044
1050
  code: "webhook/unknown_collection",
1045
1051
  message: `Unknown collection: ${collection}`,
@@ -1074,6 +1080,6 @@ function definePlugin(plugin) {
1074
1080
  return plugin;
1075
1081
  }
1076
1082
  //#endregion
1077
- export { CMSError, CollectionClientImpl, DEFAULT_RETRY_CONFIG, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, noopDocOps, noopImgOps, sha256Hex, withRetry };
1083
+ export { CMSError, CollectionClientImpl, DEFAULT_RETRY_CONFIG, collectionKey, createClient, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, matchCMSError, memoryCache, mergeHooks, mergeLoggers, noopDocOps, noopImgOps, sha256Hex, withRetry };
1078
1084
 
1079
1085
  //# 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/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createCMS({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/renderer` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/renderer\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/renderer のロードにも失敗しました。\" +\n \" createCMS の renderer オプションを指定するか、@notion-headless-cms/renderer をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n onRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n config.onRetry?.(attempt + 1, status);\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n *\n * (Cache adapter 内部のキー戦略はアダプタごとに異なるが、\n * 表示や再計算用に core 側でも公開ヘルパーを提供する)\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名 (CLI 生成の `CollectionDef.slugField`)。\n * `source.properties[slugField].notion` を Notion プロパティ名として\n * `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n // bypassCache: 強制ブロッキング取得\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: キャッシュ即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n // メタ未キャッシュ: 同期フェッチ (保存はバックグラウンド可)\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /**\n * 本文キャッシュをロードする。キャッシュが無いか、メタとの整合性が取れない場合は\n * 再生成して書き戻す。\n */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n // notionBlocks 対応より前のキャッシュは notionBlocks を持たないため、\n // source が loadNotionBlocks を実装していて cached に欠けている場合は\n // 再生成して埋める (後方互換のための lazy backfill)。\n const needsNotionBlocksBackfill =\n this.ctx.source.loadNotionBlocks !== undefined &&\n cached !== null &&\n cached.notionBlocks === undefined;\n if (\n cached &&\n cached.notionUpdatedAt === expected &&\n !needsNotionBlocksBackfill\n ) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけバックグラウンド再生成する。エラーは onSwrError フックに通知する。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // 同一インスタンス内で本文ロードを集約する (複数呼び出しでも 1 回の I/O)\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => (await loadPayload()).notionBlocks,\n }) as ItemWithContent<T>;\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n // SWR: 即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n // 未キャッシュ: 同期フェッチ\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else if (this.ctx.ttlMs !== undefined) {\n // 変更なし + TTL あり: cachedAt をリセットして次回の期限切れを先送りする\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、TTL をリセット\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", { attempt, status });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n });\n },\n };\n\n // slugField から Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // フォールバック: list して線形探索\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順、未設定の場合は lastEditedTime 降順でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n // lastEditedTime は必須なので av/bv は常に truthy\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/**\n * CMSError のコードから HTTP ステータスコードを返す。\n * 既知の webhook エラーコードのみ対応し、それ以外は null を返す。\n */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n // 画像: GET {basePath}/images/:hash\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n // Revalidate: POST {basePath}/revalidate/:collection\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateCMSOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<C>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/** `CMSClient` のグローバル名前空間。 */\nexport interface CMSGlobalOps {\n /** 登録されているコレクション名の一覧。 */\n readonly collections: readonly string[];\n /** 全コレクションまたは特定スコープのキャッシュを無効化する。 */\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n /** ハッシュキーでキャッシュ画像を取得する。 */\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む関数。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。react-renderer の\n * `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n /**\n * 画像プロキシのベース URL (`createCMS({ imageProxyBase })`)。\n * デフォルト `/api/images`。\n */\n readonly imageProxyBase: string;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n img: ImageCacheOps;\n imgName: string;\n hasImg: boolean;\n}\n\n/**\n * `cache` オプションから document / image オペレーションを解決する。\n *\n * - 各 adapter の `handles` を見て先勝ち (最初に見つかったもの) で振り分ける\n * - 未指定なら両方 noop\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgFound = true;\n }\n }\n\n return { doc, docName, img, imgName, hasImg: imgFound };\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/** `logger` から `minLevel` 未満のレベルを除いた新しい Logger を返す。 */\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。\n *\n * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createCMS`\n * (低レベルのこの関数をラップしたもの) を経由する。\n *\n * @example\n * createCMS({\n * collections: {\n * posts: {\n * source: createNotionCollection({ token, dataSourceId, properties }),\n * slugField: \"slug\",\n * statusField: \"status\",\n * publishedStatuses: [\"公開済み\"],\n * }\n * },\n * cache: [memoryCache()],\n * swr: { ttlMs: 5 * 60_000 },\n * });\n */\nexport function createCMS<C extends CollectionsConfig>(\n opts: CreateCMSOptions<C>,\n): CMSClient<C> {\n if (!opts.collections || Object.keys(opts.collections).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createCMS: collections に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createCMS\" },\n });\n }\n\n for (const [name, def] of Object.entries(opts.collections)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createCMS\", collection: name },\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createCMS\", collection: name },\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const logger = opts.logLevel\n ? applyLogLevel(baseLogger, opts.logLevel)\n : baseLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: (keyof C & string)[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(opts.collections)) {\n collectionNames.push(name as keyof C & string);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = opts.collections[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<C>;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/cache/noop.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。\n * テストでも使える。\n */\nexport const noopDocOps: DocumentCacheOps = noopDoc;\nexport const noopImgOps: ImageCacheOps = noopImg;\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheOps, Logger, StorageBinary } from \"./types/index\";\n\n/**\n * レスポンスの Content-Type ヘッダから画像の MIME タイプを取り出す。\n * ヘッダがない、または image/* でない場合は CMSError を投げる。\n * URL 拡張子からの推測や jpeg デフォルトは行わない。\n */\nfunction pickImageContentType(\n headerValue: string | null,\n notionUrl: string,\n): string {\n if (!headerValue) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: \"Image response missing Content-Type header.\",\n context: { operation: \"fetchAndCacheImage:contentType\", notionUrl },\n });\n }\n const value = (headerValue.split(\";\")[0] ?? headerValue).trim().toLowerCase();\n if (!value.startsWith(\"image/\")) {\n throw new CMSError({\n code: \"cache/image_invalid_content_type\",\n message: `Image response has non-image Content-Type: ${value}`,\n context: {\n operation: \"fetchAndCacheImage:contentType\",\n notionUrl,\n contentType: value,\n },\n });\n }\n return value;\n}\n\n/**\n * Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n cache: ImageCacheOps,\n cacheName: string,\n notionUrl: string,\n hash: string,\n imageProxyBase: string,\n logger?: Logger,\n): Promise<string> {\n const proxyUrl = `${imageProxyBase}/${hash}`;\n\n const existing = await cache.get(hash);\n if (existing) {\n logger?.debug?.(\"画像キャッシュヒット\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n return proxyUrl;\n }\n\n logger?.debug?.(\"画像キャッシュミス、Notion からフェッチ\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n\n try {\n const response = await fetch(notionUrl, {\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) {\n throw new CMSError({\n code: \"cache/image_fetch_failed\",\n message: `Failed to fetch Notion image: HTTP ${response.status}`,\n context: {\n operation: \"fetchAndCacheImage\",\n notionUrl,\n httpStatus: response.status,\n },\n });\n }\n\n const data = await response.arrayBuffer();\n const contentType = pickImageContentType(\n response.headers.get(\"content-type\"),\n notionUrl,\n );\n await cache.set(hash, data, contentType);\n logger?.debug?.(\"画像をキャッシュに保存\", {\n operation: \"fetchAndCacheImage\",\n cacheAdapter: cacheName,\n imageHash: hash,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"cache/io_failed\",\n message: \"Failed to fetch or cache Notion image.\",\n cause: err,\n context: { operation: \"fetchAndCacheImage\", notionUrl },\n });\n }\n\n return proxyUrl;\n}\n\n/**\n * `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。\n * 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして\n * {@link ImageCacheOps} に保存後、プロキシ URL を返す。\n *\n * ハッシュのメモ化はファクトリ呼び出し単位でスコープ化されており、\n * インスタンス間でキャッシュを共有しない。\n */\nexport function buildCacheImageFn(\n cache: ImageCacheOps,\n cacheName: string,\n imageProxyBase: string,\n logger?: Logger,\n): (notionUrl: string) => Promise<string> {\n const hashMemo = new Map<string, string>();\n return async (notionUrl) => {\n let hash = hashMemo.get(notionUrl);\n if (hash === undefined) {\n hash = await sha256Hex(notionUrl);\n hashMemo.set(notionUrl, hash);\n }\n return fetchAndCacheImage(\n cache,\n cacheName,\n notionUrl,\n hash,\n imageProxyBase,\n logger,\n );\n };\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n BaseContentItem,\n CachedItemContent,\n CachedItemMeta,\n CMSHooks,\n ContentConfig,\n DataSource,\n ImageCacheOps,\n Logger,\n RendererFn,\n} from \"./types/index\";\n\n/**\n * `@notion-headless-cms/renderer` を動的 import してデフォルトレンダラーを返す。\n * core のゼロ依存ルールを守るため静的 import は禁止。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n try {\n const mod = await import(\"@notion-headless-cms/renderer\");\n return (mod as { renderMarkdown: RendererFn }).renderMarkdown;\n } catch {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"renderer が未指定で、@notion-headless-cms/renderer のロードにも失敗しました。\" +\n \" createClient の renderer オプションを指定するか、@notion-headless-cms/renderer をインストールしてください。\",\n context: { operation: \"loadDefaultRenderer\" },\n });\n }\n}\n\n/** 本文レンダリングに必要な依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n source: DataSource<T>;\n rendererFn: RendererFn | undefined;\n imgCache: ImageCacheOps;\n imgCacheName: string;\n hasImageCache: boolean;\n imageProxyBase: string;\n contentConfig: ContentConfig | undefined;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n}\n\n/**\n * メタデータキャッシュエントリを生成する。Notion API も renderer も呼ばない軽量関数。\n */\nexport function buildCachedItemMeta<T extends BaseContentItem>(\n item: T,\n source: DataSource<T>,\n): CachedItemMeta<T> {\n return {\n item,\n notionUpdatedAt: source.getLastModified(item),\n cachedAt: Date.now(),\n };\n}\n\n/**\n * アイテム本文を Markdown ロード → blocks 生成 → HTML レンダリング → フック適用まで\n * 実行し、本文キャッシュ用の `CachedItemContent` を返す。\n */\nexport async function buildCachedItemContent<T extends BaseContentItem>(\n item: T,\n ctx: RenderContext<T>,\n): Promise<CachedItemContent> {\n const start = Date.now();\n ctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n slug: item.slug,\n pageId: item.id,\n });\n ctx.hooks.onRenderStart?.(item.slug);\n\n let markdown: string;\n try {\n markdown = await ctx.source.loadMarkdown(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_markdown_failed\",\n message: \"Failed to load markdown from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n let blocks: ContentBlock[];\n try {\n blocks = await ctx.source.loadBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n // react-renderer など Notion 形式を直接消費する利用側のため、\n // DataSource が対応していれば BlockObjectResponse ツリーも取得・キャッシュする。\n let notionBlocks: unknown[] | undefined;\n if (ctx.source.loadNotionBlocks) {\n try {\n notionBlocks = await ctx.source.loadNotionBlocks(item);\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"source/load_blocks_failed\",\n message: \"Failed to load Notion blocks from source.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:loadNotionBlocks\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n }\n\n const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionBlocks,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n onRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n config.onRetry?.(attempt + 1, status);\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n *\n * (Cache adapter 内部のキー戦略はアダプタごとに異なるが、\n * 表示や再計算用に core 側でも公開ヘルパーを提供する)\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名 (CLI 生成の `CollectionDef.slugField`)。\n * `source.properties[slugField].notion` を Notion プロパティ名として\n * `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n // bypassCache: 強制ブロッキング取得\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: キャッシュ即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n // メタ未キャッシュ: 同期フェッチ (保存はバックグラウンド可)\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /**\n * 本文キャッシュをロードする。キャッシュが無いか、メタとの整合性が取れない場合は\n * 再生成して書き戻す。\n */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n // notionBlocks 対応より前のキャッシュは notionBlocks を持たないため、\n // source が loadNotionBlocks を実装していて cached に欠けている場合は\n // 再生成して埋める (後方互換のための lazy backfill)。\n const needsNotionBlocksBackfill =\n this.ctx.source.loadNotionBlocks !== undefined &&\n cached !== null &&\n cached.notionBlocks === undefined;\n if (\n cached &&\n cached.notionUpdatedAt === expected &&\n !needsNotionBlocksBackfill\n ) {\n return cached;\n }\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけバックグラウンド再生成する。エラーは onSwrError フックに通知する。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // 同一インスタンス内で本文ロードを集約する (複数呼び出しでも 1 回の I/O)\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n notionBlocks: async () => (await loadPayload()).notionBlocks,\n }) as ItemWithContent<T>;\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n // SWR: 即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n // 未キャッシュ: 同期フェッチ\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else if (this.ctx.ttlMs !== undefined) {\n // 変更なし + TTL あり: cachedAt をリセットして次回の期限切れを先送りする\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、TTL をリセット\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", { attempt, status });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n });\n },\n };\n\n // slugField から Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // フォールバック: list して線形探索\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順、未設定の場合は lastEditedTime 降順でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n // lastEditedTime は必須なので av/bv は常に truthy\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/**\n * CMSError のコードから HTTP ステータスコードを返す。\n * 既知の webhook エラーコードのみ対応し、それ以外は null を返す。\n */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n // 画像: GET {basePath}/images/:hash\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n // Revalidate: POST {basePath}/revalidate/:collection\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateClientOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\nimport type {\n CMSAdapter,\n CMSSources,\n MergeSourceCollections,\n} from \"./types/sources\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<C>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/** `CMSClient` のグローバル名前空間。 */\nexport interface CMSGlobalOps {\n /** 登録されているコレクション名の一覧。 */\n readonly collections: readonly string[];\n /** 全コレクションまたは特定スコープのキャッシュを無効化する。 */\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n /** ハッシュキーでキャッシュ画像を取得する。 */\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n /**\n * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む関数。\n * 画像キャッシュが未設定 (noop) の場合は `undefined`。react-renderer の\n * `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う。\n */\n readonly cacheImage: ((url: string) => Promise<string>) | undefined;\n /**\n * 画像プロキシのベース URL (`createClient({ imageProxyBase })`)。\n * デフォルト `/api/images`。\n */\n readonly imageProxyBase: string;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n img: ImageCacheOps;\n imgName: string;\n hasImg: boolean;\n}\n\n/**\n * `cache` オプションから document / image オペレーションを解決する。\n *\n * - 各 adapter の `handles` を見て先勝ち (最初に見つかったもの) で振り分ける\n * - 未指定なら両方 noop\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgFound = true;\n }\n }\n\n return { doc, docName, img, imgName, hasImg: imgFound };\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/** `logger` から `minLevel` 未満のレベルを除いた新しい Logger を返す。 */\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。\n *\n * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createClient`\n * (低レベルのこの関数をラップしたもの) を経由する。\n *\n * @example\n * createClient({\n * collections: {\n * posts: {\n * source: createNotionCollection({ token, dataSourceId, properties }),\n * slugField: \"slug\",\n * statusField: \"status\",\n * publishedStatuses: [\"公開済み\"],\n * }\n * },\n * cache: [memoryCache()],\n * swr: { ttlMs: 5 * 60_000 },\n * });\n */\nexport function createClient<\n C extends CollectionsConfig = CollectionsConfig,\n S extends CMSSources = CMSSources,\n>(\n opts: CreateClientOptions<C, S>,\n): CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : C\n> {\n // sources が指定されていれば各 adapter の collections をマージする (後勝ち)\n let mergedFromSources: CollectionsConfig | undefined;\n if (opts.sources) {\n mergedFromSources = {};\n for (const adapter of Object.values(\n opts.sources as unknown as Record<string, CMSAdapter | undefined>,\n )) {\n if (adapter) Object.assign(mergedFromSources, adapter.collections);\n }\n }\n const collectionsInput: CollectionsConfig | undefined =\n mergedFromSources ?? opts.collections;\n\n if (!collectionsInput || Object.keys(collectionsInput).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createClient: sources または collections に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createClient\" },\n });\n }\n\n for (const [name, def] of Object.entries(collectionsInput)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createClient\", collection: name },\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createClient: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createClient\", collection: name },\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const logger = opts.logLevel\n ? applyLogLevel(baseLogger, opts.logLevel)\n : baseLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: (keyof C & string)[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(collectionsInput)) {\n collectionNames.push(name as keyof C & string);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const cacheImage = cacheRes.hasImg\n ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger)\n : undefined;\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n cacheImage,\n imageProxyBase,\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = collectionsInput[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<\n MergeSourceCollections<S> extends CollectionsConfig\n ? MergeSourceCollections<S>\n : C\n >;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as CachedItemMeta, i as CachedItemList, l as ImageRef, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem } from "./content-CuJcHvgF.mjs";
1
+ import { a as CachedItemMeta, i as CachedItemList, l as ImageRef, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
2
 
3
3
  //#region src/types/data-source.d.ts
4
4
  /**
@@ -9,6 +9,11 @@ interface PropertyDef {
9
9
  type: "title" | "richText" | "select" | "status" | "multiSelect" | "date" | "number" | "checkbox" | "url";
10
10
  /** Notion DB 上のプロパティ名(表示名)。 */
11
11
  notion: string;
12
+ /**
13
+ * status / select の選択肢一覧。CLI が `as const` で出力する。
14
+ * 型レベルで literal union を導出するためにのみ使用し、runtime では参照しない。
15
+ */
16
+ options?: readonly string[];
12
17
  }
13
18
  /** Notion DB のプロパティ一覧マップ。CLI 生成の `*Properties` の型。 */
14
19
  type PropertyMap = Record<string, PropertyDef>;
@@ -112,7 +117,7 @@ interface ImageCacheOps {
112
117
  * 統一キャッシュアダプタ。`handles` で担当領域を申告し、
113
118
  * `doc` / `img` のいずれか(または両方)を実装する。
114
119
  *
115
- * `createCMS({ cache })` には `CacheAdapter | CacheAdapter[]` を渡せる。
120
+ * `createClient({ cache })` には `CacheAdapter | CacheAdapter[]` を渡せる。
116
121
  * 配列で渡された場合、core は `handles` を見て document / image をそれぞれ別アダプタに振り分ける。
117
122
  *
118
123
  * @example
@@ -150,4 +155,4 @@ interface MemoryCacheOptions extends MemoryDocumentOptions, MemoryImageOptions {
150
155
  declare function memoryCache(options?: MemoryCacheOptions): CacheAdapter;
151
156
  //#endregion
152
157
  export { CacheAdapter as a, DataSource as c, PropertyDef as d, PropertyMap as f, memoryCache as i, InvalidateKind as l, MemoryDocumentOptions as n, DocumentCacheOps as o, WebhookConfig as p, MemoryImageOptions as r, ImageCacheOps as s, MemoryCacheOptions as t, InvalidateScope as u };
153
- //# sourceMappingURL=memory-B7w5TQd_.d.mts.map
158
+ //# sourceMappingURL=memory-BT9rLPr1.d.mts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notion-headless-cms/core",
3
- "version": "0.3.17",
3
+ "version": "0.3.19",
4
4
  "description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
5
5
  "keywords": [
6
6
  "notion",