@notion-headless-cms/core 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,28 +1,78 @@
1
1
  # @notion-headless-cms/core
2
2
 
3
- CMS エンジン本体。データソース・キャッシュ・レンダラーを統合し、Stale-While-Revalidate / 更新検知 / クエリビルダー / フック / リトライを提供する。**外部ランタイム依存ゼロ**。
3
+ CMS エンジン本体。`createCMS` を提供し、SWR / 更新検知 / フック /
4
+ リトライ / Web Standard Route Handler を内蔵する。**外部ランタイム依存ゼロ**。
4
5
 
5
6
  ## インストール
6
7
 
7
8
  ```bash
8
- npm install @notion-headless-cms/core
9
+ pnpm add @notion-headless-cms/core @notion-headless-cms/notion-orm \
10
+ @notion-headless-cms/renderer @notionhq/client zod \
11
+ unified remark-parse remark-gfm remark-rehype rehype-stringify
9
12
  ```
10
13
 
11
- `core` 自体は外部ランタイム依存ゼロ。`@notion-headless-cms/renderer` `peerDependenciesMeta.optional: true` のため、renderer を `createCMS({ renderer })` で明示注入するか、動的フォールバック(`import("@notion-headless-cms/renderer")`)を使う場合のみインストールが必要。
14
+ `core` 自体は依存ゼロだが、実際に Notion を叩くには `notion-orm`、
15
+ HTML 化には `renderer` が必要。CLI 生成物 (`nhc-schema.ts`) が
16
+ notion-orm を自動参照する。
12
17
 
13
- Cloudflare Workers / Node.js / Next.js などで使う場合は、それぞれのアダプタを使うのが最短ルート:
18
+ ## 基本形
14
19
 
15
- - [`@notion-headless-cms/adapter-cloudflare`](../adapter-cloudflare)
16
- - [`@notion-headless-cms/adapter-node`](../adapter-node)
17
- - [`@notion-headless-cms/adapter-next`](../adapter-next)
20
+ ```ts
21
+ import { createCMS, nodePreset } from "@notion-headless-cms/core";
22
+ import { cmsDataSources } from "./generated/nhc-schema";
18
23
 
19
- ### サブパスエクスポート
24
+ const cms = createCMS({
25
+ ...nodePreset({ ttlMs: 5 * 60_000 }),
26
+ dataSources: cmsDataSources,
27
+ });
28
+
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());
32
+ ```
20
33
 
21
- 一部の機能はサブパス経由でインポートでき、バレル経由より小さくバンドルできる。
34
+ ## 公開 API
35
+
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
+
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
49
+
50
+ ### `cms.<collection>` の主なメソッド
51
+ - `getItem(slug)` — 本文込みで単件取得 (SWR)。返り値は `T & { content: { blocks, html(), markdown() } }`
52
+ - `getList(opts?)` — 公開済み一覧 (本文なし)
53
+ - `getStaticParams()` / `getStaticPaths()` — SSG 用
54
+ - `adjacent(slug, opts?)` — 前後記事ナビゲーション
55
+ - `revalidate(scope?)` / `prefetch(opts?)` — コレクション別キャッシュ操作
56
+
57
+ ### グローバル操作
58
+ - `cms.$collections` — コレクション名一覧
59
+ - `cms.$revalidate(scope?)` — `"all" | { collection } | { collection, slug }`
60
+ - `cms.$handler(opts?)` — Web Standard `(req) => Response` を返す (画像プロキシ + webhook)
61
+ - `cms.$getCachedImage(hash)` — 画像キャッシュ直アクセス
62
+
63
+ ### cache アダプタ
64
+ - `memoryDocumentCache({ maxItems? })` — LRU 対応インメモリキャッシュ
65
+ - `memoryImageCache({ maxItems?, maxSizeBytes? })`
66
+ - `noopDocumentCache()` / `noopImageCache()`
67
+
68
+ ### エラー
69
+ - `CMSError` / `isCMSError` / `isCMSErrorInNamespace(err, "core/")`
70
+ - 名前空間: `core/*` / `source/*` / `cache/*` / `renderer/*` / `cli/*`
71
+
72
+ ## サブパスエクスポート
22
73
 
23
74
  ```ts
24
75
  import { CMSError } from "@notion-headless-cms/core/errors";
25
- import { mergeHooks } from "@notion-headless-cms/core/hooks";
26
76
  import { memoryDocumentCache } from "@notion-headless-cms/core/cache/memory";
27
77
  ```
28
78
 
@@ -31,173 +81,17 @@ import { memoryDocumentCache } from "@notion-headless-cms/core/cache/memory";
31
81
  | `@notion-headless-cms/core` | 全エクスポート |
32
82
  | `@notion-headless-cms/core/errors` | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` |
33
83
  | `@notion-headless-cms/core/hooks` | `mergeHooks` / `mergeLoggers` |
34
- | `@notion-headless-cms/core/cache/memory` | `memoryDocumentCache` / `memoryImageCache` / `*Options` |
84
+ | `@notion-headless-cms/core/cache/memory` | `memoryDocumentCache` / `memoryImageCache` |
35
85
  | `@notion-headless-cms/core/cache/noop` | `noopDocumentCache` / `noopImageCache` |
36
86
 
37
- ## 使い方(core を直接使う場合)
38
-
39
- ```typescript
40
- import { createCMS, memoryDocumentCache, memoryImageCache } from "@notion-headless-cms/core";
41
- import { notionAdapter } from "@notion-headless-cms/source-notion";
42
- import { renderMarkdown } from "@notion-headless-cms/renderer";
43
-
44
- const cms = createCMS({
45
- source: notionAdapter({
46
- token: process.env.NOTION_TOKEN!,
47
- dataSourceId: process.env.NOTION_DATA_SOURCE_ID!,
48
- }),
49
- renderer: renderMarkdown,
50
- schema: {
51
- publishedStatuses: ["公開"],
52
- properties: { slug: "Slug" },
53
- },
54
- cache: {
55
- document: memoryDocumentCache({ maxItems: 500 }),
56
- image: memoryImageCache({ maxItems: 200, maxSizeBytes: 64 * 1024 * 1024 }),
57
- ttlMs: 5 * 60 * 1000,
58
- },
59
- });
60
-
61
- // ソース直接
62
- const items = await cms.list();
63
- const item = await cms.find("my-post");
64
- const rendered = item ? await cms.render(item) : null;
65
-
66
- // SWR
67
- const { items: cachedItems } = await cms.cache.getList();
68
- const cached = await cms.cache.get("my-post");
69
- console.log(cached?.html);
70
- ```
71
-
72
- ### `CacheConfig`
73
-
74
- ```ts
75
- type CacheConfig<T> =
76
- | "disabled" // 完全無効化
77
- | {
78
- document?: DocumentCacheAdapter<T>; // 未指定なら noop
79
- image?: ImageCacheAdapter; // 未指定なら noop
80
- ttlMs?: number; // 未指定は TTL なし
81
- };
82
- ```
83
-
84
- ### カスタムコンテンツ型
85
-
86
- `BaseContentItem` を拡張することで任意のプロパティを追加できる。
87
-
88
- ```typescript
89
- import type { BaseContentItem } from "@notion-headless-cms/core";
90
- import { createCMS } from "@notion-headless-cms/core";
91
- import { notionAdapter } from "@notion-headless-cms/source-notion";
92
-
93
- interface MyPost extends BaseContentItem {
94
- title: string;
95
- category: string;
96
- }
97
-
98
- const cms = createCMS<MyPost>({
99
- source: notionAdapter<MyPost>({
100
- token: process.env.NOTION_TOKEN!,
101
- dataSourceId: process.env.NOTION_DATA_SOURCE_ID!,
102
- mapItem: (page) => ({
103
- id: page.id,
104
- slug: /* ... */ "",
105
- updatedAt: page.last_edited_time,
106
- publishedAt: page.created_time,
107
- title: /* ... */ "",
108
- category: /* ... */ "",
109
- }),
110
- }),
111
- });
112
- ```
113
-
114
- > 型安全なマッピングは [`defineSchema`](../source-notion) を推奨。
115
-
116
- ## 主要 API
117
-
118
- ### `CMS<T>` / `createCMS<T>(options)`
119
-
120
- | メソッド | 説明 |
121
- |---|---|
122
- | `list()` | ソースから一覧取得 |
123
- | `find(slug)` | ソースから単一アイテム取得 |
124
- | `findMany(slugs[])` | 複数スラッグを並列取得(`Map<string, T>` を返す) |
125
- | `render(item)` | Markdown → HTML にレンダリング |
126
- | `isPublished(item)` | `publishedStatuses` 判定 |
127
- | `cache.getList()` | SWR で一覧取得 |
128
- | `cache.get(slug)` | SWR で単一アイテム取得 |
129
- | `cache.prefetchAll(opts?)` | 全アイテムを事前レンダリング |
130
- | `cache.revalidate(scope?)` | キャッシュ無効化 |
131
- | `cache.sync(payload?)` | Webhook 由来のキャッシュ同期 |
132
- | `cache.checkList(version)` | 一覧差分検知 |
133
- | `cache.checkItem(slug, lastEdited)` | 個別差分検知 |
134
- | `query()` | QueryBuilder を返す |
135
- | `getStaticSlugs()` | 静的生成用スラッグ一覧 |
136
- | `getCachedImage(hash)` | キャッシュ画像を取得 |
137
- | `createCachedImageResponse(hash)` | キャッシュ画像の Response を生成 |
138
-
139
- 詳細は [CMS メソッド一覧](../../docs/api/cms-methods.md) を参照。
140
-
141
- ### ユーティリティ
142
-
143
- | エクスポート | 説明 |
144
- |---|---|
145
- | `memoryDocumentCache<T>({ maxItems? })` | インメモリ DocumentCacheAdapter。`maxItems` 指定時は LRU 方式で古いものから削除 |
146
- | `memoryImageCache({ maxItems?, maxSizeBytes? })` | インメモリ ImageCacheAdapter。件数とバイト数の両方で上限を設定可 |
147
- | `noopDocumentCache<T>()` / `noopImageCache()` | 何もしないアダプタ |
148
- | `isStale(cachedAt, ttlMs)` | TTL 切れ判定 |
149
- | `sha256Hex(data)` | SHA256 ハッシュ生成 |
150
- | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` | エラークラスと判定ヘルパー |
151
- | `QueryBuilder` | クエリ組み立て(`cms.query()` が返す) |
152
- | `withRetry` / `DEFAULT_RETRY_CONFIG` | リトライ付き実行 |
153
- | `mergeHooks` / `mergeLoggers` | プラグイン・直接指定のフック/ロガーを統合 |
154
- | `definePlugin` | プラグイン(hooks / logger)の型付き定義 |
155
-
156
- ### ライフサイクルフック
157
-
158
- `createCMS({ hooks })` に指定する。観測フック(`onCache*` / `onRender*`)は例外を投げても他のフックに波及せず、`logger.error` に流される。
159
-
160
- | フック | 呼び出しタイミング |
161
- |---|---|
162
- | `beforeCache(item)` | キャッシュ書き込み直前(結果差し替え可) |
163
- | `afterRender(html, item)` | HTML 生成直後(差し替え可) |
164
- | `onCacheHit(slug, item)` / `onCacheMiss(slug)` | アイテム SWR のヒット・ミス |
165
- | `onListCacheHit(items, cachedAt)` / `onListCacheMiss()` | 一覧 SWR のヒット・ミス |
166
- | `onRenderStart(slug)` / `onRenderEnd(slug, durationMs)` | レンダリング所要時間の観測 |
167
- | `onError(error)` | 内部エラー通知 |
168
-
169
- ## 主要な型
170
-
171
- - `CreateCMSOptions<T>` — `createCMS()` の引数
172
- - `BaseContentItem` — デフォルト・カスタム型の基底(`status` / `publishedAt` はオプション)
173
- - `CachedItem<T>` / `CachedItemList<T>` — キャッシュ済みコンテンツ
174
- - `CacheAccessor<T>` — `cms.cache` の型(外部で型引数として使いたい場合に)
175
- - `DataSourceAdapter<T>` — データソース抽象
176
- - `DocumentCacheAdapter<T>` / `ImageCacheAdapter` — キャッシュ抽象
177
- - `RendererFn` / `RenderOptions` — レンダラー関数の型
178
- - `CMSErrorCode` / `CMSErrorContext` — エラー型(名前空間付き)
179
-
180
- ## エラー体系
181
-
182
- すべての内部エラーは `CMSError` に統一される。コードは `namespace/kind` 形式。
183
-
184
- ```ts
185
- import { isCMSErrorInNamespace } from "@notion-headless-cms/core";
186
-
187
- try {
188
- await cms.list();
189
- } catch (err) {
190
- if (isCMSErrorInNamespace(err, "source/")) {
191
- // Notion 取得系エラー
192
- }
193
- }
194
- ```
87
+ ## ランタイム別レシピ
195
88
 
196
- 組み込みコード: `core/config_invalid` / `core/schema_invalid` / `source/fetch_items_failed` / `source/fetch_item_failed` / `source/load_markdown_failed` / `cache/io_failed` / `cache/image_fetch_failed` / `renderer/failed`
89
+ - [Node.js スクリプト](../../docs/recipes/nodejs-script.md)
90
+ - [Cloudflare Workers + R2 + KV](../../docs/recipes/cloudflare-workers.md) (`cloudflarePreset` は `cache-r2` パッケージ)
91
+ - [Next.js App Router](../../docs/recipes/nextjs-app-router.md)
197
92
 
198
- ## 関連パッケージ
93
+ ## 詳細ドキュメント
199
94
 
200
- - [`@notion-headless-cms/source-notion`](../source-notion) — Notion データソース
201
- - [`@notion-headless-cms/renderer`](../renderer) — Markdown → HTML レンダラー
202
- - [`@notion-headless-cms/cache-r2`](../cache-r2) — R2 キャッシュ
203
- - [`@notion-headless-cms/cache-next`](../cache-next) — Next.js ISR キャッシュ
95
+ - [クイックスタート](../../docs/quickstart.md)
96
+ - [CMS メソッド一覧](../../docs/api/cms-methods.md)
97
+ - [v0.2 → v0.3 移行ガイド](../../docs/migration/v0.3.md)
@@ -1,5 +1,5 @@
1
- import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "../content-BrwEY2_p.mjs";
2
- import { i as InvalidateScope, n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-B-MG4yyg.mjs";
1
+ import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "../content-BlD8UPas.mjs";
2
+ import { l as InvalidateScope, n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-CmM7n5LX.mjs";
3
3
 
4
4
  //#region src/cache/memory.d.ts
5
5
  interface MemoryDocumentCacheOptions {
@@ -40,7 +40,10 @@ var MemoryDocumentCache = class {
40
40
  if (scope === "all") {
41
41
  this.list = null;
42
42
  this.items.clear();
43
- } else if ("slug" in scope) this.items.delete(scope.slug);
43
+ return;
44
+ }
45
+ this.list = null;
46
+ if ("slug" in scope) this.items.delete(scope.slug);
44
47
  }
45
48
  enforceLimit() {
46
49
  if (this.maxItems === void 0) return;
@@ -1 +1 @@
1
- {"version":3,"file":"memory.mjs","names":[],"sources":["../../src/cache/memory.ts"],"sourcesContent":["import type {\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tCacheInvalidateScope,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tStorageBinary,\n} from \"../types/index\";\n\nexport interface MemoryDocumentCacheOptions {\n\t/** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxItems?: number;\n}\n\nexport interface MemoryImageCacheOptions {\n\t/** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxItems?: number;\n\t/** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxSizeBytes?: number;\n}\n\n/**\n * Map の挿入順を LRU として扱う軽量実装。\n * `touch` で既存キーを末尾に移動、`enforceLimit` で古いキー(先頭)から削除する。\n */\nfunction touch<K, V>(map: Map<K, V>, key: K): void {\n\tconst v = map.get(key);\n\tif (v === undefined) return;\n\tmap.delete(key);\n\tmap.set(key, v);\n}\n\n/** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */\nexport class MemoryDocumentCache<T extends BaseContentItem = BaseContentItem>\n\timplements DocumentCacheAdapter<T>\n{\n\treadonly name = \"memory-document\";\n\tprivate list: CachedItemList<T> | null = null;\n\tprivate items = new Map<string, CachedItem<T>>();\n\tprivate readonly maxItems: number | undefined;\n\n\tconstructor(options?: MemoryDocumentCacheOptions) {\n\t\tthis.maxItems = options?.maxItems;\n\t}\n\n\tgetList(): Promise<CachedItemList<T> | null> {\n\t\treturn Promise.resolve(this.list);\n\t}\n\n\tsetList(data: CachedItemList<T>): Promise<void> {\n\t\tthis.list = data;\n\t\treturn Promise.resolve();\n\t}\n\n\tgetItem(slug: string): Promise<CachedItem<T> | null> {\n\t\tconst entry = this.items.get(slug);\n\t\tif (entry) touch(this.items, slug);\n\t\treturn Promise.resolve(entry ?? null);\n\t}\n\n\tsetItem(slug: string, data: CachedItem<T>): Promise<void> {\n\t\tif (this.items.has(slug)) this.items.delete(slug);\n\t\tthis.items.set(slug, data);\n\t\tthis.enforceLimit();\n\t\treturn Promise.resolve();\n\t}\n\n\tasync invalidate(scope: CacheInvalidateScope): Promise<void> {\n\t\tif (scope === \"all\") {\n\t\t\tthis.list = null;\n\t\t\tthis.items.clear();\n\t\t} else if (\"slug\" in scope) {\n\t\t\tthis.items.delete(scope.slug);\n\t\t}\n\t}\n\n\tprivate enforceLimit(): void {\n\t\tif (this.maxItems === undefined) return;\n\t\twhile (this.items.size > this.maxItems) {\n\t\t\tconst firstKey = this.items.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tthis.items.delete(firstKey);\n\t\t}\n\t}\n}\n\n/** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */\nexport class MemoryImageCache implements ImageCacheAdapter {\n\treadonly name = \"memory-image\";\n\tprivate store = new Map<string, StorageBinary>();\n\tprivate totalBytes = 0;\n\tprivate readonly maxItems: number | undefined;\n\tprivate readonly maxSizeBytes: number | undefined;\n\n\tconstructor(options?: MemoryImageCacheOptions) {\n\t\tthis.maxItems = options?.maxItems;\n\t\tthis.maxSizeBytes = options?.maxSizeBytes;\n\t}\n\n\tget(hash: string): Promise<StorageBinary | null> {\n\t\tconst entry = this.store.get(hash);\n\t\tif (entry) touch(this.store, hash);\n\t\treturn Promise.resolve(entry ?? null);\n\t}\n\n\tset(hash: string, data: ArrayBuffer, contentType: string): Promise<void> {\n\t\tconst existing = this.store.get(hash);\n\t\tif (existing) {\n\t\t\tthis.totalBytes -= existing.data.byteLength;\n\t\t\tthis.store.delete(hash);\n\t\t}\n\t\tthis.store.set(hash, { data, contentType });\n\t\tthis.totalBytes += data.byteLength;\n\t\tthis.enforceLimit();\n\t\treturn Promise.resolve();\n\t}\n\n\tprivate enforceLimit(): void {\n\t\twhile (\n\t\t\t(this.maxItems !== undefined && this.store.size > this.maxItems) ||\n\t\t\t(this.maxSizeBytes !== undefined && this.totalBytes > this.maxSizeBytes)\n\t\t) {\n\t\t\tconst firstKey = this.store.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tconst victim = this.store.get(firstKey);\n\t\t\tif (victim) this.totalBytes -= victim.data.byteLength;\n\t\t\tthis.store.delete(firstKey);\n\t\t}\n\t}\n}\n\n/** インメモリキャッシュ(ドキュメント用)を生成する。 */\nexport function memoryDocumentCache<\n\tT extends BaseContentItem = BaseContentItem,\n>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T> {\n\treturn new MemoryDocumentCache<T>(options);\n}\n\n/** インメモリキャッシュ(画像用)を生成する。 */\nexport function memoryImageCache(\n\toptions?: MemoryImageCacheOptions,\n): ImageCacheAdapter {\n\treturn new MemoryImageCache(options);\n}\n\n/**\n * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。\n * memoryCache() はドキュメントキャッシュを返す(後方互換)。\n */\nexport function memoryCache<T extends BaseContentItem = BaseContentItem>(\n\toptions?: MemoryDocumentCacheOptions,\n): DocumentCacheAdapter<T> {\n\treturn new MemoryDocumentCache<T>(options);\n}\n"],"mappings":";;;;;AA0BA,SAAS,MAAY,KAAgB,KAAc;CAClD,MAAM,IAAI,IAAI,IAAI,IAAI;AACtB,KAAI,MAAM,KAAA,EAAW;AACrB,KAAI,OAAO,IAAI;AACf,KAAI,IAAI,KAAK,EAAE;;;AAIhB,IAAa,sBAAb,MAEA;CACC,OAAgB;CAChB,OAAyC;CACzC,wBAAgB,IAAI,KAA4B;CAChD;CAEA,YAAY,SAAsC;AACjD,OAAK,WAAW,SAAS;;CAG1B,UAA6C;AAC5C,SAAO,QAAQ,QAAQ,KAAK,KAAK;;CAGlC,QAAQ,MAAwC;AAC/C,OAAK,OAAO;AACZ,SAAO,QAAQ,SAAS;;CAGzB,QAAQ,MAA6C;EACpD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;AAClC,MAAI,MAAO,OAAM,KAAK,OAAO,KAAK;AAClC,SAAO,QAAQ,QAAQ,SAAS,KAAK;;CAGtC,QAAQ,MAAc,MAAoC;AACzD,MAAI,KAAK,MAAM,IAAI,KAAK,CAAE,MAAK,MAAM,OAAO,KAAK;AACjD,OAAK,MAAM,IAAI,MAAM,KAAK;AAC1B,OAAK,cAAc;AACnB,SAAO,QAAQ,SAAS;;CAGzB,MAAM,WAAW,OAA4C;AAC5D,MAAI,UAAU,OAAO;AACpB,QAAK,OAAO;AACZ,QAAK,MAAM,OAAO;aACR,UAAU,MACpB,MAAK,MAAM,OAAO,MAAM,KAAK;;CAI/B,eAA6B;AAC5B,MAAI,KAAK,aAAa,KAAA,EAAW;AACjC,SAAO,KAAK,MAAM,OAAO,KAAK,UAAU;GACvC,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,aAAa,KAAA,EAAW;AAC5B,QAAK,MAAM,OAAO,SAAS;;;;;AAM9B,IAAa,mBAAb,MAA2D;CAC1D,OAAgB;CAChB,wBAAgB,IAAI,KAA4B;CAChD,aAAqB;CACrB;CACA;CAEA,YAAY,SAAmC;AAC9C,OAAK,WAAW,SAAS;AACzB,OAAK,eAAe,SAAS;;CAG9B,IAAI,MAA6C;EAChD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;AAClC,MAAI,MAAO,OAAM,KAAK,OAAO,KAAK;AAClC,SAAO,QAAQ,QAAQ,SAAS,KAAK;;CAGtC,IAAI,MAAc,MAAmB,aAAoC;EACxE,MAAM,WAAW,KAAK,MAAM,IAAI,KAAK;AACrC,MAAI,UAAU;AACb,QAAK,cAAc,SAAS,KAAK;AACjC,QAAK,MAAM,OAAO,KAAK;;AAExB,OAAK,MAAM,IAAI,MAAM;GAAE;GAAM;GAAa,CAAC;AAC3C,OAAK,cAAc,KAAK;AACxB,OAAK,cAAc;AACnB,SAAO,QAAQ,SAAS;;CAGzB,eAA6B;AAC5B,SACE,KAAK,aAAa,KAAA,KAAa,KAAK,MAAM,OAAO,KAAK,YACtD,KAAK,iBAAiB,KAAA,KAAa,KAAK,aAAa,KAAK,cAC1D;GACD,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,aAAa,KAAA,EAAW;GAC5B,MAAM,SAAS,KAAK,MAAM,IAAI,SAAS;AACvC,OAAI,OAAQ,MAAK,cAAc,OAAO,KAAK;AAC3C,QAAK,MAAM,OAAO,SAAS;;;;;AAM9B,SAAgB,oBAEd,SAA+D;AAChE,QAAO,IAAI,oBAAuB,QAAQ;;;AAI3C,SAAgB,iBACf,SACoB;AACpB,QAAO,IAAI,iBAAiB,QAAQ;;;;;;AAOrC,SAAgB,YACf,SAC0B;AAC1B,QAAO,IAAI,oBAAuB,QAAQ"}
1
+ {"version":3,"file":"memory.mjs","names":[],"sources":["../../src/cache/memory.ts"],"sourcesContent":["import type {\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tInvalidateScope,\n\tStorageBinary,\n} from \"../types/index\";\n\nexport interface MemoryDocumentCacheOptions {\n\t/** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxItems?: number;\n}\n\nexport interface MemoryImageCacheOptions {\n\t/** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxItems?: number;\n\t/** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxSizeBytes?: number;\n}\n\n/**\n * Map の挿入順を LRU として扱う軽量実装。\n * `touch` で既存キーを末尾に移動、`enforceLimit` で古いキー(先頭)から削除する。\n */\nfunction touch<K, V>(map: Map<K, V>, key: K): void {\n\tconst v = map.get(key);\n\tif (v === undefined) return;\n\tmap.delete(key);\n\tmap.set(key, v);\n}\n\n/** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */\nexport class MemoryDocumentCache<T extends BaseContentItem = BaseContentItem>\n\timplements DocumentCacheAdapter<T>\n{\n\treadonly name = \"memory-document\";\n\tprivate list: CachedItemList<T> | null = null;\n\tprivate items = new Map<string, CachedItem<T>>();\n\tprivate readonly maxItems: number | undefined;\n\n\tconstructor(options?: MemoryDocumentCacheOptions) {\n\t\tthis.maxItems = options?.maxItems;\n\t}\n\n\tgetList(): Promise<CachedItemList<T> | null> {\n\t\treturn Promise.resolve(this.list);\n\t}\n\n\tsetList(data: CachedItemList<T>): Promise<void> {\n\t\tthis.list = data;\n\t\treturn Promise.resolve();\n\t}\n\n\tgetItem(slug: string): Promise<CachedItem<T> | null> {\n\t\tconst entry = this.items.get(slug);\n\t\tif (entry) touch(this.items, slug);\n\t\treturn Promise.resolve(entry ?? null);\n\t}\n\n\tsetItem(slug: string, data: CachedItem<T>): Promise<void> {\n\t\tif (this.items.has(slug)) this.items.delete(slug);\n\t\tthis.items.set(slug, data);\n\t\tthis.enforceLimit();\n\t\treturn Promise.resolve();\n\t}\n\n\tasync invalidate(scope: InvalidateScope): Promise<void> {\n\t\tif (scope === \"all\") {\n\t\t\tthis.list = null;\n\t\t\tthis.items.clear();\n\t\t\treturn;\n\t\t}\n\t\t// { collection } または { collection, slug }。単一コレクション想定のため\n\t\t// list を必ず破棄し、slug 指定があれば該当 item も削除する\n\t\tthis.list = null;\n\t\tif (\"slug\" in scope) {\n\t\t\tthis.items.delete(scope.slug);\n\t\t}\n\t}\n\n\tprivate enforceLimit(): void {\n\t\tif (this.maxItems === undefined) return;\n\t\twhile (this.items.size > this.maxItems) {\n\t\t\tconst firstKey = this.items.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tthis.items.delete(firstKey);\n\t\t}\n\t}\n}\n\n/** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */\nexport class MemoryImageCache implements ImageCacheAdapter {\n\treadonly name = \"memory-image\";\n\tprivate store = new Map<string, StorageBinary>();\n\tprivate totalBytes = 0;\n\tprivate readonly maxItems: number | undefined;\n\tprivate readonly maxSizeBytes: number | undefined;\n\n\tconstructor(options?: MemoryImageCacheOptions) {\n\t\tthis.maxItems = options?.maxItems;\n\t\tthis.maxSizeBytes = options?.maxSizeBytes;\n\t}\n\n\tget(hash: string): Promise<StorageBinary | null> {\n\t\tconst entry = this.store.get(hash);\n\t\tif (entry) touch(this.store, hash);\n\t\treturn Promise.resolve(entry ?? null);\n\t}\n\n\tset(hash: string, data: ArrayBuffer, contentType: string): Promise<void> {\n\t\tconst existing = this.store.get(hash);\n\t\tif (existing) {\n\t\t\tthis.totalBytes -= existing.data.byteLength;\n\t\t\tthis.store.delete(hash);\n\t\t}\n\t\tthis.store.set(hash, { data, contentType });\n\t\tthis.totalBytes += data.byteLength;\n\t\tthis.enforceLimit();\n\t\treturn Promise.resolve();\n\t}\n\n\tprivate enforceLimit(): void {\n\t\twhile (\n\t\t\t(this.maxItems !== undefined && this.store.size > this.maxItems) ||\n\t\t\t(this.maxSizeBytes !== undefined && this.totalBytes > this.maxSizeBytes)\n\t\t) {\n\t\t\tconst firstKey = this.store.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tconst victim = this.store.get(firstKey);\n\t\t\tif (victim) this.totalBytes -= victim.data.byteLength;\n\t\t\tthis.store.delete(firstKey);\n\t\t}\n\t}\n}\n\n/** インメモリキャッシュ(ドキュメント用)を生成する。 */\nexport function memoryDocumentCache<\n\tT extends BaseContentItem = BaseContentItem,\n>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T> {\n\treturn new MemoryDocumentCache<T>(options);\n}\n\n/** インメモリキャッシュ(画像用)を生成する。 */\nexport function memoryImageCache(\n\toptions?: MemoryImageCacheOptions,\n): ImageCacheAdapter {\n\treturn new MemoryImageCache(options);\n}\n\n/**\n * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。\n * memoryCache() はドキュメントキャッシュを返す(後方互換)。\n */\nexport function memoryCache<T extends BaseContentItem = BaseContentItem>(\n\toptions?: MemoryDocumentCacheOptions,\n): DocumentCacheAdapter<T> {\n\treturn new MemoryDocumentCache<T>(options);\n}\n"],"mappings":";;;;;AA0BA,SAAS,MAAY,KAAgB,KAAc;CAClD,MAAM,IAAI,IAAI,IAAI,IAAI;AACtB,KAAI,MAAM,KAAA,EAAW;AACrB,KAAI,OAAO,IAAI;AACf,KAAI,IAAI,KAAK,EAAE;;;AAIhB,IAAa,sBAAb,MAEA;CACC,OAAgB;CAChB,OAAyC;CACzC,wBAAgB,IAAI,KAA4B;CAChD;CAEA,YAAY,SAAsC;AACjD,OAAK,WAAW,SAAS;;CAG1B,UAA6C;AAC5C,SAAO,QAAQ,QAAQ,KAAK,KAAK;;CAGlC,QAAQ,MAAwC;AAC/C,OAAK,OAAO;AACZ,SAAO,QAAQ,SAAS;;CAGzB,QAAQ,MAA6C;EACpD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;AAClC,MAAI,MAAO,OAAM,KAAK,OAAO,KAAK;AAClC,SAAO,QAAQ,QAAQ,SAAS,KAAK;;CAGtC,QAAQ,MAAc,MAAoC;AACzD,MAAI,KAAK,MAAM,IAAI,KAAK,CAAE,MAAK,MAAM,OAAO,KAAK;AACjD,OAAK,MAAM,IAAI,MAAM,KAAK;AAC1B,OAAK,cAAc;AACnB,SAAO,QAAQ,SAAS;;CAGzB,MAAM,WAAW,OAAuC;AACvD,MAAI,UAAU,OAAO;AACpB,QAAK,OAAO;AACZ,QAAK,MAAM,OAAO;AAClB;;AAID,OAAK,OAAO;AACZ,MAAI,UAAU,MACb,MAAK,MAAM,OAAO,MAAM,KAAK;;CAI/B,eAA6B;AAC5B,MAAI,KAAK,aAAa,KAAA,EAAW;AACjC,SAAO,KAAK,MAAM,OAAO,KAAK,UAAU;GACvC,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,aAAa,KAAA,EAAW;AAC5B,QAAK,MAAM,OAAO,SAAS;;;;;AAM9B,IAAa,mBAAb,MAA2D;CAC1D,OAAgB;CAChB,wBAAgB,IAAI,KAA4B;CAChD,aAAqB;CACrB;CACA;CAEA,YAAY,SAAmC;AAC9C,OAAK,WAAW,SAAS;AACzB,OAAK,eAAe,SAAS;;CAG9B,IAAI,MAA6C;EAChD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;AAClC,MAAI,MAAO,OAAM,KAAK,OAAO,KAAK;AAClC,SAAO,QAAQ,QAAQ,SAAS,KAAK;;CAGtC,IAAI,MAAc,MAAmB,aAAoC;EACxE,MAAM,WAAW,KAAK,MAAM,IAAI,KAAK;AACrC,MAAI,UAAU;AACb,QAAK,cAAc,SAAS,KAAK;AACjC,QAAK,MAAM,OAAO,KAAK;;AAExB,OAAK,MAAM,IAAI,MAAM;GAAE;GAAM;GAAa,CAAC;AAC3C,OAAK,cAAc,KAAK;AACxB,OAAK,cAAc;AACnB,SAAO,QAAQ,SAAS;;CAGzB,eAA6B;AAC5B,SACE,KAAK,aAAa,KAAA,KAAa,KAAK,MAAM,OAAO,KAAK,YACtD,KAAK,iBAAiB,KAAA,KAAa,KAAK,aAAa,KAAK,cAC1D;GACD,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,aAAa,KAAA,EAAW;GAC5B,MAAM,SAAS,KAAK,MAAM,IAAI,SAAS;AACvC,OAAI,OAAQ,MAAK,cAAc,OAAO,KAAK;AAC3C,QAAK,MAAM,OAAO,SAAS;;;;;AAM9B,SAAgB,oBAEd,SAA+D;AAChE,QAAO,IAAI,oBAAuB,QAAQ;;;AAI3C,SAAgB,iBACf,SACoB;AACpB,QAAO,IAAI,iBAAiB,QAAQ;;;;;;AAOrC,SAAgB,YACf,SAC0B;AAC1B,QAAO,IAAI,oBAAuB,QAAQ"}
@@ -1,5 +1,5 @@
1
- import { t as BaseContentItem } from "../content-BrwEY2_p.mjs";
2
- import { n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-B-MG4yyg.mjs";
1
+ import { t as BaseContentItem } from "../content-BlD8UPas.mjs";
2
+ import { n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-CmM7n5LX.mjs";
3
3
 
4
4
  //#region src/cache/noop.d.ts
5
5
  /** 何もしないドキュメントキャッシュを返す(シングルトン)。 */
@@ -1 +1 @@
1
- {"version":3,"file":"noop.mjs","names":[],"sources":["../../src/cache/noop.ts"],"sourcesContent":["import type {\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tCacheInvalidateScope,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tStorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントキャッシュ実装。常に null を返す。 */\nclass NoopDocumentCache<T extends BaseContentItem = BaseContentItem>\n\timplements DocumentCacheAdapter<T>\n{\n\treadonly name = \"noop-document\";\n\n\tgetList(): Promise<CachedItemList<T> | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tsetList(_data: CachedItemList<T>): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tgetItem(_slug: string): Promise<CachedItem<T> | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tsetItem(_slug: string, _data: CachedItem<T>): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tinvalidate(_scope: CacheInvalidateScope): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n\n/** 何もキャッシュしない画像キャッシュ実装。常に null を返す。 */\nclass NoopImageCache implements ImageCacheAdapter {\n\treadonly name = \"noop-image\";\n\n\tget(_hash: string): Promise<StorageBinary | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tset(_hash: string, _data: ArrayBuffer, _contentType: string): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n\nconst _noopDocument = new NoopDocumentCache();\nconst _noopImage = new NoopImageCache();\n\n/** 何もしないドキュメントキャッシュを返す(シングルトン)。 */\nexport function noopDocumentCache<\n\tT extends BaseContentItem = BaseContentItem,\n>(): DocumentCacheAdapter<T> {\n\treturn _noopDocument as DocumentCacheAdapter<T>;\n}\n\n/** 何もしない画像キャッシュを返す(シングルトン)。 */\nexport function noopImageCache(): ImageCacheAdapter {\n\treturn _noopImage;\n}\n"],"mappings":";;AAWA,IAAM,oBAAN,MAEA;CACC,OAAgB;CAEhB,UAA6C;AAC5C,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,QAAQ,OAAyC;AAChD,SAAO,QAAQ,SAAS;;CAGzB,QAAQ,OAA8C;AACrD,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,QAAQ,OAAe,OAAqC;AAC3D,SAAO,QAAQ,SAAS;;CAGzB,WAAW,QAA6C;AACvD,SAAO,QAAQ,SAAS;;;;AAK1B,IAAM,iBAAN,MAAkD;CACjD,OAAgB;CAEhB,IAAI,OAA8C;AACjD,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,IAAI,OAAe,OAAoB,cAAqC;AAC3E,SAAO,QAAQ,SAAS;;;AAI1B,MAAM,gBAAgB,IAAI,mBAAmB;AAC7C,MAAM,aAAa,IAAI,gBAAgB;;AAGvC,SAAgB,oBAEa;AAC5B,QAAO;;;AAIR,SAAgB,iBAAoC;AACnD,QAAO"}
1
+ {"version":3,"file":"noop.mjs","names":[],"sources":["../../src/cache/noop.ts"],"sourcesContent":["import type {\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tInvalidateScope,\n\tStorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントキャッシュ実装。常に null を返す。 */\nclass NoopDocumentCache<T extends BaseContentItem = BaseContentItem>\n\timplements DocumentCacheAdapter<T>\n{\n\treadonly name = \"noop-document\";\n\n\tgetList(): Promise<CachedItemList<T> | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tsetList(_data: CachedItemList<T>): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tgetItem(_slug: string): Promise<CachedItem<T> | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tsetItem(_slug: string, _data: CachedItem<T>): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tinvalidate(_scope: InvalidateScope): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n\n/** 何もキャッシュしない画像キャッシュ実装。常に null を返す。 */\nclass NoopImageCache implements ImageCacheAdapter {\n\treadonly name = \"noop-image\";\n\n\tget(_hash: string): Promise<StorageBinary | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tset(_hash: string, _data: ArrayBuffer, _contentType: string): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n\nconst _noopDocument = new NoopDocumentCache();\nconst _noopImage = new NoopImageCache();\n\n/** 何もしないドキュメントキャッシュを返す(シングルトン)。 */\nexport function noopDocumentCache<\n\tT extends BaseContentItem = BaseContentItem,\n>(): DocumentCacheAdapter<T> {\n\treturn _noopDocument as DocumentCacheAdapter<T>;\n}\n\n/** 何もしない画像キャッシュを返す(シングルトン)。 */\nexport function noopImageCache(): ImageCacheAdapter {\n\treturn _noopImage;\n}\n"],"mappings":";;AAWA,IAAM,oBAAN,MAEA;CACC,OAAgB;CAEhB,UAA6C;AAC5C,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,QAAQ,OAAyC;AAChD,SAAO,QAAQ,SAAS;;CAGzB,QAAQ,OAA8C;AACrD,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,QAAQ,OAAe,OAAqC;AAC3D,SAAO,QAAQ,SAAS;;CAGzB,WAAW,QAAwC;AAClD,SAAO,QAAQ,SAAS;;;;AAK1B,IAAM,iBAAN,MAAkD;CACjD,OAAgB;CAEhB,IAAI,OAA8C;AACjD,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,IAAI,OAAe,OAAoB,cAAqC;AAC3E,SAAO,QAAQ,SAAS;;;AAI1B,MAAM,gBAAgB,IAAI,mBAAmB;AAC7C,MAAM,aAAa,IAAI,gBAAgB;;AAGvC,SAAgB,oBAEa;AAC5B,QAAO;;;AAIR,SAAgB,iBAAoC;AACnD,QAAO"}
@@ -0,0 +1,205 @@
1
+ import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "./content-BlD8UPas.mjs";
2
+
3
+ //#region src/content/blocks.d.ts
4
+ /**
5
+ * CMS 本文の中間表現 (AST)。
6
+ *
7
+ * DataSource が本文をこの配列にノーマライズして返す。
8
+ * レンダラー (`blocksToHtml`) やカスタム React コンポーネントは
9
+ * この型だけを扱えばよい。
10
+ *
11
+ * 対応が難しいブロック (Notion の column / synced block など) は
12
+ * `{ type: "raw", html }` にフォールバックする。
13
+ */
14
+ type ContentBlock = {
15
+ type: "paragraph";
16
+ children: InlineNode[];
17
+ } | {
18
+ type: "heading";
19
+ level: 1 | 2 | 3;
20
+ children: InlineNode[];
21
+ } | {
22
+ type: "image";
23
+ src: string;
24
+ alt?: string;
25
+ cachedHash?: string;
26
+ } | {
27
+ type: "code";
28
+ lang?: string;
29
+ value: string;
30
+ } | {
31
+ type: "list";
32
+ ordered: boolean;
33
+ items: ContentBlock[][];
34
+ } | {
35
+ type: "quote";
36
+ children: ContentBlock[];
37
+ } | {
38
+ type: "divider";
39
+ } | {
40
+ type: "raw";
41
+ html: string;
42
+ };
43
+ /** paragraph / heading 等の子に並ぶインラインノード。 */
44
+ type InlineNode = {
45
+ type: "text";
46
+ value: string;
47
+ bold?: boolean;
48
+ italic?: boolean;
49
+ code?: boolean;
50
+ } | {
51
+ type: "link";
52
+ url: string;
53
+ children: InlineNode[];
54
+ } | {
55
+ type: "break";
56
+ };
57
+ /**
58
+ * `getItem({ include: { content: true } })` で返される本文。
59
+ * blocks は常に同梱。html / markdown は遅延生成。
60
+ */
61
+ interface ContentResult {
62
+ /** 本文の AST (第一級)。 */
63
+ blocks: ContentBlock[];
64
+ /** 遅延 HTML。renderer が必要な場合のみ呼ぶ。 */
65
+ html(): Promise<string>;
66
+ /** 遅延 Markdown。 */
67
+ markdown(): Promise<string>;
68
+ }
69
+ /** 画像参照 (DataSource.resolveImageUrl に渡す)。 */
70
+ interface ImageRef {
71
+ /** 元の Notion 画像 URL (期限切れの可能性あり)。 */
72
+ originalUrl: string;
73
+ /** 関連するアイテム ID。 */
74
+ itemId?: string;
75
+ }
76
+ //#endregion
77
+ //#region src/types/data-source.d.ts
78
+ /**
79
+ * キャッシュ無効化のスコープ (DataSource 層で参照する形)。
80
+ */
81
+ type InvalidateScope = "all" | {
82
+ collection: string;
83
+ } | {
84
+ collection: string;
85
+ slug: string;
86
+ };
87
+ /**
88
+ * Webhook 受信時の検証設定。
89
+ */
90
+ interface WebhookConfig {
91
+ /** 署名検証用シークレット。 */
92
+ secret?: string;
93
+ }
94
+ /**
95
+ * コンテンツソースを抽象化する v1 インターフェース。
96
+ *
97
+ * ユーザーは直接実装しない。`notion-orm` 等の ORM パッケージが実装する。
98
+ * core は Notion 固有の知識を持たず、このインターフェース経由でのみデータを扱う。
99
+ * 将来 `googledocs-orm` 等の別ソースもこの I/F を満たせば差し替え可能。
100
+ */
101
+ interface DataSource<T extends BaseContentItem = BaseContentItem> {
102
+ /** ソース識別子 (ロギング・デバッグ用)。 */
103
+ readonly name: string;
104
+ /** 公開扱いするステータス値 (ORM 側デフォルト)。 */
105
+ readonly publishedStatuses?: readonly string[];
106
+ /** アクセス許可するステータス値 (ORM 側デフォルト)。 */
107
+ readonly accessibleStatuses?: readonly string[];
108
+ /** 公開済みアイテム一覧を取得する。 */
109
+ list(opts?: {
110
+ publishedStatuses?: readonly string[];
111
+ }): Promise<T[]>;
112
+ /** スラッグで単件取得。見つからなければ null。 */
113
+ findBySlug(slug: string): Promise<T | null>;
114
+ /** アイテム本文を ContentBlock 配列で返す。 */
115
+ loadBlocks(item: T): Promise<ContentBlock[]>;
116
+ /** アイテム本文を Markdown 文字列で返す (html() 生成の元ソース)。 */
117
+ loadMarkdown(item: T): Promise<string>;
118
+ /** SWR 鮮度判定用。item の最終更新タイムスタンプ。 */
119
+ getLastModified(item: T): string;
120
+ /** リスト全体のバージョン文字列 (例: 最新 last_edited_time)。 */
121
+ getListVersion(items: T[]): string;
122
+ /** 期限切れ画像 URL の再取得 (Notion の署名 URL 対応)。 */
123
+ resolveImageUrl?(ref: ImageRef): Promise<string>;
124
+ /**
125
+ * Webhook リクエストをパースして無効化スコープを返す。
126
+ * 実装していない場合は `$handler` が body の `{ slug }` にフォールバック。
127
+ */
128
+ parseWebhook?(req: Request, config: WebhookConfig): Promise<InvalidateScope>;
129
+ }
130
+ /**
131
+ * `nhcSchema` の各コレクション設定エントリ。
132
+ * ユーザーは CLI 生成の `nhcSchema` を渡すだけで、
133
+ * この型は `createCMS` 内部で DataSource のファクトリに渡される。
134
+ */
135
+ interface CollectionConfig<T extends BaseContentItem = BaseContentItem> {
136
+ /** Notion データソース (database) ID。 */
137
+ databaseId: string;
138
+ /** スキーマ情報 (ORM が解釈する不透明データ)。 */
139
+ schema?: any;
140
+ /** 公開扱いするステータス値。 */
141
+ publishedStatuses?: string[];
142
+ /** アクセス許可するステータス値。 */
143
+ accessibleStatuses?: string[];
144
+ /** `T` を型レベルで持ち回るためのマーカー (ランタイム値なし)。 */
145
+ __itemType?: T;
146
+ }
147
+ /**
148
+ * `nhc generate` が生成する `nhcSchema` の型。
149
+ * コレクション名をキーとして、各コレクションの設定を保持する。
150
+ */
151
+ type CMSSchema = Record<string, CollectionConfig<any>>;
152
+ /** `CollectionConfig<T>` から `T` を抽出するユーティリティ型。 */
153
+ type InferCollectionItem<C> = C extends CollectionConfig<infer T> ? T : BaseContentItem;
154
+ /**
155
+ * DataSource を生成するファクトリ関数の型。
156
+ * `createCMS` はコレクション名 → この関数 → DataSource の経路で組み立てる。
157
+ *
158
+ * ユーザーコードは直接呼ばない。`@notion-headless-cms/notion-orm` 等の
159
+ * ORM パッケージが provide する。ORM 固有のオプション (Notion なら
160
+ * `{ token }`、Google Docs なら `{ credentials }` 等) は `TOptions` の
161
+ * generic で受け取る。
162
+ *
163
+ * @example
164
+ * // notion-orm が DataSourceFactory<{ token: string }> として実装する
165
+ * const factory: DataSourceFactory<{ token: string }> = ({ collection, config, options }) =>
166
+ * createNotionCollection({
167
+ * token: options.token,
168
+ * dataSourceId: config.databaseId,
169
+ * schema: config.schema,
170
+ * });
171
+ */
172
+ type DataSourceFactory<TOptions = unknown> = <T extends BaseContentItem>(args: {
173
+ collection: string;
174
+ config: CollectionConfig<T>;
175
+ options: TOptions;
176
+ }) => DataSource<T>;
177
+ //#endregion
178
+ //#region src/types/cache.d.ts
179
+ /** ドキュメントキャッシュを抽象化するインターフェース。 */
180
+ interface DocumentCacheAdapter<T extends BaseContentItem = BaseContentItem> {
181
+ readonly name: string;
182
+ getList(): Promise<CachedItemList<T> | null>;
183
+ setList(data: CachedItemList<T>): Promise<void>;
184
+ getItem(slug: string): Promise<CachedItem<T> | null>;
185
+ setItem(slug: string, data: CachedItem<T>): Promise<void>;
186
+ invalidate?(scope: InvalidateScope): Promise<void>;
187
+ }
188
+ /** 画像キャッシュを抽象化するインターフェース。 */
189
+ interface ImageCacheAdapter {
190
+ readonly name: string;
191
+ get(hash: string): Promise<StorageBinary | null>;
192
+ set(hash: string, data: ArrayBuffer, contentType: string): Promise<void>;
193
+ }
194
+ /**
195
+ * キャッシュ設定。`"disabled"` を渡すと完全にキャッシュを無効化する。
196
+ * オブジェクトの場合、document / image それぞれ独立したアダプタを差し込める。
197
+ */
198
+ type CacheConfig<T extends BaseContentItem = BaseContentItem> = "disabled" | {
199
+ document?: DocumentCacheAdapter<T>;
200
+ image?: ImageCacheAdapter; /** キャッシュの有効期間(ミリ秒)。未設定の場合はTTLなし。 */
201
+ ttlMs?: number;
202
+ };
203
+ //#endregion
204
+ export { CollectionConfig as a, InferCollectionItem as c, ContentBlock as d, ContentResult as f, CMSSchema as i, InvalidateScope as l, InlineNode as m, DocumentCacheAdapter as n, DataSource as o, ImageRef as p, ImageCacheAdapter as r, DataSourceFactory as s, CacheConfig as t, WebhookConfig as u };
205
+ //# sourceMappingURL=cache-CmM7n5LX.d.mts.map
@@ -15,6 +15,8 @@ interface BaseContentItem {
15
15
  id: string;
16
16
  /** URL キー(必須)。 */
17
17
  slug: string;
18
+ /** Notion ページ名(title 型プロパティのテキスト)。 */
19
+ title?: string | null;
18
20
  /** 最終更新タイムスタンプ(変更検知に必須)。 */
19
21
  updatedAt: string;
20
22
  /** コンテンツのステータス。ステータスのない DB では省略可能。 */
@@ -50,4 +52,4 @@ interface CMSSchemaProperties {
50
52
  }
51
53
  //#endregion
52
54
  export { StorageBinary as a, CachedItemList as i, CMSSchemaProperties as n, CachedItem as r, BaseContentItem as t };
53
- //# sourceMappingURL=content-BrwEY2_p.d.mts.map
55
+ //# sourceMappingURL=content-BlD8UPas.d.mts.map
package/dist/errors.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/errors.d.ts
2
- type BuiltInCMSErrorCode = "core/config_invalid" | "core/schema_invalid" | "source/fetch_items_failed" | "source/fetch_item_failed" | "source/load_markdown_failed" | "cache/io_failed" | "cache/image_fetch_failed" | "renderer/failed";
2
+ type BuiltInCMSErrorCode = "core/config_invalid" | "core/schema_invalid" | "core/notion_orm_missing" | "source/fetch_items_failed" | "source/fetch_item_failed" | "source/load_markdown_failed" | "cache/io_failed" | "cache/image_fetch_failed" | "renderer/failed" | "cli/config_invalid" | "cli/config_load_failed" | "cli/schema_invalid" | "cli/generate_failed" | "cli/init_failed" | "cli/notion_api_failed" | "cli/env_file_not_found";
3
3
  /**
4
4
  * CMS エラーコード。
5
5
  * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["type BuiltInCMSErrorCode =\n\t| \"core/config_invalid\"\n\t| \"core/schema_invalid\"\n\t| \"source/fetch_items_failed\"\n\t| \"source/fetch_item_failed\"\n\t| \"source/load_markdown_failed\"\n\t| \"cache/io_failed\"\n\t| \"cache/image_fetch_failed\"\n\t| \"renderer/failed\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n\toperation: string;\n\tslug?: string;\n\tdataSourceId?: string;\n\tpageId?: string;\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\nexport class CMSError extends Error {\n\treadonly code: CMSErrorCode;\n\toverride readonly cause?: unknown;\n\treadonly context: CMSErrorContext;\n\n\tconstructor(params: {\n\t\tcode: CMSErrorCode;\n\t\tmessage: string;\n\t\tcause?: unknown;\n\t\tcontext: CMSErrorContext;\n\t}) {\n\t\tsuper(params.message, { cause: params.cause });\n\t\tthis.name = \"CMSError\";\n\t\tthis.code = params.code;\n\t\tthis.cause = params.cause;\n\t\tthis.context = params.context;\n\t}\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n\treturn error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n\terror: unknown,\n\tnamespace: string,\n): error is CMSError {\n\treturn isCMSError(error) && error.code.startsWith(namespace);\n}\n"],"mappings":";AAyBA,IAAa,WAAb,cAA8B,MAAM;CACnC;CACA;CACA;CAEA,YAAY,QAKT;AACF,QAAM,OAAO,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC;AAC9C,OAAK,OAAO;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;AAIxB,SAAgB,WAAW,OAAmC;AAC7D,QAAO,iBAAiB;;;AAIzB,SAAgB,sBACf,OACA,WACoB;AACpB,QAAO,WAAW,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["type BuiltInCMSErrorCode =\n\t| \"core/config_invalid\"\n\t| \"core/schema_invalid\"\n\t| \"core/notion_orm_missing\"\n\t| \"source/fetch_items_failed\"\n\t| \"source/fetch_item_failed\"\n\t| \"source/load_markdown_failed\"\n\t| \"cache/io_failed\"\n\t| \"cache/image_fetch_failed\"\n\t| \"renderer/failed\"\n\t| \"cli/config_invalid\"\n\t| \"cli/config_load_failed\"\n\t| \"cli/schema_invalid\"\n\t| \"cli/generate_failed\"\n\t| \"cli/init_failed\"\n\t| \"cli/notion_api_failed\"\n\t| \"cli/env_file_not_found\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n\toperation: string;\n\tslug?: string;\n\tdataSourceId?: string;\n\tpageId?: string;\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\nexport class CMSError extends Error {\n\treadonly code: CMSErrorCode;\n\toverride readonly cause?: unknown;\n\treadonly context: CMSErrorContext;\n\n\tconstructor(params: {\n\t\tcode: CMSErrorCode;\n\t\tmessage: string;\n\t\tcause?: unknown;\n\t\tcontext: CMSErrorContext;\n\t}) {\n\t\tsuper(params.message, { cause: params.cause });\n\t\tthis.name = \"CMSError\";\n\t\tthis.code = params.code;\n\t\tthis.cause = params.cause;\n\t\tthis.context = params.context;\n\t}\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n\treturn error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n\terror: unknown,\n\tnamespace: string,\n): error is CMSError {\n\treturn isCMSError(error) && error.code.startsWith(namespace);\n}\n"],"mappings":";AAiCA,IAAa,WAAb,cAA8B,MAAM;CACnC;CACA;CACA;CAEA,YAAY,QAKT;AACF,QAAM,OAAO,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC;AAC9C,OAAK,OAAO;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;AAIxB,SAAgB,WAAW,OAAmC;AAC7D,QAAO,iBAAiB;;;AAIzB,SAAgB,sBACf,OACA,WACoB;AACpB,QAAO,WAAW,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU"}
@@ -1,4 +1,4 @@
1
- import { r as CachedItem, t as BaseContentItem } from "./content-BrwEY2_p.mjs";
1
+ import { r as CachedItem, t as BaseContentItem } from "./content-BlD8UPas.mjs";
2
2
 
3
3
  //#region src/types/hooks.d.ts
4
4
  type MaybePromise<T> = T | Promise<T>;
@@ -57,4 +57,4 @@ declare function mergeLoggers(plugins: Array<{
57
57
  }>, directLogger?: Logger): Logger | undefined;
58
58
  //#endregion
59
59
  export { Logger as a, definePlugin as i, mergeLoggers as n, CMSHooks as o, CMSPlugin as r, MaybePromise as s, mergeHooks as t };
60
- //# sourceMappingURL=hooks-DCSAQkST.d.mts.map
60
+ //# sourceMappingURL=hooks-DKnjPzHO.d.mts.map
package/dist/hooks.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as mergeLoggers, t as mergeHooks } from "./hooks-DCSAQkST.mjs";
1
+ import { n as mergeLoggers, t as mergeHooks } from "./hooks-DKnjPzHO.mjs";
2
2
  export { mergeHooks, mergeLoggers };