@notion-headless-cms/core 0.1.1 → 0.1.3

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,6 +1,6 @@
1
1
  # @notion-headless-cms/core
2
2
 
3
- CMS エンジン本体。Notion からのコンテンツ取得・変換・キャッシュを統合管理する。
3
+ CMS エンジン本体。データソース・キャッシュ・レンダラーを統合し、Stale-While-Revalidate / 更新検知 / クエリビルダー / フック / リトライを提供する。**外部ランタイム依存ゼロ**。
4
4
 
5
5
  ## インストール
6
6
 
@@ -8,98 +8,196 @@ CMS エンジン本体。Notion からのコンテンツ取得・変換・キャ
8
8
  npm install @notion-headless-cms/core
9
9
  ```
10
10
 
11
- Cloudflare Workers で使う場合は [`@notion-headless-cms/adapter-cloudflare`](../adapter-cloudflare) の利用を推奨する。
11
+ `core` 自体は外部ランタイム依存ゼロ。`@notion-headless-cms/renderer` は `peerDependenciesMeta.optional: true` のため、renderer を `createCMS({ renderer })` で明示注入するか、動的フォールバック(`import("@notion-headless-cms/renderer")`)を使う場合のみインストールが必要。
12
12
 
13
- ## 使い方
13
+ Cloudflare Workers / Node.js / Next.js などで使う場合は、それぞれのアダプタを使うのが最短ルート:
14
14
 
15
- ```typescript
16
- import { CMS } from "@notion-headless-cms/core";
15
+ - [`@notion-headless-cms/adapter-cloudflare`](../adapter-cloudflare)
16
+ - [`@notion-headless-cms/adapter-node`](../adapter-node)
17
+ - [`@notion-headless-cms/adapter-next`](../adapter-next)
17
18
 
18
- const cms = new CMS({
19
- env: {
20
- NOTION_TOKEN: process.env.NOTION_TOKEN!,
21
- NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID!,
22
- },
19
+ ### サブパスエクスポート
20
+
21
+ 一部の機能はサブパス経由でインポートでき、バレル経由より小さくバンドルできる。
22
+
23
+ ```ts
24
+ import { CMSError } from "@notion-headless-cms/core/errors";
25
+ import { mergeHooks } from "@notion-headless-cms/core/hooks";
26
+ import { memoryDocumentCache } from "@notion-headless-cms/core/cache/memory";
27
+ ```
28
+
29
+ | サブパス | 内容 |
30
+ |---|---|
31
+ | `@notion-headless-cms/core` | 全エクスポート |
32
+ | `@notion-headless-cms/core/errors` | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` |
33
+ | `@notion-headless-cms/core/hooks` | `mergeHooks` / `mergeLoggers` |
34
+ | `@notion-headless-cms/core/cache/memory` | `memoryDocumentCache` / `memoryImageCache` / `*Options` |
35
+ | `@notion-headless-cms/core/cache/noop` | `noopDocumentCache` / `noopImageCache` |
36
+
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,
23
50
  schema: {
24
51
  publishedStatuses: ["公開"],
25
52
  properties: { slug: "Slug" },
26
53
  },
27
- cache: { ttlMs: 5 * 60 * 1000 },
54
+ cache: {
55
+ document: memoryDocumentCache({ maxItems: 500 }),
56
+ image: memoryImageCache({ maxItems: 200, maxSizeBytes: 64 * 1024 * 1024 }),
57
+ ttlMs: 5 * 60 * 1000,
58
+ },
28
59
  });
29
60
 
30
- // コンテンツ一覧を取得
31
- const { items } = await cms.getItems();
61
+ // ソース直接
62
+ const items = await cms.list();
63
+ const item = await cms.find("my-post");
64
+ const rendered = item ? await cms.render(item) : null;
32
65
 
33
- // スラッグで個別コンテンツを取得(HTML 付き)
34
- const cached = await cms.getItemBySlug("my-post");
66
+ // SWR
67
+ const { items: cachedItems } = await cms.cache.getList();
68
+ const cached = await cms.cache.get("my-post");
35
69
  console.log(cached?.html);
36
70
  ```
37
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
+
38
84
  ### カスタムコンテンツ型
39
85
 
40
86
  `BaseContentItem` を拡張することで任意のプロパティを追加できる。
41
87
 
42
88
  ```typescript
43
- import type { BaseContentItem, CMSConfig } from "@notion-headless-cms/core";
44
- import type { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
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";
45
92
 
46
93
  interface MyPost extends BaseContentItem {
47
94
  title: string;
48
95
  category: string;
49
96
  }
50
97
 
51
- const config: CMSConfig<MyPost> = {
52
- env: {
53
- NOTION_TOKEN: process.env.NOTION_TOKEN!,
54
- NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID!,
55
- },
56
- schema: {
57
- mapItem: (page: PageObjectResponse): MyPost => ({
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) => ({
58
103
  id: page.id,
59
104
  slug: /* ... */ "",
60
- status: /* ... */ "",
61
- publishedAt: page.created_time,
62
105
  updatedAt: page.last_edited_time,
106
+ publishedAt: page.created_time,
63
107
  title: /* ... */ "",
64
108
  category: /* ... */ "",
65
109
  }),
66
- },
67
- };
110
+ }),
111
+ });
68
112
  ```
69
113
 
114
+ > 型安全なマッピングは [`defineSchema`](../source-notion) を推奨。
115
+
70
116
  ## 主要 API
71
117
 
72
- ### `CMS` クラス
118
+ ### `CMS<T>` / `createCMS<T>(options)`
73
119
 
74
120
  | メソッド | 説明 |
75
121
  |---|---|
76
- | `getItems()` | コンテンツ一覧を返す |
77
- | `getItemBySlug(slug)` | スラッグで個別コンテンツを返す |
78
- | `renderItem(item)` | アイテムをレンダリングして `CachedItem` を返す |
79
- | `renderItemBySlug(slug)` | スラッグで取得してレンダリングする |
80
- | `getItemsCachedFirst(options?)` | キャッシュ優先でコンテンツ一覧を返す(SWR) |
81
- | `getItemCachedFirst(slug, options?)` | キャッシュ優先で個別コンテンツを返す(SWR |
82
- | `checkItemsUpdate(clientVersion)` | 一覧の更新有無を確認する |
83
- | `checkItemUpdate(slug, lastEdited)` | 個別コンテンツの更新有無を確認する |
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) を参照。
84
140
 
85
141
  ### ユーティリティ
86
142
 
87
143
  | エクスポート | 説明 |
88
144
  |---|---|
89
- | `CacheStore` | JSON/バイナリストレージの抽象ラッパー |
145
+ | `memoryDocumentCache<T>({ maxItems? })` | インメモリ DocumentCacheAdapter。`maxItems` 指定時は LRU 方式で古いものから削除 |
146
+ | `memoryImageCache({ maxItems?, maxSizeBytes? })` | インメモリ ImageCacheAdapter。件数とバイト数の両方で上限を設定可 |
147
+ | `noopDocumentCache<T>()` / `noopImageCache()` | 何もしないアダプタ |
90
148
  | `isStale(cachedAt, ttlMs)` | TTL 切れ判定 |
91
- | `sha256Hex(data)` | SHA256 ハッシュ生成(画像キー生成に使用) |
92
- | `CMSError` | カスタムエラークラス |
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)` | 内部エラー通知 |
93
168
 
94
169
  ## 主要な型
95
170
 
96
- - `CMSConfig<T>` — CMS 設定オブジェクト(`env` フィールドで認証情報を渡す)
97
- - `BaseContentItem` — デフォルト・カスタム型の基底インターフェース
98
- - `CachedItem<T>` キャッシュ済みコンテンツ(HTML + メタデータ)
99
- - `StorageAdapter`ストレージ抽象インターフェース
100
- - `CMSEnv`必須環境変数の型
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
+ ```
195
+
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`
101
197
 
102
198
  ## 関連パッケージ
103
199
 
104
- - [`@notion-headless-cms/adapter-cloudflare`](../adapter-cloudflare) — Cloudflare Workers 向けファクトリー
105
- - [`@notion-headless-cms/cache-r2`](../cache-r2) — R2 ストレージ実装
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 キャッシュ
@@ -0,0 +1,52 @@
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";
3
+
4
+ //#region src/cache/memory.d.ts
5
+ interface MemoryDocumentCacheOptions {
6
+ /** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
7
+ maxItems?: number;
8
+ }
9
+ interface MemoryImageCacheOptions {
10
+ /** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
11
+ maxItems?: number;
12
+ /** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */
13
+ maxSizeBytes?: number;
14
+ }
15
+ /** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
16
+ declare class MemoryDocumentCache<T extends BaseContentItem = BaseContentItem> implements DocumentCacheAdapter<T> {
17
+ readonly name = "memory-document";
18
+ private list;
19
+ private items;
20
+ private readonly maxItems;
21
+ constructor(options?: MemoryDocumentCacheOptions);
22
+ getList(): Promise<CachedItemList<T> | null>;
23
+ setList(data: CachedItemList<T>): Promise<void>;
24
+ getItem(slug: string): Promise<CachedItem<T> | null>;
25
+ setItem(slug: string, data: CachedItem<T>): Promise<void>;
26
+ invalidate(scope: InvalidateScope): Promise<void>;
27
+ private enforceLimit;
28
+ }
29
+ /** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
30
+ declare class MemoryImageCache implements ImageCacheAdapter {
31
+ readonly name = "memory-image";
32
+ private store;
33
+ private totalBytes;
34
+ private readonly maxItems;
35
+ private readonly maxSizeBytes;
36
+ constructor(options?: MemoryImageCacheOptions);
37
+ get(hash: string): Promise<StorageBinary | null>;
38
+ set(hash: string, data: ArrayBuffer, contentType: string): Promise<void>;
39
+ private enforceLimit;
40
+ }
41
+ /** インメモリキャッシュ(ドキュメント用)を生成する。 */
42
+ declare function memoryDocumentCache<T extends BaseContentItem = BaseContentItem>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T>;
43
+ /** インメモリキャッシュ(画像用)を生成する。 */
44
+ declare function memoryImageCache(options?: MemoryImageCacheOptions): ImageCacheAdapter;
45
+ /**
46
+ * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。
47
+ * memoryCache() はドキュメントキャッシュを返す(後方互換)。
48
+ */
49
+ declare function memoryCache<T extends BaseContentItem = BaseContentItem>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T>;
50
+ //#endregion
51
+ export { MemoryDocumentCache, MemoryDocumentCacheOptions, MemoryImageCache, MemoryImageCacheOptions, memoryCache, memoryDocumentCache, memoryImageCache };
52
+ //# sourceMappingURL=memory.d.mts.map
@@ -0,0 +1,112 @@
1
+ //#region src/cache/memory.ts
2
+ /**
3
+ * Map の挿入順を LRU として扱う軽量実装。
4
+ * `touch` で既存キーを末尾に移動、`enforceLimit` で古いキー(先頭)から削除する。
5
+ */
6
+ function touch(map, key) {
7
+ const v = map.get(key);
8
+ if (v === void 0) return;
9
+ map.delete(key);
10
+ map.set(key, v);
11
+ }
12
+ /** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
13
+ var MemoryDocumentCache = class {
14
+ name = "memory-document";
15
+ list = null;
16
+ items = /* @__PURE__ */ new Map();
17
+ maxItems;
18
+ constructor(options) {
19
+ this.maxItems = options?.maxItems;
20
+ }
21
+ getList() {
22
+ return Promise.resolve(this.list);
23
+ }
24
+ setList(data) {
25
+ this.list = data;
26
+ return Promise.resolve();
27
+ }
28
+ getItem(slug) {
29
+ const entry = this.items.get(slug);
30
+ if (entry) touch(this.items, slug);
31
+ return Promise.resolve(entry ?? null);
32
+ }
33
+ setItem(slug, data) {
34
+ if (this.items.has(slug)) this.items.delete(slug);
35
+ this.items.set(slug, data);
36
+ this.enforceLimit();
37
+ return Promise.resolve();
38
+ }
39
+ async invalidate(scope) {
40
+ if (scope === "all") {
41
+ this.list = null;
42
+ this.items.clear();
43
+ } else if ("slug" in scope) this.items.delete(scope.slug);
44
+ }
45
+ enforceLimit() {
46
+ if (this.maxItems === void 0) return;
47
+ while (this.items.size > this.maxItems) {
48
+ const firstKey = this.items.keys().next().value;
49
+ if (firstKey === void 0) break;
50
+ this.items.delete(firstKey);
51
+ }
52
+ }
53
+ };
54
+ /** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
55
+ var MemoryImageCache = class {
56
+ name = "memory-image";
57
+ store = /* @__PURE__ */ new Map();
58
+ totalBytes = 0;
59
+ maxItems;
60
+ maxSizeBytes;
61
+ constructor(options) {
62
+ this.maxItems = options?.maxItems;
63
+ this.maxSizeBytes = options?.maxSizeBytes;
64
+ }
65
+ get(hash) {
66
+ const entry = this.store.get(hash);
67
+ if (entry) touch(this.store, hash);
68
+ return Promise.resolve(entry ?? null);
69
+ }
70
+ set(hash, data, contentType) {
71
+ const existing = this.store.get(hash);
72
+ if (existing) {
73
+ this.totalBytes -= existing.data.byteLength;
74
+ this.store.delete(hash);
75
+ }
76
+ this.store.set(hash, {
77
+ data,
78
+ contentType
79
+ });
80
+ this.totalBytes += data.byteLength;
81
+ this.enforceLimit();
82
+ return Promise.resolve();
83
+ }
84
+ enforceLimit() {
85
+ while (this.maxItems !== void 0 && this.store.size > this.maxItems || this.maxSizeBytes !== void 0 && this.totalBytes > this.maxSizeBytes) {
86
+ const firstKey = this.store.keys().next().value;
87
+ if (firstKey === void 0) break;
88
+ const victim = this.store.get(firstKey);
89
+ if (victim) this.totalBytes -= victim.data.byteLength;
90
+ this.store.delete(firstKey);
91
+ }
92
+ }
93
+ };
94
+ /** インメモリキャッシュ(ドキュメント用)を生成する。 */
95
+ function memoryDocumentCache(options) {
96
+ return new MemoryDocumentCache(options);
97
+ }
98
+ /** インメモリキャッシュ(画像用)を生成する。 */
99
+ function memoryImageCache(options) {
100
+ return new MemoryImageCache(options);
101
+ }
102
+ /**
103
+ * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。
104
+ * memoryCache() はドキュメントキャッシュを返す(後方互換)。
105
+ */
106
+ function memoryCache(options) {
107
+ return new MemoryDocumentCache(options);
108
+ }
109
+ //#endregion
110
+ export { MemoryDocumentCache, MemoryImageCache, memoryCache, memoryDocumentCache, memoryImageCache };
111
+
112
+ //# sourceMappingURL=memory.mjs.map
@@ -0,0 +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"}
@@ -0,0 +1,11 @@
1
+ import { t as BaseContentItem } from "../content-BrwEY2_p.mjs";
2
+ import { n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-B-MG4yyg.mjs";
3
+
4
+ //#region src/cache/noop.d.ts
5
+ /** 何もしないドキュメントキャッシュを返す(シングルトン)。 */
6
+ declare function noopDocumentCache<T extends BaseContentItem = BaseContentItem>(): DocumentCacheAdapter<T>;
7
+ /** 何もしない画像キャッシュを返す(シングルトン)。 */
8
+ declare function noopImageCache(): ImageCacheAdapter;
9
+ //#endregion
10
+ export { noopDocumentCache, noopImageCache };
11
+ //# sourceMappingURL=noop.d.mts.map
@@ -0,0 +1,44 @@
1
+ //#region src/cache/noop.ts
2
+ /** 何もキャッシュしないドキュメントキャッシュ実装。常に null を返す。 */
3
+ var NoopDocumentCache = class {
4
+ name = "noop-document";
5
+ getList() {
6
+ return Promise.resolve(null);
7
+ }
8
+ setList(_data) {
9
+ return Promise.resolve();
10
+ }
11
+ getItem(_slug) {
12
+ return Promise.resolve(null);
13
+ }
14
+ setItem(_slug, _data) {
15
+ return Promise.resolve();
16
+ }
17
+ invalidate(_scope) {
18
+ return Promise.resolve();
19
+ }
20
+ };
21
+ /** 何もキャッシュしない画像キャッシュ実装。常に null を返す。 */
22
+ var NoopImageCache = class {
23
+ name = "noop-image";
24
+ get(_hash) {
25
+ return Promise.resolve(null);
26
+ }
27
+ set(_hash, _data, _contentType) {
28
+ return Promise.resolve();
29
+ }
30
+ };
31
+ const _noopDocument = new NoopDocumentCache();
32
+ const _noopImage = new NoopImageCache();
33
+ /** 何もしないドキュメントキャッシュを返す(シングルトン)。 */
34
+ function noopDocumentCache() {
35
+ return _noopDocument;
36
+ }
37
+ /** 何もしない画像キャッシュを返す(シングルトン)。 */
38
+ function noopImageCache() {
39
+ return _noopImage;
40
+ }
41
+ //#endregion
42
+ export { noopDocumentCache, noopImageCache };
43
+
44
+ //# sourceMappingURL=noop.mjs.map
@@ -0,0 +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"}
@@ -0,0 +1,45 @@
1
+ import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "./content-BrwEY2_p.mjs";
2
+
3
+ //#region src/types/cache.d.ts
4
+ /**
5
+ * キャッシュ無効化のスコープ。
6
+ * v1 API では `{ collection, slug? }` が主形式。
7
+ * 旧 `{ tag }` 形式は後方互換のため残す。
8
+ */
9
+ type InvalidateScope = "all" | {
10
+ slug: string;
11
+ } | {
12
+ tag: string;
13
+ } | {
14
+ collection: string;
15
+ } | {
16
+ collection: string;
17
+ slug: string;
18
+ };
19
+ /** ドキュメントキャッシュを抽象化するインターフェース。 */
20
+ interface DocumentCacheAdapter<T extends BaseContentItem = BaseContentItem> {
21
+ readonly name: string;
22
+ getList(): Promise<CachedItemList<T> | null>;
23
+ setList(data: CachedItemList<T>): Promise<void>;
24
+ getItem(slug: string): Promise<CachedItem<T> | null>;
25
+ setItem(slug: string, data: CachedItem<T>): Promise<void>;
26
+ invalidate?(scope: InvalidateScope): Promise<void>;
27
+ }
28
+ /** 画像キャッシュを抽象化するインターフェース。 */
29
+ interface ImageCacheAdapter {
30
+ readonly name: string;
31
+ get(hash: string): Promise<StorageBinary | null>;
32
+ set(hash: string, data: ArrayBuffer, contentType: string): Promise<void>;
33
+ }
34
+ /**
35
+ * キャッシュ設定。`"disabled"` を渡すと完全にキャッシュを無効化する。
36
+ * オブジェクトの場合、document / image それぞれ独立したアダプタを差し込める。
37
+ */
38
+ type CacheConfig<T extends BaseContentItem = BaseContentItem> = "disabled" | {
39
+ document?: DocumentCacheAdapter<T>;
40
+ image?: ImageCacheAdapter; /** キャッシュの有効期間(ミリ秒)。未設定の場合はTTLなし。 */
41
+ ttlMs?: number;
42
+ };
43
+ //#endregion
44
+ export { InvalidateScope as i, DocumentCacheAdapter as n, ImageCacheAdapter as r, CacheConfig as t };
45
+ //# sourceMappingURL=cache-B-MG4yyg.d.mts.map
@@ -0,0 +1,53 @@
1
+ //#region src/types/content.d.ts
2
+ /**
3
+ * ライブラリが動作するために必須なフィールド。
4
+ * 利用者はこのインターフェースを拡張して独自のコンテンツ型を定義する。
5
+ *
6
+ * @example
7
+ * interface Post extends BaseContentItem {
8
+ * title: string;
9
+ * author: string;
10
+ * }
11
+ * createCMS<Post>({ source: notionAdapter({ ... }) })
12
+ */
13
+ interface BaseContentItem {
14
+ /** Notion ページ ID(変更検知に必須)。 */
15
+ id: string;
16
+ /** URL キー(必須)。 */
17
+ slug: string;
18
+ /** 最終更新タイムスタンプ(変更検知に必須)。 */
19
+ updatedAt: string;
20
+ /** コンテンツのステータス。ステータスのない DB では省略可能。 */
21
+ status?: string;
22
+ /** 公開日時。日付プロパティのない DB では省略可能。 */
23
+ publishedAt?: string;
24
+ }
25
+ /** ストレージにキャッシュされたレンダリング済みコンテンツ。 */
26
+ interface CachedItem<T extends BaseContentItem = BaseContentItem> {
27
+ html: string;
28
+ item: T;
29
+ notionUpdatedAt: string;
30
+ cachedAt: number;
31
+ }
32
+ /** ストレージにキャッシュされたコンテンツ一覧。 */
33
+ interface CachedItemList<T extends BaseContentItem = BaseContentItem> {
34
+ items: T[];
35
+ cachedAt: number;
36
+ }
37
+ /** ストレージから取得したバイナリオブジェクト。 */
38
+ interface StorageBinary {
39
+ data: ArrayBuffer;
40
+ contentType?: string;
41
+ }
42
+ /** Notionのプロパティ名マッピング(すべてオプション)。 */
43
+ interface CMSSchemaProperties {
44
+ /** Notionのスラッグプロパティ名。デフォルト: 'Slug' */
45
+ slug?: string;
46
+ /** Notionのステータスプロパティ名。デフォルト: 'Status' */
47
+ status?: string;
48
+ /** Notionの公開日プロパティ名。デフォルト: 'CreatedAt' */
49
+ date?: string;
50
+ }
51
+ //#endregion
52
+ export { StorageBinary as a, CachedItemList as i, CMSSchemaProperties as n, CachedItem as r, BaseContentItem as t };
53
+ //# sourceMappingURL=content-BrwEY2_p.d.mts.map
@@ -0,0 +1,32 @@
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";
3
+ /**
4
+ * CMS エラーコード。
5
+ * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、
6
+ * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。
7
+ */
8
+ type CMSErrorCode = BuiltInCMSErrorCode | (string & {});
9
+ interface CMSErrorContext {
10
+ operation: string;
11
+ slug?: string;
12
+ dataSourceId?: string;
13
+ pageId?: string;
14
+ [key: string]: string | number | boolean | null | undefined;
15
+ }
16
+ declare class CMSError extends Error {
17
+ readonly code: CMSErrorCode;
18
+ readonly cause?: unknown;
19
+ readonly context: CMSErrorContext;
20
+ constructor(params: {
21
+ code: CMSErrorCode;
22
+ message: string;
23
+ cause?: unknown;
24
+ context: CMSErrorContext;
25
+ });
26
+ }
27
+ declare function isCMSError(error: unknown): error is CMSError;
28
+ /** エラーコードが特定の名前空間に属するかを判定する(例: "source/")。 */
29
+ declare function isCMSErrorInNamespace(error: unknown, namespace: string): error is CMSError;
30
+ //#endregion
31
+ export { CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace };
32
+ //# sourceMappingURL=errors.d.mts.map
@@ -0,0 +1,24 @@
1
+ //#region src/errors.ts
2
+ var CMSError = class extends Error {
3
+ code;
4
+ cause;
5
+ context;
6
+ constructor(params) {
7
+ super(params.message, { cause: params.cause });
8
+ this.name = "CMSError";
9
+ this.code = params.code;
10
+ this.cause = params.cause;
11
+ this.context = params.context;
12
+ }
13
+ };
14
+ function isCMSError(error) {
15
+ return error instanceof CMSError;
16
+ }
17
+ /** エラーコードが特定の名前空間に属するかを判定する(例: "source/")。 */
18
+ function isCMSErrorInNamespace(error, namespace) {
19
+ return isCMSError(error) && error.code.startsWith(namespace);
20
+ }
21
+ //#endregion
22
+ export { CMSError, isCMSError, isCMSErrorInNamespace };
23
+
24
+ //# sourceMappingURL=errors.mjs.map