@notion-headless-cms/core 0.3.16 → 0.3.18

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,109 +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
82
 
75
- ### cache アダプタ
76
- - `memoryDocumentCache({ maxItems? })` — LRU 対応インメモリキャッシュ
77
- - `memoryImageCache({ maxItems?, maxSizeBytes? })`
78
- - `noopDocumentCache()` / `noopImageCache()`
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()`
79
107
 
80
108
  ### エラー
81
- - `CMSError` / `isCMSError` / `isCMSErrorInNamespace(err, "core/")`
109
+
110
+ - `CMSError` / `isCMSError` / `isCMSErrorInNamespace(err, "core/")` / `matchCMSError`
82
111
  - 名前空間: `core/*` / `source/*` / `cache/*` / `renderer/*` / `cli/*`
83
112
 
84
113
  ## サブパスエクスポート
85
114
 
86
115
  ```ts
87
116
  import { CMSError } from "@notion-headless-cms/core/errors";
88
- import { memoryDocumentCache } from "@notion-headless-cms/core/cache/memory";
117
+ import { memoryCache } from "@notion-headless-cms/core/cache/memory";
89
118
  ```
90
119
 
91
120
  | サブパス | 内容 |
92
121
  |---|---|
93
122
  | `@notion-headless-cms/core` | 全エクスポート |
94
- | `@notion-headless-cms/core/errors` | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` |
123
+ | `@notion-headless-cms/core/errors` | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` / `matchCMSError` |
95
124
  | `@notion-headless-cms/core/hooks` | `mergeHooks` / `mergeLoggers` |
96
- | `@notion-headless-cms/core/cache/memory` | `memoryDocumentCache` / `memoryImageCache` |
97
- | `@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` |
98
127
 
99
128
  ## ランタイム別レシピ
100
129
 
101
130
  - [Node.js スクリプト](../../docs/recipes/nodejs-script.md)
102
- - [Cloudflare Workers + R2 + KV](../../docs/recipes/cloudflare-workers.md) (`cloudflarePreset` は `cache-r2` パッケージ)
131
+ - [Cloudflare Workers + R2 + KV](../../docs/recipes/cloudflare-workers.md)
103
132
  - [Next.js App Router](../../docs/recipes/nextjs-app-router.md)
133
+ - [カスタムデータソース](../../docs/recipes/custom-source.md)
104
134
 
105
135
  ## 詳細ドキュメント
106
136
 
107
137
  - [クイックスタート](../../docs/quickstart.md)
108
138
  - [CMS メソッド一覧](../../docs/api/cms-methods.md)
109
- - [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-BKDsuGVN.mjs";
1
+ import { i as memoryCache, n as MemoryDocumentOptions, r as MemoryImageOptions, t as MemoryCacheOptions } from "../memory-Ct4NDOFO.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(変更検知に必須)。 */
@@ -128,6 +128,12 @@ interface CachedItemContent {
128
128
  html: string;
129
129
  markdown: string;
130
130
  blocks: ContentBlock[];
131
+ /**
132
+ * Notion API 由来のブロックツリー(`BlockObjectResponse + children`)。
133
+ * react-renderer など Notion 固有形式を消費する利用側のために保持する。
134
+ * core はゼロ依存ルールに従い `@notionhq/client` の型を import しないため `unknown[]` で扱う。
135
+ */
136
+ notionBlocks?: unknown[];
131
137
  /** メタデータ整合性検証用に同じ値を保持する。 */
132
138
  notionUpdatedAt: string;
133
139
  cachedAt: number;
@@ -153,4 +159,4 @@ interface CMSSchemaProperties {
153
159
  }
154
160
  //#endregion
155
161
  export { CachedItemMeta as a, ContentResult as c, CachedItemList as i, ImageRef as l, CMSSchemaProperties as n, StorageBinary as o, CachedItemContent as r, ContentBlock as s, BaseContentItem as t, InlineNode as u };
156
- //# sourceMappingURL=content-D7PfY-bV.d.mts.map
162
+ //# sourceMappingURL=content-DwsfWZao.d.mts.map
@@ -1,4 +1,4 @@
1
- import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-D7PfY-bV.mjs";
1
+ import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-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-C4HRqHLo.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-C4HRqHLo.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-D7PfY-bV.mjs";
2
- import { a as CacheAdapter, c as DataSource, d as PropertyDef, f as PropertyMap, i as memoryCache, l as InvalidateKind, o as DocumentCacheOps, p as WebhookConfig, s as ImageCacheOps, t as MemoryCacheOptions, u as InvalidateScope } from "./memory-BKDsuGVN.mjs";
1
+ import { a as CachedItemMeta, c as ContentResult, i as CachedItemList, l as ImageRef, n as CMSSchemaProperties, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem, u as InlineNode } from "./content-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-Ct4NDOFO.mjs";
3
3
  import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-CC_x98vG.mjs";
4
- import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-C4HRqHLo.mjs";
4
+ import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-C6F2PG8x.mjs";
5
5
 
6
6
  //#region src/types/collection.d.ts
7
7
  /**
@@ -52,10 +52,17 @@ type ItemWithContent<T extends BaseContentItem> = T & {
52
52
  /** HTML 文字列を返す。 */html(): Promise<string>; /** Markdown 文字列を返す。 */
53
53
  markdown(): Promise<string>; /** コンテンツ AST(ContentBlock 配列)を返す。 */
54
54
  blocks(): Promise<ContentBlock[]>;
55
+ /**
56
+ * Notion API のブロックツリー(`BlockObjectResponse + children`)を返す。
57
+ * DataSource が `loadNotionBlocks` を実装している場合のみ非 undefined。
58
+ * react-renderer 等の Notion 固有 renderer に渡すために使う。
59
+ * core はゼロ依存ルールに従い `unknown[]` 型として扱う(利用側でキャスト)。
60
+ */
61
+ notionBlocks(): Promise<unknown[] | undefined>;
55
62
  };
56
63
  /** `cache.warm()` のオプション。 */
57
64
  interface WarmOptions {
58
- /** 並列度。デフォルトは createCMS の rateLimiter.maxConcurrent。 */
65
+ /** 並列度。デフォルトは createClient の rateLimiter.maxConcurrent。 */
59
66
  concurrency?: number;
60
67
  /** 進捗コールバック。 */
61
68
  onProgress?: (done: number, total: number) => void;
@@ -129,6 +136,31 @@ interface CollectionClient<T extends BaseContentItem = BaseContentItem> {
129
136
  cache: CollectionCacheOps<T>;
130
137
  }
131
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
132
164
  //#region src/types/config.d.ts
133
165
  /** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */
134
166
  type LogLevel = "debug" | "info" | "warn" | "error";
@@ -175,7 +207,7 @@ interface RateLimiterConfig {
175
207
  baseDelayMs?: number;
176
208
  }
177
209
  /**
178
- * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createCMS` に渡される。
210
+ * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createClient` に渡される。
179
211
  *
180
212
  * `source` は notion-orm 等の DataSource 実装。
181
213
  * `slugField` / `statusField` は TS フィールド名 (DataSource の `properties` キーと一致)。
@@ -195,18 +227,18 @@ interface CollectionDef<T extends BaseContentItem = BaseContentItem> {
195
227
  hooks?: CMSHooks<T>;
196
228
  }
197
229
  /**
198
- * `createCMS({ collections })` の map 型。
230
+ * `createClient({ collections })` の map 型。
199
231
  * キーがコレクション名、値が `CollectionDef<T>`。
200
232
  */
201
233
  type CollectionsConfig = Record<string, CollectionDef<BaseContentItem>>;
202
234
  /** `CollectionsConfig` から各 T を抽出するユーティリティ型。 */
203
235
  type InferCollectionItem<C> = C extends CollectionDef<infer T> ? T : BaseContentItem;
204
236
  /**
205
- * `createCMS()` の入力。
206
- * 通常は CLI が生成した `nhc.ts` の `createCMS` がこの型をラップする。
237
+ * `createClient()` の入力。
238
+ * 通常は CLI が生成した `nhc.ts` の `createClient` がこの型をラップする。
207
239
  *
208
240
  * @example
209
- * createCMS({
241
+ * createClient({
210
242
  * collections: {
211
243
  * posts: {
212
244
  * source: createNotionCollection({ token, dataSourceId, properties }),
@@ -219,9 +251,14 @@ type InferCollectionItem<C> = C extends CollectionDef<infer T> ? T : BaseContent
219
251
  * swr: { ttlMs: 5 * 60_000 },
220
252
  * });
221
253
  */
222
- interface CreateCMSOptions<C extends CollectionsConfig = CollectionsConfig> {
223
- /** コレクション定義のマップ。 */
224
- 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;
225
262
  /**
226
263
  * キャッシュアダプタ (配列)。未指定時はキャッシュなし。
227
264
  * - `memoryCache()` のように doc + image 両方を担当するもの
@@ -266,7 +303,7 @@ declare function isStale(cachedAt: number, ttlMs?: number): boolean;
266
303
  //#endregion
267
304
  //#region src/cache/noop.d.ts
268
305
  /**
269
- * 何もキャッシュしないアダプタ。`createCMS({ cache })` 未指定時の内部デフォルト。
306
+ * 何もキャッシュしないアダプタ。`createClient({ cache })` 未指定時の内部デフォルト。
270
307
  * テストでも使える。
271
308
  */
272
309
  declare const noopDocOps: DocumentCacheOps;
@@ -320,15 +357,26 @@ interface CMSGlobalOps {
320
357
  handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;
321
358
  /** ハッシュキーでキャッシュ画像を取得する。 */
322
359
  getCachedImage(hash: string): Promise<StorageBinary | null>;
360
+ /**
361
+ * Notion 画像 URL を `{imageProxyBase}/{sha256}` 形式へ変換しキャッシュへ書き込む関数。
362
+ * 画像キャッシュが未設定 (noop) の場合は `undefined`。react-renderer の
363
+ * `resolveBlockImageUrls` などサーバー側で URL 書き換えに使う。
364
+ */
365
+ readonly cacheImage: ((url: string) => Promise<string>) | undefined;
366
+ /**
367
+ * 画像プロキシのベース URL (`createClient({ imageProxyBase })`)。
368
+ * デフォルト `/api/images`。
369
+ */
370
+ readonly imageProxyBase: string;
323
371
  }
324
372
  /**
325
373
  * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。
326
374
  *
327
- * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createCMS`
375
+ * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createClient`
328
376
  * (低レベルのこの関数をラップしたもの) を経由する。
329
377
  *
330
378
  * @example
331
- * createCMS({
379
+ * createClient({
332
380
  * collections: {
333
381
  * posts: {
334
382
  * source: createNotionCollection({ token, dataSourceId, properties }),
@@ -341,7 +389,7 @@ interface CMSGlobalOps {
341
389
  * swr: { ttlMs: 5 * 60_000 },
342
390
  * });
343
391
  */
344
- 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>;
345
393
  //#endregion
346
394
  //#region src/rendering.d.ts
347
395
  /** 本文レンダリングに必要な依存を束ねたコンテキスト。 */
@@ -408,7 +456,7 @@ interface CollectionContext<T extends BaseContentItem> {
408
456
  */
409
457
  slugField: string;
410
458
  }
411
- /** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */
459
+ /** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */
412
460
  declare class CollectionClientImpl<T extends BaseContentItem> implements CollectionClient<T> {
413
461
  private readonly ctx;
414
462
  readonly cache: CollectionCacheOps<T>;
@@ -441,5 +489,5 @@ declare class CollectionClientImpl<T extends BaseContentItem> implements Collect
441
489
  private fetchRaw;
442
490
  }
443
491
  //#endregion
444
- 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 };
445
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
  }
@@ -226,6 +226,22 @@ async function buildCachedItemContent(item, ctx) {
226
226
  }
227
227
  });
228
228
  }
229
+ let notionBlocks;
230
+ if (ctx.source.loadNotionBlocks) try {
231
+ notionBlocks = await ctx.source.loadNotionBlocks(item);
232
+ } catch (err) {
233
+ if (isCMSError(err)) throw err;
234
+ throw new CMSError({
235
+ code: "source/load_blocks_failed",
236
+ message: "Failed to load Notion blocks from source.",
237
+ cause: err,
238
+ context: {
239
+ operation: "buildCachedItemContent:loadNotionBlocks",
240
+ pageId: item.id,
241
+ slug: item.slug
242
+ }
243
+ });
244
+ }
229
245
  const cacheImage = ctx.hasImageCache ? buildCacheImageFn(ctx.imgCache, ctx.imgCacheName, ctx.imageProxyBase, ctx.logger) : void 0;
230
246
  const rendererFn = ctx.rendererFn ?? await loadDefaultRenderer();
231
247
  let html;
@@ -254,6 +270,7 @@ async function buildCachedItemContent(item, ctx) {
254
270
  html,
255
271
  blocks,
256
272
  markdown,
273
+ notionBlocks,
257
274
  notionUpdatedAt: ctx.source.getLastModified(item),
258
275
  cachedAt: Date.now()
259
276
  };
@@ -315,7 +332,7 @@ async function withRetry(fn, config) {
315
332
  function collectionKey(collection, slug) {
316
333
  return slug ? `${collection}:${slug}` : collection;
317
334
  }
318
- /** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */
335
+ /** CollectionClient の実装。ユーザーは `createClient` 経由でインスタンスを受け取る。 */
319
336
  var CollectionClientImpl = class {
320
337
  cache;
321
338
  constructor(ctx) {
@@ -482,7 +499,8 @@ var CollectionClientImpl = class {
482
499
  async loadOrBuildContent(slug, item) {
483
500
  const expected = this.ctx.source.getLastModified(item);
484
501
  const cached = await this.ctx.docCache.getContent(this.ctx.collection, slug);
485
- if (cached && cached.notionUpdatedAt === expected) return cached;
502
+ const needsNotionBlocksBackfill = this.ctx.source.loadNotionBlocks !== void 0 && cached !== null && cached.notionBlocks === void 0;
503
+ if (cached && cached.notionUpdatedAt === expected && !needsNotionBlocksBackfill) return cached;
486
504
  const fresh = await buildCachedItemContent(item, this.ctx.render);
487
505
  await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);
488
506
  this.ctx.hooks.onContentRevalidated?.(slug, fresh);
@@ -528,7 +546,8 @@ var CollectionClientImpl = class {
528
546
  return Object.assign(Object.create(null), item, {
529
547
  html: async () => (await loadPayload()).html,
530
548
  markdown: async () => (await loadPayload()).markdown,
531
- blocks: async () => (await loadPayload()).blocks
549
+ blocks: async () => (await loadPayload()).blocks,
550
+ notionBlocks: async () => (await loadPayload()).notionBlocks
532
551
  });
533
552
  }
534
553
  async fetchList() {
@@ -912,11 +931,11 @@ function applyLogLevel(logger, minLevel) {
912
931
  /**
913
932
  * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。
914
933
  *
915
- * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createCMS`
934
+ * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createClient`
916
935
  * (低レベルのこの関数をラップしたもの) を経由する。
917
936
  *
918
937
  * @example
919
- * createCMS({
938
+ * createClient({
920
939
  * collections: {
921
940
  * posts: {
922
941
  * source: createNotionCollection({ token, dataSourceId, properties }),
@@ -929,26 +948,32 @@ function applyLogLevel(logger, minLevel) {
929
948
  * swr: { ttlMs: 5 * 60_000 },
930
949
  * });
931
950
  */
932
- function createCMS(opts) {
933
- 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({
934
959
  code: "core/config_invalid",
935
- message: "createCMS: collections に少なくとも 1 つのコレクションを指定してください。",
936
- context: { operation: "createCMS" }
960
+ message: "createClient: sources または collections に少なくとも 1 つのコレクションを指定してください。",
961
+ context: { operation: "createClient" }
937
962
  });
938
- for (const [name, def] of Object.entries(opts.collections)) {
963
+ for (const [name, def] of Object.entries(collectionsInput)) {
939
964
  if (!def.source) throw new CMSError({
940
965
  code: "core/config_invalid",
941
- message: `createCMS: コレクション "${name}" の source は必須です。`,
966
+ message: `createClient: コレクション "${name}" の source は必須です。`,
942
967
  context: {
943
- operation: "createCMS",
968
+ operation: "createClient",
944
969
  collection: name
945
970
  }
946
971
  });
947
972
  if (!def.slugField) throw new CMSError({
948
973
  code: "core/config_invalid",
949
- message: `createCMS: コレクション "${name}" の slugField は必須です。`,
974
+ message: `createClient: コレクション "${name}" の slugField は必須です。`,
950
975
  context: {
951
- operation: "createCMS",
976
+ operation: "createClient",
952
977
  collection: name
953
978
  }
954
979
  });
@@ -969,7 +994,7 @@ function createCMS(opts) {
969
994
  };
970
995
  const collectionNames = [];
971
996
  const collections = {};
972
- for (const [name, def] of Object.entries(opts.collections)) {
997
+ for (const [name, def] of Object.entries(collectionsInput)) {
973
998
  collectionNames.push(name);
974
999
  const source = def.source;
975
1000
  const colHooks = def.hooks;
@@ -1007,6 +1032,8 @@ function createCMS(opts) {
1007
1032
  }
1008
1033
  const globalOps = {
1009
1034
  collections: collectionNames,
1035
+ cacheImage: cacheRes.hasImg ? buildCacheImageFn(cacheRes.img, cacheRes.imgName, imageProxyBase, logger) : void 0,
1036
+ imageProxyBase,
1010
1037
  async invalidate(scope) {
1011
1038
  logger?.debug?.("グローバルキャッシュを無効化", {
1012
1039
  operation: "invalidate",
@@ -1018,7 +1045,7 @@ function createCMS(opts) {
1018
1045
  return createHandler({
1019
1046
  imageCache: cacheRes.img,
1020
1047
  async parseWebhookFor(collection, req, webhookSecret) {
1021
- const def = opts.collections[collection];
1048
+ const def = collectionsInput[collection];
1022
1049
  if (!def) throw new CMSError({
1023
1050
  code: "webhook/unknown_collection",
1024
1051
  message: `Unknown collection: ${collection}`,
@@ -1053,6 +1080,6 @@ function definePlugin(plugin) {
1053
1080
  return plugin;
1054
1081
  }
1055
1082
  //#endregion
1056
- 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 };
1057
1084
 
1058
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 const cacheImage = ctx.hasImageCache\n ? buildCacheImageFn(\n ctx.imgCache,\n ctx.imgCacheName,\n ctx.imageProxyBase,\n ctx.logger,\n )\n : undefined;\n\n const rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\n let html: string;\n try {\n html = await rendererFn(markdown, {\n imageProxyBase: ctx.imageProxyBase,\n cacheImage,\n remarkPlugins: ctx.contentConfig?.remarkPlugins,\n rehypePlugins: ctx.contentConfig?.rehypePlugins,\n });\n } catch (err) {\n if (isCMSError(err)) throw err;\n throw new CMSError({\n code: \"renderer/failed\",\n message: \"Failed to render markdown.\",\n cause: err,\n context: {\n operation: \"buildCachedItemContent:renderMarkdown\",\n pageId: item.id,\n slug: item.slug,\n },\n });\n }\n\n if (ctx.hooks.afterRender) {\n html = await ctx.hooks.afterRender(html, item);\n }\n\n let result: CachedItemContent = {\n html,\n blocks,\n markdown,\n notionUpdatedAt: ctx.source.getLastModified(item),\n cachedAt: Date.now(),\n };\n\n if (ctx.hooks.beforeCacheContent) {\n result = await ctx.hooks.beforeCacheContent(result, item);\n }\n\n const durationMs = Date.now() - start;\n ctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n slug: item.slug,\n durationMs,\n });\n ctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n return result;\n}\n","export interface RetryConfig {\n retryOn: number[];\n maxRetries: number;\n baseDelayMs: number;\n /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n jitter?: boolean;\n onRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n retryOn: [429, 502, 503],\n maxRetries: 4,\n baseDelayMs: 1000,\n jitter: true,\n};\n\n/**\n * 指数バックオフ(オプションでジッター付き)でリトライする。\n *\n * `config.retryOn` に含まれるステータスコードを持つエラーのみリトライ対象。\n * 遅延は `baseDelayMs * 2^attempt` の指数バックオフ。\n * `jitter` が `true`(デフォルト)の場合、0.5〜1.0 の乱数係数を乗算して\n * Thundering Herd を防ぐ。`false` にすると確定的な遅延になる。\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n config: RetryConfig,\n): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n const status = (err as { status?: number }).status;\n if (status === undefined || !config.retryOn.includes(status)) {\n throw err;\n }\n lastError = err;\n if (attempt < config.maxRetries) {\n config.onRetry?.(attempt + 1, status);\n const jitterFactor =\n config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n throw lastError;\n}\n","import { isStale } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItemContent, buildCachedItemMeta } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n AdjacencyOptions,\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n CheckResult,\n CMSHooks,\n CollectionCacheOps,\n CollectionClient,\n DataSource,\n DocumentCacheOps,\n FindOptions,\n ItemWithContent,\n ListOptions,\n Logger,\n SortOption,\n WarmOptions,\n WarmResult,\n WhereClause,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n *\n * (Cache adapter 内部のキー戦略はアダプタごとに異なるが、\n * 表示や再計算用に core 側でも公開ヘルパーを提供する)\n */\nexport function collectionKey(collection: string, slug?: string): string {\n return slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n collection: string;\n source: DataSource<T>;\n docCache: DocumentCacheOps;\n docCacheName: string;\n render: RenderContext<T>;\n hooks: CMSHooks<T>;\n logger: Logger | undefined;\n ttlMs: number | undefined;\n publishedStatuses: string[];\n accessibleStatuses: string[];\n retryConfig: RetryConfig;\n maxConcurrent: number;\n waitUntil: ((p: Promise<unknown>) => void) | undefined;\n /**\n * slug として使うフィールド名 (CLI 生成の `CollectionDef.slugField`)。\n * `source.properties[slugField].notion` を Notion プロパティ名として\n * `findByProp` を呼び出す。\n */\n slugField: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n implements CollectionClient<T>\n{\n readonly cache: CollectionCacheOps<T>;\n\n constructor(private readonly ctx: CollectionContext<T>) {\n this.cache = {\n invalidate: () => this.invalidateImpl(),\n invalidateItem: (slug: string) => this.invalidateItemImpl(slug),\n warm: (opts?: WarmOptions) => this.warmImpl(opts),\n };\n }\n\n // ── 基本取得 ──────────────────────────────────────────────────────────\n\n async find(\n slug: string,\n opts: FindOptions = {},\n ): Promise<ItemWithContent<T> | null> {\n // bypassCache: 強制ブロッキング取得\n if (opts.bypassCache) {\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n\n const cachedMeta = await this.ctx.docCache.getMeta<T>(\n this.ctx.collection,\n slug,\n );\n if (cachedMeta) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cachedMeta.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"キャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n return this.attachLazyContent(meta);\n }\n // SWR: キャッシュ即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateItemBg(slug, cachedMeta);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"キャッシュヒット\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n cachedAt: cachedMeta.cachedAt,\n });\n this.ctx.hooks.onCacheHit?.(slug, cachedMeta);\n return this.attachLazyContent(cachedMeta);\n }\n\n // メタ未キャッシュ: 同期フェッチ (保存はバックグラウンド可)\n this.ctx.logger?.debug?.(\"キャッシュミス、フェッチ\", {\n operation: \"find\",\n slug,\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onCacheMiss?.(slug);\n const item = await this.fetchRaw(slug);\n if (!item) return null;\n const meta = await this.persistMeta(slug, item, { background: true });\n return this.attachLazyContent(meta);\n }\n\n async list(opts?: ListOptions<T>): Promise<T[]> {\n const allItems = await this.fetchList();\n return applyListOptions(allItems, opts);\n }\n\n async params(): Promise<string[]> {\n const items = await this.fetchList();\n return items.map((item) => item.slug);\n }\n\n async check(\n slug: string,\n currentVersion: string,\n ): Promise<CheckResult<T> | null> {\n const raw = await this.fetchRaw(slug);\n if (!raw) return null;\n if (raw.lastEditedTime === currentVersion) return { stale: false };\n const meta = await this.persistMeta(slug, raw);\n await this.invalidateContentEntry(slug);\n return { stale: true, item: this.attachLazyContent(meta) };\n }\n\n async adjacent(\n slug: string,\n opts?: AdjacencyOptions<T>,\n ): Promise<{ prev: T | null; next: T | null }> {\n const items = applyListOptions(await this.fetchList(), {\n sort: opts?.sort,\n });\n const index = items.findIndex((it) => it.slug === slug);\n if (index === -1) return { prev: null, next: null };\n return {\n prev: index > 0 ? (items[index - 1] ?? null) : null,\n next: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n };\n }\n\n // ── キャッシュ操作 ────────────────────────────────────────────────────\n\n private async invalidateImpl(): Promise<void> {\n this.ctx.logger?.debug?.(\"コレクション全体のキャッシュを無効化\", {\n operation: \"cache.invalidate\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n await this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n }\n\n private async invalidateItemImpl(slug: string): Promise<void> {\n this.ctx.logger?.debug?.(\"アイテムキャッシュを無効化\", {\n operation: \"cache.invalidateItem\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n slug,\n });\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n });\n }\n\n private async warmImpl(opts?: WarmOptions): Promise<WarmResult> {\n const items = await this.fetchListRaw();\n const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n let ok = 0;\n const failed: Array<{ slug: string; error: unknown }> = [];\n\n for (let i = 0; i < items.length; i += concurrency) {\n const chunk = items.slice(i, i + concurrency);\n await Promise.all(\n chunk.map(async (item) => {\n try {\n await this.persistMeta(item.slug, item);\n const content = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(\n this.ctx.collection,\n item.slug,\n content,\n );\n ok++;\n } catch (err) {\n failed.push({ slug: item.slug, error: err });\n this.ctx.logger?.warn?.(\"warm: アイテムの事前レンダリングに失敗\", {\n slug: item.slug,\n pageId: item.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }),\n );\n opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n }\n\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return { ok, failed };\n }\n\n // ── 内部 ──────────────────────────────────────────────────────────────\n\n private async persistMeta(\n slug: string,\n item: T,\n opts: { background?: boolean } = {},\n ): Promise<CachedItemMeta<T>> {\n let meta = buildCachedItemMeta(item, this.ctx.source);\n if (this.ctx.hooks.beforeCacheMeta) {\n meta = await this.ctx.hooks.beforeCacheMeta(meta);\n }\n const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);\n if (opts.background && this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return meta;\n }\n\n private async invalidateContentEntry(slug: string): Promise<void> {\n await this.ctx.docCache.invalidate({\n collection: this.ctx.collection,\n slug,\n kind: \"content\",\n });\n }\n\n /**\n * 本文キャッシュをロードする。キャッシュが無いか、メタとの整合性が取れない場合は\n * 再生成して書き戻す。\n */\n private async loadOrBuildContent(\n slug: string,\n item: T,\n ): Promise<CachedItemContent> {\n const expected = this.ctx.source.getLastModified(item);\n const cached = await this.ctx.docCache.getContent(\n this.ctx.collection,\n slug,\n );\n if (cached && cached.notionUpdatedAt === expected) return cached;\n\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n return fresh;\n }\n\n /** メタ既知の状態で本文だけバックグラウンド再生成する。エラーは onSwrError フックに通知する。 */\n private async rebuildContentBg(slug: string, item: T): Promise<void> {\n try {\n const fresh = await buildCachedItemContent(item, this.ctx.render);\n await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);\n this.ctx.hooks.onContentRevalidated?.(slug, fresh);\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/content_rebuild_failed\",\n message: \"SWR background content rebuild failed.\",\n cause: err,\n context: {\n operation: \"swr.rebuildContentBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-content\", slug });\n this.ctx.logger?.warn?.(\"本文のバックグラウンド再生成に失敗\", {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private attachLazyContent(meta: CachedItemMeta<T>): ItemWithContent<T> {\n const slug = meta.item.slug;\n const item = meta.item;\n // 同一インスタンス内で本文ロードを集約する (複数呼び出しでも 1 回の I/O)\n let payloadPromise: Promise<CachedItemContent> | undefined;\n const loadPayload = (): Promise<CachedItemContent> => {\n if (!payloadPromise) {\n payloadPromise = this.loadOrBuildContent(slug, item);\n }\n return payloadPromise;\n };\n\n return Object.assign(Object.create(null) as object, item, {\n html: async () => (await loadPayload()).html,\n markdown: async () => (await loadPayload()).markdown,\n blocks: async () => (await loadPayload()).blocks,\n }) as ItemWithContent<T>;\n }\n\n private async fetchList(): Promise<T[]> {\n const cached = await this.ctx.docCache.getList<T>(this.ctx.collection);\n if (cached) {\n if (\n this.ctx.ttlMs !== undefined &&\n isStale(cached.cachedAt, this.ctx.ttlMs)\n ) {\n // TTL 切れ: ブロッキング再取得\n this.ctx.logger?.debug?.(\"リストキャッシュ期限切れ(TTL)、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n await this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt: Date.now(),\n });\n return items;\n }\n // SWR: 即時返却 + バックグラウンド差分チェック\n const bg = this.checkAndUpdateListBg(cached);\n if (this.ctx.waitUntil) this.ctx.waitUntil(bg);\n this.ctx.logger?.debug?.(\"リストキャッシュヒット\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheHit?.(cached);\n return cached.items;\n }\n\n // 未キャッシュ: 同期フェッチ\n this.ctx.logger?.debug?.(\"リストキャッシュミス、フェッチ\", {\n operation: \"list\",\n collection: this.ctx.collection,\n cacheAdapter: this.ctx.docCacheName,\n });\n this.ctx.hooks.onListCacheMiss?.();\n const items = await this.fetchListRaw();\n const cachedAt = Date.now();\n const save = this.ctx.docCache.setList(this.ctx.collection, {\n items,\n cachedAt,\n });\n if (this.ctx.waitUntil) {\n this.ctx.waitUntil(save);\n } else {\n await save;\n }\n return items;\n }\n\n private async checkAndUpdateItemBg(\n slug: string,\n cached: CachedItemMeta<T>,\n ): Promise<void> {\n try {\n const item = await this.fetchRaw(slug);\n if (!item) return;\n const lm = this.ctx.source.getLastModified(item);\n if (lm !== cached.notionUpdatedAt) {\n const meta = await this.persistMeta(slug, item);\n await this.invalidateContentEntry(slug);\n this.ctx.logger?.debug?.(\"SWR: 差分を検出、メタを差し替え\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n notionUpdatedAt: cached.notionUpdatedAt,\n });\n this.ctx.hooks.onCacheRevalidated?.(slug, meta);\n await this.rebuildContentBg(slug, item);\n } else if (this.ctx.ttlMs !== undefined) {\n // 変更なし + TTL あり: cachedAt をリセットして次回の期限切れを先送りする\n await this.ctx.docCache.setMeta(this.ctx.collection, slug, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: 差分なし、TTL をリセット\", {\n operation: \"find:bg\",\n slug,\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/item_check_failed\",\n message: \"SWR background item check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateItemBg\",\n collection: this.ctx.collection,\n slug,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"item-meta\", slug });\n this.ctx.logger?.warn?.(\n \"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n {\n slug,\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n try {\n const items = await this.fetchListRaw();\n if (\n this.ctx.source.getListVersion(items) !==\n this.ctx.source.getListVersion(cached.items)\n ) {\n const listEntry = { items, cachedAt: Date.now() };\n await this.ctx.docCache.setList(this.ctx.collection, listEntry);\n this.ctx.logger?.debug?.(\n \"SWR: リスト差分を検出、キャッシュを差し替え\",\n {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n },\n );\n this.ctx.hooks.onListCacheRevalidated?.(listEntry);\n } else if (this.ctx.ttlMs !== undefined) {\n await this.ctx.docCache.setList(this.ctx.collection, {\n ...cached,\n cachedAt: Date.now(),\n });\n this.ctx.logger?.debug?.(\"SWR: リスト差分なし、TTL をリセット\", {\n operation: \"list:bg\",\n collection: this.ctx.collection,\n });\n }\n } catch (err) {\n const cmsErr = isCMSError(err)\n ? err\n : new CMSError({\n code: \"swr/list_check_failed\",\n message: \"SWR background list check failed.\",\n cause: err,\n context: {\n operation: \"swr.checkAndUpdateListBg\",\n collection: this.ctx.collection,\n },\n });\n this.ctx.hooks.onSwrError?.(cmsErr, { phase: \"list\" });\n this.ctx.logger?.warn?.(\n \"SWR: リストのバックグラウンド差分チェックに失敗\",\n {\n collection: this.ctx.collection,\n code: cmsErr.code,\n error: err instanceof Error ? err.message : String(err),\n },\n );\n }\n }\n\n private async fetchListRaw(): Promise<T[]> {\n const items = await withRetry(\n () =>\n this.ctx.source.list({\n publishedStatuses:\n this.ctx.publishedStatuses.length > 0\n ? this.ctx.publishedStatuses\n : undefined,\n }),\n {\n ...this.ctx.retryConfig,\n onRetry: (attempt, status) => {\n this.ctx.logger?.warn?.(\"list() リトライ中\", { attempt, status });\n },\n },\n );\n return items.filter((item) => {\n if (item.isArchived || item.isInTrash) return false;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n )\n return false;\n return true;\n });\n }\n\n private async fetchRaw(slug: string): Promise<T | null> {\n const retryOpts = {\n ...this.ctx.retryConfig,\n onRetry: (attempt: number, status: number) => {\n this.ctx.logger?.warn?.(\"find() リトライ中\", {\n attempt,\n status,\n slug,\n });\n },\n };\n\n // slugField から Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n const notionPropName =\n this.ctx.source.properties?.[this.ctx.slugField]?.notion;\n\n let item: T | null;\n const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n if (notionPropName && findByProp) {\n item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n } else {\n // フォールバック: list して線形探索\n const all = await withRetry(() => this.ctx.source.list(), retryOpts);\n item = all.find((i) => i.slug === slug) ?? null;\n }\n\n if (!item) return null;\n if (item.isArchived || item.isInTrash) return null;\n if (\n this.ctx.accessibleStatuses.length > 0 &&\n (!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n ) {\n return null;\n }\n return item;\n }\n}\n\nfunction matchesWhere<T extends BaseContentItem>(\n item: T,\n where: WhereClause<T>,\n): boolean {\n for (const key of Object.keys(where) as (keyof T & string)[]) {\n const expected = where[key];\n const actual = item[key];\n if (Array.isArray(expected)) {\n if (!(expected as readonly unknown[]).includes(actual)) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction applyListOptions<T extends BaseContentItem>(\n items: T[],\n opts?: ListOptions<T>,\n): T[] {\n if (!opts) return sortByPublishedAtDesc(items);\n let result = items;\n\n if (opts.statuses) {\n const allow = new Set(\n Array.isArray(opts.statuses) ? opts.statuses : [opts.statuses],\n );\n result = result.filter((it) => it.status != null && allow.has(it.status));\n }\n\n if (opts.tag) {\n const tag = opts.tag;\n result = result.filter((it) => {\n const tags = (it as { tags?: string[] }).tags;\n return Array.isArray(tags) && tags.includes(tag);\n });\n }\n\n if (opts.where) {\n const where = opts.where;\n result = result.filter((it) => matchesWhere(it, where));\n }\n\n if (opts.filter) {\n result = result.filter(opts.filter);\n }\n\n if (opts.sort) {\n result = [...result].sort(makeComparator(opts.sort));\n } else {\n result = sortByPublishedAtDesc(result);\n }\n\n const skip = opts.skip ?? 0;\n const limit = opts.limit;\n if (skip > 0 || limit !== undefined) {\n result = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n }\n\n return result;\n}\n\n/** publishedAt 降順、未設定の場合は lastEditedTime 降順でソートする。 */\nfunction sortByPublishedAtDesc<T extends BaseContentItem>(items: T[]): T[] {\n return [...items].sort((a, b) => {\n // lastEditedTime は必須なので av/bv は常に truthy\n const av = a.publishedAt ?? a.lastEditedTime;\n const bv = b.publishedAt ?? b.lastEditedTime;\n if (av === bv) return 0;\n return av > bv ? -1 : 1;\n });\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n sort: SortOption<T>,\n): (a: T, b: T) => number {\n if (sort.compare) return sort.compare;\n const by = sort.by as keyof T;\n const dir = sort.dir === \"asc\" ? 1 : -1;\n return (a, b) => {\n const av = a[by];\n const bv = b[by];\n if (av === bv) return 0;\n if (av === undefined || av === null) return 1;\n if (bv === undefined || bv === null) return -1;\n if (typeof av === \"string\" && typeof bv === \"string\") {\n return av > bv ? dir : -dir;\n }\n if (typeof av === \"number\" && typeof bv === \"number\") {\n return av > bv ? dir : -dir;\n }\n throw new CMSError({\n code: \"core/sort_unsupported_type\",\n message: `\"${String(by)}\" フィールドの型 \"${typeof av}\" はソート非対応です。compare 関数を指定してください。`,\n context: {\n operation: \"makeComparator\",\n field: String(by),\n type: typeof av,\n },\n });\n };\n}\n","import { isCMSError } from \"./errors\";\nimport type { ImageCacheOps, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n /** マウントするベースパス。デフォルト `/api/cms`。 */\n basePath?: string;\n /** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n imagesPath?: string;\n /** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n revalidatePath?: string;\n /** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n webhookSecret?: string;\n /** デフォルト実装を無効化する場合 true。 */\n disabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n imageCache: ImageCacheOps;\n /**\n * 指定コレクションの DataSource.parseWebhook を呼ぶ。\n * 未知コレクション → `webhook/unknown_collection` CMSError\n * parseWebhook 未実装 → `webhook/not_implemented` CMSError\n */\n parseWebhookFor(\n collection: string,\n req: Request,\n webhookSecret: string | undefined,\n ): Promise<InvalidateScope>;\n revalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n basePath: \"/api/cms\",\n imagesPath: \"/images\",\n revalidatePath: \"/revalidate\",\n} as const;\n\n/**\n * CMSError のコードから HTTP ステータスコードを返す。\n * 既知の webhook エラーコードのみ対応し、それ以外は null を返す。\n */\nfunction webhookErrorStatus(code: string): number | null {\n if (code === \"webhook/signature_invalid\") return 401;\n if (code === \"webhook/not_implemented\") return 501;\n if (code === \"webhook/unknown_collection\") return 404;\n if (code === \"webhook/payload_invalid\") return 400;\n return null;\n}\n\n/**\n * Web Standard な Request → Response ルーター。\n * Next.js / React Router / Hono / Cloudflare Workers いずれでも使える。\n *\n * ルート:\n * - GET `{basePath}/images/:hash` — 画像プロキシ\n * - POST `{basePath}/revalidate/:collection` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n adapter: HandlerAdapter,\n opts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n const basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n const imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n const revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (!path.startsWith(basePath)) {\n return new Response(\"Not Found\", { status: 404 });\n }\n const rel = path.slice(basePath.length) || \"/\";\n\n // 画像: GET {basePath}/images/:hash\n if (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n const hash = rel.slice(imagesPath.length + 1);\n if (!hash) return new Response(\"Bad Request\", { status: 400 });\n const object = await adapter.imageCache.get(hash);\n if (!object) return new Response(\"Not Found\", { status: 404 });\n const headers = new Headers();\n if (object.contentType) headers.set(\"content-type\", object.contentType);\n headers.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n return new Response(object.data, { headers });\n }\n\n // Revalidate: POST {basePath}/revalidate/:collection\n if (req.method === \"POST\" && rel.startsWith(`${revalidatePath}/`)) {\n const collection = rel.slice(revalidatePath.length + 1);\n if (!collection || collection.includes(\"/\")) {\n return new Response(\n JSON.stringify({ ok: false, reason: \"collection required\" }),\n { status: 400, headers: { \"content-type\": \"application/json\" } },\n );\n }\n try {\n const scope = await adapter.parseWebhookFor(\n collection,\n req,\n opts.webhookSecret,\n );\n await adapter.revalidate(scope);\n return new Response(JSON.stringify({ ok: true, scope }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch (err) {\n if (isCMSError(err)) {\n const status = webhookErrorStatus(err.code);\n if (status !== null) {\n return new Response(JSON.stringify({ ok: false, code: err.code }), {\n status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n }\n throw err;\n }\n }\n\n return new Response(\"Not Found\", { status: 404 });\n };\n}\n\nfunction trimTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { noopDocOps, noopImgOps } from \"./cache/noop\";\nimport { CollectionClientImpl, type CollectionContext } from \"./collection\";\nimport { CMSError } from \"./errors\";\nimport { createHandler, type HandlerOptions } from \"./handler\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n BaseContentItem,\n CacheAdapter,\n CMSHooks,\n CollectionClient,\n CollectionsConfig,\n CreateCMSOptions,\n DataSource,\n DocumentCacheOps,\n ImageCacheOps,\n InferCollectionItem,\n InvalidateScope,\n Logger,\n LogLevel,\n RendererFn,\n StorageBinary,\n} from \"./types/index\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<C>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<C extends CollectionsConfig> = {\n [K in keyof C]: CollectionClient<InferCollectionItem<C[K]>>;\n} & CMSGlobalOps;\n\n/** `CMSClient` のグローバル名前空間。 */\nexport interface CMSGlobalOps {\n /** 登録されているコレクション名の一覧。 */\n readonly collections: readonly string[];\n /** 全コレクションまたは特定スコープのキャッシュを無効化する。 */\n invalidate(scope?: InvalidateScope): Promise<void>;\n /** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n /** ハッシュキーでキャッシュ画像を取得する。 */\n getCachedImage(hash: string): Promise<StorageBinary | null>;\n}\n\ninterface ResolvedCache {\n doc: DocumentCacheOps;\n docName: string;\n img: ImageCacheOps;\n imgName: string;\n hasImg: boolean;\n}\n\n/**\n * `cache` オプションから document / image オペレーションを解決する。\n *\n * - 各 adapter の `handles` を見て先勝ち (最初に見つかったもの) で振り分ける\n * - 未指定なら両方 noop\n */\nfunction resolveCache(\n cache: readonly CacheAdapter[] | undefined,\n): ResolvedCache {\n const adapters = cache ?? [];\n\n let doc: DocumentCacheOps = noopDocOps;\n let docName = \"noop-document\";\n let img: ImageCacheOps = noopImgOps;\n let imgName = \"noop-image\";\n let docFound = false;\n let imgFound = false;\n\n for (const adapter of adapters) {\n if (!docFound && adapter.handles.includes(\"document\") && adapter.doc) {\n doc = adapter.doc;\n docName = adapter.name;\n docFound = true;\n }\n if (!imgFound && adapter.handles.includes(\"image\") && adapter.img) {\n img = adapter.img;\n imgName = adapter.name;\n imgFound = true;\n }\n }\n\n return { doc, docName, img, imgName, hasImg: imgFound };\n}\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/** `logger` から `minLevel` 未満のレベルを除いた新しい Logger を返す。 */\nfunction applyLogLevel(\n logger: Logger | undefined,\n minLevel: LogLevel,\n): Logger | undefined {\n if (!logger) return undefined;\n const minOrder = LOG_LEVEL_ORDER[minLevel];\n const filtered: Logger = {};\n for (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n if (LOG_LEVEL_ORDER[level] >= minOrder) {\n filtered[level] = logger[level];\n }\n }\n return filtered;\n}\n\n/**\n * 複数の `CollectionDef` を束ねた CMS クライアントを生成する。\n *\n * 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createCMS`\n * (低レベルのこの関数をラップしたもの) を経由する。\n *\n * @example\n * createCMS({\n * collections: {\n * posts: {\n * source: createNotionCollection({ token, dataSourceId, properties }),\n * slugField: \"slug\",\n * statusField: \"status\",\n * publishedStatuses: [\"公開済み\"],\n * }\n * },\n * cache: [memoryCache()],\n * swr: { ttlMs: 5 * 60_000 },\n * });\n */\nexport function createCMS<C extends CollectionsConfig>(\n opts: CreateCMSOptions<C>,\n): CMSClient<C> {\n if (!opts.collections || Object.keys(opts.collections).length === 0) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message:\n \"createCMS: collections に少なくとも 1 つのコレクションを指定してください。\",\n context: { operation: \"createCMS\" },\n });\n }\n\n for (const [name, def] of Object.entries(opts.collections)) {\n if (!def.source) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の source は必須です。`,\n context: { operation: \"createCMS\", collection: name },\n });\n }\n if (!def.slugField) {\n throw new CMSError({\n code: \"core/config_invalid\",\n message: `createCMS: コレクション \"${name}\" の slugField は必須です。`,\n context: { operation: \"createCMS\", collection: name },\n });\n }\n }\n\n const cacheRes = resolveCache(opts.cache);\n const ttlMs = opts.swr?.ttlMs;\n const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n const contentConfig = opts.content;\n const rendererFn: RendererFn | undefined = opts.renderer;\n const waitUntil = opts.waitUntil;\n const baseLogger: Logger | undefined = mergeLoggers(\n opts.plugins ?? [],\n opts.logger,\n );\n const logger = opts.logLevel\n ? applyLogLevel(baseLogger, opts.logLevel)\n : baseLogger;\n const hooks: CMSHooks<BaseContentItem> = mergeHooks(\n opts.plugins ?? [],\n opts.hooks,\n logger,\n );\n const maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n const retryConfig: RetryConfig = {\n ...DEFAULT_RETRY_CONFIG,\n ...(opts.rateLimiter ?? {}),\n };\n\n const collectionNames: (keyof C & string)[] = [];\n const collections: Record<string, CollectionClient<BaseContentItem>> = {};\n for (const [name, def] of Object.entries(opts.collections)) {\n collectionNames.push(name as keyof C & string);\n const source = def.source as DataSource<BaseContentItem>;\n const colHooks = def.hooks as CMSHooks<BaseContentItem> | undefined;\n const collectionHooks: CMSHooks<BaseContentItem> = colHooks\n ? mergeHooks([{ name: `${name}:global`, hooks }], colHooks, logger)\n : hooks;\n const renderCtx: RenderContext<BaseContentItem> = {\n source,\n rendererFn,\n imgCache: cacheRes.img,\n imgCacheName: cacheRes.imgName,\n hasImageCache: cacheRes.hasImg,\n imageProxyBase,\n contentConfig,\n hooks: collectionHooks,\n logger,\n };\n const ctx: CollectionContext<BaseContentItem> = {\n collection: name,\n source,\n docCache: cacheRes.doc,\n docCacheName: cacheRes.docName,\n render: renderCtx,\n hooks: collectionHooks,\n logger,\n ttlMs,\n publishedStatuses: def.publishedStatuses\n ? [...def.publishedStatuses]\n : [],\n accessibleStatuses: def.accessibleStatuses\n ? [...def.accessibleStatuses]\n : [],\n retryConfig,\n maxConcurrent,\n waitUntil,\n slugField: def.slugField,\n };\n collections[name] = new CollectionClientImpl(ctx);\n }\n\n const globalOps: CMSGlobalOps = {\n collections: collectionNames,\n async invalidate(scope?: InvalidateScope): Promise<void> {\n logger?.debug?.(\"グローバルキャッシュを無効化\", {\n operation: \"invalidate\",\n cacheAdapter: cacheRes.docName,\n });\n await cacheRes.doc.invalidate(scope ?? \"all\");\n },\n handler(handlerOpts?: HandlerOptions) {\n return createHandler(\n {\n imageCache: cacheRes.img,\n async parseWebhookFor(collection, req, webhookSecret) {\n const def = opts.collections[collection];\n if (!def) {\n throw new CMSError({\n code: \"webhook/unknown_collection\",\n message: `Unknown collection: ${collection}`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n const ds = def.source as DataSource<BaseContentItem>;\n if (!ds.parseWebhook) {\n throw new CMSError({\n code: \"webhook/not_implemented\",\n message: `Collection \"${collection}\" does not support webhooks.`,\n context: { operation: \"parseWebhookFor\", collection },\n });\n }\n return ds.parseWebhook(req, { secret: webhookSecret });\n },\n revalidate: (scope) => globalOps.invalidate(scope),\n },\n handlerOpts,\n );\n },\n getCachedImage(hash) {\n return cacheRes.img.get(hash);\n },\n };\n\n return Object.assign(\n Object.create(null) as object,\n collections,\n globalOps,\n ) as CMSClient<C>;\n}\n","import type { BaseContentItem } from \"./content\";\nimport type { CMSHooks } from \"./hooks\";\nimport type { Logger } from \"./logger\";\n\nexport interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {\n name: string;\n hooks?: CMSHooks<T>;\n logger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n plugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/cache/noop.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n const data = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n if (ttlMs === undefined) return false;\n return Date.now() - cachedAt > ttlMs;\n}\n","import type {\n BaseContentItem,\n CachedItemContent,\n CachedItemList,\n CachedItemMeta,\n DocumentCacheOps,\n ImageCacheOps,\n StorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */\nconst noopDoc: DocumentCacheOps = {\n getList<T extends BaseContentItem>(\n _collection: string,\n ): Promise<CachedItemList<T> | null> {\n return Promise.resolve(null);\n },\n setList<T extends BaseContentItem>(\n _collection: string,\n _data: CachedItemList<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemMeta<T> | null> {\n return Promise.resolve(null);\n },\n setMeta<T extends BaseContentItem>(\n _collection: string,\n _slug: string,\n _data: CachedItemMeta<T>,\n ): Promise<void> {\n return Promise.resolve();\n },\n getContent(\n _collection: string,\n _slug: string,\n ): Promise<CachedItemContent | null> {\n return Promise.resolve(null);\n },\n setContent(\n _collection: string,\n _slug: string,\n _data: CachedItemContent,\n ): Promise<void> {\n return Promise.resolve();\n },\n invalidate(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/** 何もキャッシュしない画像オペレーション。 */\nconst noopImg: ImageCacheOps = {\n get(_hash: string): Promise<StorageBinary | null> {\n return Promise.resolve(null);\n },\n set(): Promise<void> {\n return Promise.resolve();\n },\n};\n\n/**\n * 何もキャッシュしないアダプタ。`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-D7PfY-bV.mjs";
1
+ import { a as CachedItemMeta, i as CachedItemList, l as ImageRef, o as StorageBinary, r as CachedItemContent, s as ContentBlock, t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
2
 
3
3
  //#region src/types/data-source.d.ts
4
4
  /**
@@ -68,6 +68,13 @@ interface DataSource<T extends BaseContentItem = BaseContentItem> {
68
68
  loadBlocks(item: T): Promise<ContentBlock[]>;
69
69
  /** アイテム本文を Markdown 文字列で返す (html() 生成の元ソース)。 */
70
70
  loadMarkdown(item: T): Promise<string>;
71
+ /**
72
+ * Notion API のブロックツリー(`BlockObjectResponse + children`)を返す。
73
+ * 実装している場合のみ core がコンテンツキャッシュへ保存し、
74
+ * react-renderer など Notion 固有形式を消費する利用側で再利用できるようにする。
75
+ * core はゼロ依存ルールに従い `unknown[]` 型として扱う。
76
+ */
77
+ loadNotionBlocks?(item: T): Promise<unknown[]>;
71
78
  /** SWR 鮮度判定用。item の最終更新タイムスタンプ。 */
72
79
  getLastModified(item: T): string;
73
80
  /** リスト全体のバージョン文字列 (例: 最新 last_edited_time)。 */
@@ -105,7 +112,7 @@ interface ImageCacheOps {
105
112
  * 統一キャッシュアダプタ。`handles` で担当領域を申告し、
106
113
  * `doc` / `img` のいずれか(または両方)を実装する。
107
114
  *
108
- * `createCMS({ cache })` には `CacheAdapter | CacheAdapter[]` を渡せる。
115
+ * `createClient({ cache })` には `CacheAdapter | CacheAdapter[]` を渡せる。
109
116
  * 配列で渡された場合、core は `handles` を見て document / image をそれぞれ別アダプタに振り分ける。
110
117
  *
111
118
  * @example
@@ -143,4 +150,4 @@ interface MemoryCacheOptions extends MemoryDocumentOptions, MemoryImageOptions {
143
150
  declare function memoryCache(options?: MemoryCacheOptions): CacheAdapter;
144
151
  //#endregion
145
152
  export { CacheAdapter as a, DataSource as c, PropertyDef as d, PropertyMap as f, memoryCache as i, InvalidateKind as l, MemoryDocumentOptions as n, DocumentCacheOps as o, WebhookConfig as p, MemoryImageOptions as r, ImageCacheOps as s, MemoryCacheOptions as t, InvalidateScope as u };
146
- //# sourceMappingURL=memory-BKDsuGVN.d.mts.map
153
+ //# sourceMappingURL=memory-Ct4NDOFO.d.mts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notion-headless-cms/core",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
5
5
  "keywords": [
6
6
  "notion",