@notion-headless-cms/core 0.3.1 → 0.3.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/dist/cache/memory.d.mts +3 -8
- package/dist/cache/memory.mjs +5 -8
- package/dist/cache/memory.mjs.map +1 -1
- package/dist/cache/noop.d.mts +2 -2
- package/dist/{cache-CqZbZeub.d.mts → cache-D051BP4G.d.mts} +3 -83
- package/dist/content-WydAfQtk.d.mts +131 -0
- package/dist/{hooks-DKnjPzHO.d.mts → hooks-DwCisXsr.d.mts} +2 -2
- package/dist/hooks.d.mts +1 -1
- package/dist/index.d.mts +7 -5
- package/dist/index.mjs +69 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/content-BlD8UPas.d.mts +0 -55
package/dist/cache/memory.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "../content-
|
|
2
|
-
import { l as InvalidateScope, n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-
|
|
1
|
+
import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "../content-WydAfQtk.mjs";
|
|
2
|
+
import { l as InvalidateScope, n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-D051BP4G.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/cache/memory.d.ts
|
|
5
5
|
interface MemoryDocumentCacheOptions {
|
|
@@ -42,11 +42,6 @@ declare class MemoryImageCache implements ImageCacheAdapter {
|
|
|
42
42
|
declare function memoryDocumentCache<T extends BaseContentItem = BaseContentItem>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T>;
|
|
43
43
|
/** インメモリキャッシュ(画像用)を生成する。 */
|
|
44
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
45
|
//#endregion
|
|
51
|
-
export { MemoryDocumentCache, MemoryDocumentCacheOptions, MemoryImageCache, MemoryImageCacheOptions,
|
|
46
|
+
export { MemoryDocumentCache, MemoryDocumentCacheOptions, MemoryImageCache, MemoryImageCacheOptions, memoryDocumentCache, memoryImageCache };
|
|
52
47
|
//# sourceMappingURL=memory.d.mts.map
|
package/dist/cache/memory.mjs
CHANGED
|
@@ -44,6 +44,10 @@ var MemoryDocumentCache = class {
|
|
|
44
44
|
}
|
|
45
45
|
this.list = null;
|
|
46
46
|
if ("slug" in scope) this.items.delete(scope.slug);
|
|
47
|
+
else {
|
|
48
|
+
const prefix = `${scope.collection}:`;
|
|
49
|
+
for (const key of [...this.items.keys()]) if (key.startsWith(prefix)) this.items.delete(key);
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
enforceLimit() {
|
|
49
53
|
if (this.maxItems === void 0) return;
|
|
@@ -102,14 +106,7 @@ function memoryDocumentCache(options) {
|
|
|
102
106
|
function memoryImageCache(options) {
|
|
103
107
|
return new MemoryImageCache(options);
|
|
104
108
|
}
|
|
105
|
-
/**
|
|
106
|
-
* ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。
|
|
107
|
-
* memoryCache() はドキュメントキャッシュを返す(後方互換)。
|
|
108
|
-
*/
|
|
109
|
-
function memoryCache(options) {
|
|
110
|
-
return new MemoryDocumentCache(options);
|
|
111
|
-
}
|
|
112
109
|
//#endregion
|
|
113
|
-
export { MemoryDocumentCache, MemoryImageCache,
|
|
110
|
+
export { MemoryDocumentCache, MemoryImageCache, memoryDocumentCache, memoryImageCache };
|
|
114
111
|
|
|
115
112
|
//# sourceMappingURL=memory.mjs.map
|
|
@@ -1 +1 @@
|
|
|
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//
|
|
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// list は常に破棄する\n\t\tthis.list = null;\n\t\tif (\"slug\" in scope) {\n\t\t\tthis.items.delete(scope.slug);\n\t\t} else {\n\t\t\t// { collection }: プレフィックスに一致するアイテムをすべて削除する\n\t\t\t// scopeDocumentCache 経由の場合、キーは `{collection}:{slug}` 形式になる\n\t\t\tconst prefix = `${scope.collection}:`;\n\t\t\tfor (const key of [...this.items.keys()]) {\n\t\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\t\tthis.items.delete(key);\n\t\t\t\t}\n\t\t\t}\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"],"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;;AAGD,OAAK,OAAO;AACZ,MAAI,UAAU,MACb,MAAK,MAAM,OAAO,MAAM,KAAK;OACvB;GAGN,MAAM,SAAS,GAAG,MAAM,WAAW;AACnC,QAAK,MAAM,OAAO,CAAC,GAAG,KAAK,MAAM,MAAM,CAAC,CACvC,KAAI,IAAI,WAAW,OAAO,CACzB,MAAK,MAAM,OAAO,IAAI;;;CAM1B,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"}
|
package/dist/cache/noop.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as BaseContentItem } from "../content-
|
|
2
|
-
import { n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-
|
|
1
|
+
import { t as BaseContentItem } from "../content-WydAfQtk.mjs";
|
|
2
|
+
import { n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-D051BP4G.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/cache/noop.d.ts
|
|
5
5
|
/** 何もしないドキュメントキャッシュを返す(シングルトン)。 */
|
|
@@ -1,79 +1,5 @@
|
|
|
1
|
-
import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "./content-
|
|
1
|
+
import { a as StorageBinary, c as ImageRef, i as CachedItemList, o as ContentBlock, r as CachedItem, t as BaseContentItem } from "./content-WydAfQtk.mjs";
|
|
2
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
3
|
//#region src/types/data-source.d.ts
|
|
78
4
|
/**
|
|
79
5
|
* Notion プロパティ1件の型情報とプロパティ名を保持する。
|
|
@@ -122,12 +48,6 @@ interface DataSource<T extends BaseContentItem = BaseContentItem> {
|
|
|
122
48
|
list(opts?: {
|
|
123
49
|
publishedStatuses?: readonly string[];
|
|
124
50
|
}): Promise<T[]>;
|
|
125
|
-
/**
|
|
126
|
-
* スラッグで単件取得。見つからなければ null。
|
|
127
|
-
* @deprecated `properties` + `findByProp` を使用する新形式では不要。
|
|
128
|
-
* 後方互換のため残す。
|
|
129
|
-
*/
|
|
130
|
-
findBySlug?(slug: string): Promise<T | null>;
|
|
131
51
|
/**
|
|
132
52
|
* 指定した Notion プロパティ名と値で1件検索する。
|
|
133
53
|
* Core が slug フィールドのルックアップに使用する。
|
|
@@ -224,5 +144,5 @@ type CacheConfig<T extends BaseContentItem = BaseContentItem> = "disabled" | {
|
|
|
224
144
|
ttlMs?: number;
|
|
225
145
|
};
|
|
226
146
|
//#endregion
|
|
227
|
-
export { CollectionConfig as a, InferCollectionItem as c, PropertyMap as d, WebhookConfig as f,
|
|
228
|
-
//# sourceMappingURL=cache-
|
|
147
|
+
export { CollectionConfig as a, InferCollectionItem as c, PropertyMap as d, WebhookConfig as f, CMSSchema as i, InvalidateScope as l, DocumentCacheAdapter as n, DataSource as o, ImageCacheAdapter as r, DataSourceFactory as s, CacheConfig as t, PropertyDef as u };
|
|
148
|
+
//# sourceMappingURL=cache-D051BP4G.d.mts.map
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//#region src/content/blocks.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* CMS 本文の中間表現 (AST)。
|
|
4
|
+
*
|
|
5
|
+
* DataSource が本文をこの配列にノーマライズして返す。
|
|
6
|
+
* レンダラー (`blocksToHtml`) やカスタム React コンポーネントは
|
|
7
|
+
* この型だけを扱えばよい。
|
|
8
|
+
*
|
|
9
|
+
* 対応が難しいブロック (Notion の column / synced block など) は
|
|
10
|
+
* `{ type: "raw", html }` にフォールバックする。
|
|
11
|
+
*/
|
|
12
|
+
type ContentBlock = {
|
|
13
|
+
type: "paragraph";
|
|
14
|
+
children: InlineNode[];
|
|
15
|
+
} | {
|
|
16
|
+
type: "heading";
|
|
17
|
+
level: 1 | 2 | 3;
|
|
18
|
+
children: InlineNode[];
|
|
19
|
+
} | {
|
|
20
|
+
type: "image";
|
|
21
|
+
src: string;
|
|
22
|
+
alt?: string;
|
|
23
|
+
cachedHash?: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "code";
|
|
26
|
+
lang?: string;
|
|
27
|
+
value: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "list";
|
|
30
|
+
ordered: boolean;
|
|
31
|
+
items: ContentBlock[][];
|
|
32
|
+
} | {
|
|
33
|
+
type: "quote";
|
|
34
|
+
children: ContentBlock[];
|
|
35
|
+
} | {
|
|
36
|
+
type: "divider";
|
|
37
|
+
} | {
|
|
38
|
+
type: "raw";
|
|
39
|
+
html: string;
|
|
40
|
+
};
|
|
41
|
+
/** paragraph / heading 等の子に並ぶインラインノード。 */
|
|
42
|
+
type InlineNode = {
|
|
43
|
+
type: "text";
|
|
44
|
+
value: string;
|
|
45
|
+
bold?: boolean;
|
|
46
|
+
italic?: boolean;
|
|
47
|
+
code?: boolean;
|
|
48
|
+
} | {
|
|
49
|
+
type: "link";
|
|
50
|
+
url: string;
|
|
51
|
+
children: InlineNode[];
|
|
52
|
+
} | {
|
|
53
|
+
type: "break";
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* `getItem({ include: { content: true } })` で返される本文。
|
|
57
|
+
* blocks は常に同梱。html / markdown は遅延生成。
|
|
58
|
+
*/
|
|
59
|
+
interface ContentResult {
|
|
60
|
+
/** 本文の AST (第一級)。 */
|
|
61
|
+
blocks: ContentBlock[];
|
|
62
|
+
/** 遅延 HTML。renderer が必要な場合のみ呼ぶ。 */
|
|
63
|
+
html(): Promise<string>;
|
|
64
|
+
/** 遅延 Markdown。 */
|
|
65
|
+
markdown(): Promise<string>;
|
|
66
|
+
}
|
|
67
|
+
/** 画像参照 (DataSource.resolveImageUrl に渡す)。 */
|
|
68
|
+
interface ImageRef {
|
|
69
|
+
/** 元の Notion 画像 URL (期限切れの可能性あり)。 */
|
|
70
|
+
originalUrl: string;
|
|
71
|
+
/** 関連するアイテム ID。 */
|
|
72
|
+
itemId?: string;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/types/content.d.ts
|
|
76
|
+
/**
|
|
77
|
+
* ライブラリが動作するために必須なフィールド。
|
|
78
|
+
* 利用者はこのインターフェースを拡張して独自のコンテンツ型を定義する。
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* interface Post extends BaseContentItem {
|
|
82
|
+
* title: string;
|
|
83
|
+
* author: string;
|
|
84
|
+
* }
|
|
85
|
+
* createCMS<Post>({ source: createNotionCollection({ ... }) })
|
|
86
|
+
*/
|
|
87
|
+
interface BaseContentItem {
|
|
88
|
+
/** Notion ページ ID(変更検知に必須)。 */
|
|
89
|
+
id: string;
|
|
90
|
+
/** URL キー(必須)。 */
|
|
91
|
+
slug: string;
|
|
92
|
+
/** Notion ページ名(title 型プロパティのテキスト)。 */
|
|
93
|
+
title?: string | null;
|
|
94
|
+
/** 最終更新タイムスタンプ(変更検知に必須)。 */
|
|
95
|
+
updatedAt: string;
|
|
96
|
+
/** コンテンツのステータス。ステータスのない DB では省略可能。 */
|
|
97
|
+
status?: string;
|
|
98
|
+
/** 公開日時。日付プロパティのない DB では省略可能。 */
|
|
99
|
+
publishedAt?: string;
|
|
100
|
+
}
|
|
101
|
+
/** ストレージにキャッシュされたレンダリング済みコンテンツ。 */
|
|
102
|
+
interface CachedItem<T extends BaseContentItem = BaseContentItem> {
|
|
103
|
+
html: string;
|
|
104
|
+
item: T;
|
|
105
|
+
notionUpdatedAt: string;
|
|
106
|
+
cachedAt: number;
|
|
107
|
+
blocks?: ContentBlock[];
|
|
108
|
+
markdown?: string;
|
|
109
|
+
}
|
|
110
|
+
/** ストレージにキャッシュされたコンテンツ一覧。 */
|
|
111
|
+
interface CachedItemList<T extends BaseContentItem = BaseContentItem> {
|
|
112
|
+
items: T[];
|
|
113
|
+
cachedAt: number;
|
|
114
|
+
}
|
|
115
|
+
/** ストレージから取得したバイナリオブジェクト。 */
|
|
116
|
+
interface StorageBinary {
|
|
117
|
+
data: ArrayBuffer;
|
|
118
|
+
contentType?: string;
|
|
119
|
+
}
|
|
120
|
+
/** Notionのプロパティ名マッピング(すべてオプション)。 */
|
|
121
|
+
interface CMSSchemaProperties {
|
|
122
|
+
/** Notionのスラッグプロパティ名。デフォルト: 'Slug' */
|
|
123
|
+
slug?: string;
|
|
124
|
+
/** Notionのステータスプロパティ名。デフォルト: 'Status' */
|
|
125
|
+
status?: string;
|
|
126
|
+
/** Notionの公開日プロパティ名。デフォルト: 'CreatedAt' */
|
|
127
|
+
date?: string;
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { StorageBinary as a, ImageRef as c, CachedItemList as i, InlineNode as l, CMSSchemaProperties as n, ContentBlock as o, CachedItem as r, ContentResult as s, BaseContentItem as t };
|
|
131
|
+
//# sourceMappingURL=content-WydAfQtk.d.mts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as CachedItem, t as BaseContentItem } from "./content-
|
|
1
|
+
import { r as CachedItem, t as BaseContentItem } from "./content-WydAfQtk.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-
|
|
60
|
+
//# sourceMappingURL=hooks-DwCisXsr.d.mts.map
|
package/dist/hooks.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as mergeLoggers, t as mergeHooks } from "./hooks-
|
|
1
|
+
import { n as mergeLoggers, t as mergeHooks } from "./hooks-DwCisXsr.mjs";
|
|
2
2
|
export { mergeHooks, mergeLoggers };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as StorageBinary, i as CachedItemList, n as CMSSchemaProperties, r as CachedItem, t as BaseContentItem } from "./content-
|
|
2
|
-
import { a as CollectionConfig, c as InferCollectionItem, d as PropertyMap, f as WebhookConfig,
|
|
3
|
-
import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-
|
|
4
|
-
import { MemoryDocumentCacheOptions, MemoryImageCacheOptions,
|
|
1
|
+
import { a as StorageBinary, c as ImageRef, i as CachedItemList, l as InlineNode, n as CMSSchemaProperties, o as ContentBlock, r as CachedItem, s as ContentResult, t as BaseContentItem } from "./content-WydAfQtk.mjs";
|
|
2
|
+
import { a as CollectionConfig, c as InferCollectionItem, d as PropertyMap, f as WebhookConfig, i as CMSSchema, l as InvalidateScope, n as DocumentCacheAdapter, o as DataSource, r as ImageCacheAdapter, s as DataSourceFactory, t as CacheConfig, u as PropertyDef } from "./cache-D051BP4G.mjs";
|
|
3
|
+
import { a as Logger, i as definePlugin, n as mergeLoggers, o as CMSHooks, r as CMSPlugin, s as MaybePromise, t as mergeHooks } from "./hooks-DwCisXsr.mjs";
|
|
4
|
+
import { MemoryDocumentCacheOptions, MemoryImageCacheOptions, memoryDocumentCache, memoryImageCache } from "./cache/memory.mjs";
|
|
5
5
|
import { noopDocumentCache, noopImageCache } from "./cache/noop.mjs";
|
|
6
6
|
import { CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace } from "./errors.mjs";
|
|
7
7
|
|
|
@@ -360,6 +360,8 @@ declare class CollectionClientImpl<T extends BaseContentItem> implements Collect
|
|
|
360
360
|
}>;
|
|
361
361
|
private attachContent;
|
|
362
362
|
private fetchList;
|
|
363
|
+
private checkAndUpdateItemBg;
|
|
364
|
+
private checkAndUpdateListBg;
|
|
363
365
|
private fetchListRaw;
|
|
364
366
|
private findRaw;
|
|
365
367
|
}
|
|
@@ -394,5 +396,5 @@ interface NodePresetOptions {
|
|
|
394
396
|
*/
|
|
395
397
|
declare function nodePreset(opts?: NodePresetOptions): Pick<CreateCMSOptions, "cache" | "renderer">;
|
|
396
398
|
//#endregion
|
|
397
|
-
export { type AdjacencyOptions, type BaseContentItem, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchema, type CMSSchemaProperties, type CacheConfig, type CachedItem, type CachedItemList, type CollectionClient, CollectionClientImpl, type CollectionConfig, type CollectionContext, type CollectionSemantics, type ContentBlock, type ContentConfig, type ContentResult, type CreateCMSOptions, DEFAULT_RETRY_CONFIG, type DataSource, type DataSourceFactory, type DataSourceMap, type DocumentCacheAdapter, type GetListOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheAdapter, type ImageRef, type InferCollectionItem, type InferDataSourceItem, type InlineNode, type InvalidateScope, type ItemWithContent, type Logger, type MaybePromise, type MemoryDocumentCacheOptions, type MemoryImageCacheOptions, type NodePresetOptions, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type RetryConfig, type SortOption, type StorageBinary, type WebhookConfig, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale,
|
|
399
|
+
export { type AdjacencyOptions, type BaseContentItem, type CMSClient, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSGlobalOps, type CMSHooks, type CMSPlugin, type CMSSchema, type CMSSchemaProperties, type CacheConfig, type CachedItem, type CachedItemList, type CollectionClient, CollectionClientImpl, type CollectionConfig, type CollectionContext, type CollectionSemantics, type ContentBlock, type ContentConfig, type ContentResult, type CreateCMSOptions, DEFAULT_RETRY_CONFIG, type DataSource, type DataSourceFactory, type DataSourceMap, type DocumentCacheAdapter, type GetListOptions, type HandlerAdapter, type HandlerOptions, type ImageCacheAdapter, type ImageRef, type InferCollectionItem, type InferDataSourceItem, type InlineNode, type InvalidateScope, type ItemWithContent, type Logger, type MaybePromise, type MemoryDocumentCacheOptions, type MemoryImageCacheOptions, type NodePresetOptions, type PropertyDef, type PropertyMap, type RateLimiterConfig, type RenderOptions, type RendererFn, type RendererPluginList, type RetryConfig, type SortOption, type StorageBinary, type WebhookConfig, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, memoryDocumentCache, memoryImageCache, mergeHooks, mergeLoggers, nodePreset, noopDocumentCache, noopImageCache, sha256Hex, withRetry };
|
|
398
400
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { memoryDocumentCache, memoryImageCache } from "./cache/memory.mjs";
|
|
2
2
|
import { noopDocumentCache, noopImageCache } from "./cache/noop.mjs";
|
|
3
3
|
import { CMSError, isCMSError, isCMSErrorInNamespace } from "./errors.mjs";
|
|
4
4
|
import { mergeHooks, mergeLoggers } from "./hooks.mjs";
|
|
@@ -150,7 +150,7 @@ async function buildCachedItem(item, ctx) {
|
|
|
150
150
|
/**
|
|
151
151
|
* renderer オプション未指定時のフォールバック。
|
|
152
152
|
* @notion-headless-cms/renderer を動的 import する。
|
|
153
|
-
*
|
|
153
|
+
* createCMS({ renderer }) で明示注入された場合はこのパスを通らない。
|
|
154
154
|
*/
|
|
155
155
|
async function loadDefaultRenderer() {
|
|
156
156
|
try {
|
|
@@ -210,7 +210,17 @@ var CollectionClientImpl = class {
|
|
|
210
210
|
}
|
|
211
211
|
async getItem(slug) {
|
|
212
212
|
const cached = await this.ctx.docCache.getItem(slug);
|
|
213
|
-
if (cached
|
|
213
|
+
if (cached) {
|
|
214
|
+
if (this.ctx.ttlMs !== void 0 && isStale(cached.cachedAt, this.ctx.ttlMs)) {
|
|
215
|
+
this.ctx.hooks.onCacheMiss?.(slug);
|
|
216
|
+
const item = await this.findRaw(slug);
|
|
217
|
+
if (!item) return null;
|
|
218
|
+
const entry = await buildCachedItem(item, this.ctx.render);
|
|
219
|
+
await this.ctx.docCache.setItem(slug, entry);
|
|
220
|
+
return this.attachContent(entry.item, entry);
|
|
221
|
+
}
|
|
222
|
+
const bg = this.checkAndUpdateItemBg(slug, cached);
|
|
223
|
+
if (this.ctx.waitUntil) this.ctx.waitUntil(bg);
|
|
214
224
|
this.ctx.hooks.onCacheHit?.(slug, cached);
|
|
215
225
|
return this.attachContent(cached.item, cached);
|
|
216
226
|
}
|
|
@@ -308,13 +318,23 @@ var CollectionClientImpl = class {
|
|
|
308
318
|
return markdownCache;
|
|
309
319
|
}
|
|
310
320
|
};
|
|
311
|
-
|
|
312
|
-
if (maybeBlocks) blocksCache = maybeBlocks;
|
|
321
|
+
if (cached.blocks) blocksCache = cached.blocks;
|
|
313
322
|
return Object.assign(Object.create(null), item, { content });
|
|
314
323
|
}
|
|
315
324
|
async fetchList() {
|
|
316
325
|
const cached = await this.ctx.docCache.getList();
|
|
317
|
-
if (cached
|
|
326
|
+
if (cached) {
|
|
327
|
+
if (this.ctx.ttlMs !== void 0 && isStale(cached.cachedAt, this.ctx.ttlMs)) {
|
|
328
|
+
this.ctx.hooks.onListCacheMiss?.();
|
|
329
|
+
const items = await this.fetchListRaw();
|
|
330
|
+
await this.ctx.docCache.setList({
|
|
331
|
+
items,
|
|
332
|
+
cachedAt: Date.now()
|
|
333
|
+
});
|
|
334
|
+
return items;
|
|
335
|
+
}
|
|
336
|
+
const bg = this.checkAndUpdateListBg(cached);
|
|
337
|
+
if (this.ctx.waitUntil) this.ctx.waitUntil(bg);
|
|
318
338
|
this.ctx.hooks.onListCacheHit?.(cached.items, cached.cachedAt);
|
|
319
339
|
return cached.items;
|
|
320
340
|
}
|
|
@@ -329,6 +349,39 @@ var CollectionClientImpl = class {
|
|
|
329
349
|
else await save;
|
|
330
350
|
return items;
|
|
331
351
|
}
|
|
352
|
+
async checkAndUpdateItemBg(slug, cached) {
|
|
353
|
+
try {
|
|
354
|
+
const item = await this.findRaw(slug);
|
|
355
|
+
if (!item) return;
|
|
356
|
+
if (this.ctx.source.getLastModified(item) !== cached.notionUpdatedAt) {
|
|
357
|
+
const entry = await buildCachedItem(item, this.ctx.render);
|
|
358
|
+
await this.ctx.docCache.setItem(slug, entry);
|
|
359
|
+
} else if (this.ctx.ttlMs !== void 0) await this.ctx.docCache.setItem(slug, {
|
|
360
|
+
...cached,
|
|
361
|
+
cachedAt: Date.now()
|
|
362
|
+
});
|
|
363
|
+
} catch (err) {
|
|
364
|
+
this.ctx.logger?.warn?.("SWR: アイテムのバックグラウンド差分チェックに失敗", {
|
|
365
|
+
slug,
|
|
366
|
+
error: err instanceof Error ? err.message : String(err)
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async checkAndUpdateListBg(cached) {
|
|
371
|
+
try {
|
|
372
|
+
const items = await this.fetchListRaw();
|
|
373
|
+
if (this.ctx.source.getListVersion(items) !== this.ctx.source.getListVersion(cached.items)) await this.ctx.docCache.setList({
|
|
374
|
+
items,
|
|
375
|
+
cachedAt: Date.now()
|
|
376
|
+
});
|
|
377
|
+
else if (this.ctx.ttlMs !== void 0) await this.ctx.docCache.setList({
|
|
378
|
+
...cached,
|
|
379
|
+
cachedAt: Date.now()
|
|
380
|
+
});
|
|
381
|
+
} catch (err) {
|
|
382
|
+
this.ctx.logger?.warn?.("SWR: リストのバックグラウンド差分チェックに失敗", { error: err instanceof Error ? err.message : String(err) });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
332
385
|
fetchListRaw() {
|
|
333
386
|
return withRetry(() => this.ctx.source.list({ publishedStatuses: this.ctx.publishedStatuses.length > 0 ? this.ctx.publishedStatuses : void 0 }), {
|
|
334
387
|
...this.ctx.retryConfig,
|
|
@@ -355,9 +408,7 @@ var CollectionClientImpl = class {
|
|
|
355
408
|
const notionPropName = slugField ? this.ctx.source.properties?.[slugField]?.notion : void 0;
|
|
356
409
|
let item;
|
|
357
410
|
const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);
|
|
358
|
-
const findBySlug = this.ctx.source.findBySlug?.bind(this.ctx.source);
|
|
359
411
|
if (notionPropName && findByProp) item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);
|
|
360
|
-
else if (findBySlug) item = await withRetry(() => findBySlug(slug), retryOpts);
|
|
361
412
|
else item = (await withRetry(() => this.ctx.source.list(), retryOpts)).find((i) => i.slug === slug) ?? null;
|
|
362
413
|
if (!item) return null;
|
|
363
414
|
if (this.ctx.accessibleStatuses.length > 0 && (!item.status || !this.ctx.accessibleStatuses.includes(item.status))) return null;
|
|
@@ -511,32 +562,19 @@ function hasImageCacheConfigured(cache) {
|
|
|
511
562
|
*/
|
|
512
563
|
function scopeDocumentCache(base, collection) {
|
|
513
564
|
const itemKey = (slug) => `${collection}:${slug}`;
|
|
514
|
-
const listKey = collection;
|
|
515
565
|
return {
|
|
516
566
|
name: `${base.name}@${collection}`,
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
},
|
|
522
|
-
async setList(data) {
|
|
523
|
-
const anyBase = base;
|
|
524
|
-
if (typeof anyBase.setListByKey === "function") return anyBase.setListByKey(listKey, data);
|
|
525
|
-
return base.setList(data);
|
|
526
|
-
},
|
|
527
|
-
async getItem(slug) {
|
|
528
|
-
const anyBase = base;
|
|
529
|
-
if (typeof anyBase.getItemByKey === "function") return anyBase.getItemByKey(itemKey(slug));
|
|
530
|
-
return base.getItem(slug);
|
|
531
|
-
},
|
|
532
|
-
async setItem(slug, data) {
|
|
533
|
-
const anyBase = base;
|
|
534
|
-
if (typeof anyBase.setItemByKey === "function") return anyBase.setItemByKey(itemKey(slug), data);
|
|
535
|
-
return base.setItem(slug, data);
|
|
536
|
-
},
|
|
567
|
+
getList: () => base.getList(),
|
|
568
|
+
setList: (data) => base.setList(data),
|
|
569
|
+
getItem: (slug) => base.getItem(itemKey(slug)),
|
|
570
|
+
setItem: (slug, data) => base.setItem(itemKey(slug), data),
|
|
537
571
|
async invalidate(scope) {
|
|
538
572
|
if (!base.invalidate) return;
|
|
539
573
|
if (scope === "all") return base.invalidate({ collection });
|
|
574
|
+
if ("slug" in scope) return base.invalidate({
|
|
575
|
+
collection: scope.collection,
|
|
576
|
+
slug: itemKey(scope.slug)
|
|
577
|
+
});
|
|
540
578
|
return base.invalidate(scope);
|
|
541
579
|
}
|
|
542
580
|
};
|
|
@@ -544,7 +582,7 @@ function scopeDocumentCache(base, collection) {
|
|
|
544
582
|
/**
|
|
545
583
|
* `preset` オプションを解決して `cache` / `renderer` のデフォルトを補完する内部関数。
|
|
546
584
|
* 明示的な `cache` / `renderer` がある場合はそちらが優先される。
|
|
547
|
-
* `preset` 未指定時は opts
|
|
585
|
+
* `preset` 未指定時は opts をそのまま返す。
|
|
548
586
|
*/
|
|
549
587
|
function resolvePreset(opts) {
|
|
550
588
|
if (opts.preset === "disabled") return {
|
|
@@ -690,6 +728,6 @@ function definePlugin(plugin) {
|
|
|
690
728
|
return plugin;
|
|
691
729
|
}
|
|
692
730
|
//#endregion
|
|
693
|
-
export { CMSError, CollectionClientImpl, DEFAULT_RETRY_CONFIG, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale,
|
|
731
|
+
export { CMSError, CollectionClientImpl, DEFAULT_RETRY_CONFIG, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, memoryDocumentCache, memoryImageCache, mergeHooks, mergeLoggers, nodePreset, noopDocumentCache, noopImageCache, sha256Hex, withRetry };
|
|
694
732
|
|
|
695
733
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/preset-node.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n\tconst data = new TextEncoder().encode(input);\n\tconst hash = await crypto.subtle.digest(\"SHA-256\", data);\n\treturn Array.from(new Uint8Array(hash))\n\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t.join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n\tif (ttlMs === undefined) return false;\n\treturn Date.now() - cachedAt > ttlMs;\n}\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheAdapter, StorageBinary } from \"./types/index\";\n\n/** レスポンスヘッダまたはURLの拡張子からContent-Typeを推測する。 */\nfunction inferContentType(\n\turl: string,\n\tresponseContentType: string | null,\n): string {\n\tif (responseContentType?.startsWith(\"image/\")) {\n\t\treturn responseContentType.split(\";\")[0].trim();\n\t}\n\tif (url.includes(\".png\")) return \"image/png\";\n\tif (url.includes(\".gif\")) return \"image/gif\";\n\tif (url.includes(\".webp\")) return \"image/webp\";\n\treturn \"image/jpeg\";\n}\n\n/**\n * Notion画像URLをfetchしてImageCacheAdapterにキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n\tcache: ImageCacheAdapter,\n\tnotionUrl: string,\n\timageProxyBase: string,\n): Promise<string> {\n\tconst hash = await sha256Hex(notionUrl);\n\tconst proxyUrl = `${imageProxyBase}/${hash}`;\n\n\tconst existing = await cache.get(hash);\n\tif (existing) return proxyUrl;\n\n\ttry {\n\t\tconst response = await fetch(notionUrl, {\n\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"cache/image_fetch_failed\",\n\t\t\t\tmessage: `Failed to fetch Notion image: HTTP ${response.status}`,\n\t\t\t\tcontext: {\n\t\t\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\t\t\tnotionUrl,\n\t\t\t\t\thttpStatus: response.status,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst data = await response.arrayBuffer();\n\t\tconst contentType = inferContentType(\n\t\t\tnotionUrl,\n\t\t\tresponse.headers.get(\"content-type\"),\n\t\t);\n\t\tawait cache.set(hash, data, contentType);\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"cache/io_failed\",\n\t\t\tmessage: \"Failed to fetch or cache Notion image.\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"fetchAndCacheImage\", notionUrl },\n\t\t});\n\t}\n\n\treturn proxyUrl;\n}\n\n/** ImageCacheAdapter と imageProxyBase から cacheImage 関数を構築するファクトリ。 */\nexport function buildCacheImageFn(\n\tcache: ImageCacheAdapter,\n\timageProxyBase: string,\n): (notionUrl: string) => Promise<string> {\n\treturn (notionUrl) => fetchAndCacheImage(cache, notionUrl, imageProxyBase);\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n\tBaseContentItem,\n\tCachedItem,\n\tCMSHooks,\n\tContentConfig,\n\tDataSource,\n\tImageCacheAdapter,\n\tLogger,\n\tRendererFn,\n} from \"./types/index\";\n\n/** `buildCachedItem` に必要な CMS の依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n\tsource: DataSource<T>;\n\trendererFn: RendererFn | undefined;\n\timgCache: ImageCacheAdapter;\n\thasImageCache: boolean;\n\timageProxyBase: string;\n\tcontentConfig: ContentConfig | undefined;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n}\n\n/**\n * キャッシュに保存する CachedItem を拡張し、`blocks` を同梱できるようにする。\n * `CachedItem<T>` の構造互換性を保ちつつ blocks を optional で載せる。\n */\nexport type CachedItemWithBlocks<T extends BaseContentItem> = CachedItem<T> & {\n\tblocks?: ContentBlock[];\n\tmarkdown?: string;\n};\n\n/**\n * コンテンツアイテムをソースから Markdown ロード → blocks 生成 → HTML レンダリング\n * → フック適用まで実行し、キャッシュ保存用の `CachedItem` を返す。\n */\nexport async function buildCachedItem<T extends BaseContentItem>(\n\titem: T,\n\tctx: RenderContext<T>,\n): Promise<CachedItemWithBlocks<T>> {\n\tconst start = Date.now();\n\tctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n\t\tslug: item.slug,\n\t\tpageId: item.id,\n\t});\n\tctx.hooks.onRenderStart?.(item.slug);\n\n\tlet markdown: string;\n\ttry {\n\t\tmarkdown = await ctx.source.loadMarkdown(item);\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"source/load_markdown_failed\",\n\t\t\tmessage: \"Failed to load markdown from source.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:loadMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tlet blocks: ContentBlock[] = [];\n\ttry {\n\t\tblocks = await ctx.source.loadBlocks(item);\n\t} catch (err) {\n\t\tctx.logger?.warn?.(\"loadBlocks に失敗したため raw フォールバック\", {\n\t\t\tslug: item.slug,\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\tblocks = [];\n\t}\n\n\tconst cacheImage = ctx.hasImageCache\n\t\t? buildCacheImageFn(ctx.imgCache, ctx.imageProxyBase)\n\t\t: undefined;\n\n\tlet html: string;\n\tconst rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\ttry {\n\t\thtml = await rendererFn(markdown, {\n\t\t\timageProxyBase: ctx.imageProxyBase,\n\t\t\tcacheImage,\n\t\t\tremarkPlugins: ctx.contentConfig?.remarkPlugins,\n\t\t\trehypePlugins: ctx.contentConfig?.rehypePlugins,\n\t\t});\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage: \"Failed to render markdown.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:renderMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tif (ctx.hooks.afterRender) {\n\t\thtml = await ctx.hooks.afterRender(html, item);\n\t}\n\n\tlet result: CachedItemWithBlocks<T> = {\n\t\thtml,\n\t\tblocks,\n\t\tmarkdown,\n\t\titem,\n\t\tnotionUpdatedAt: ctx.source.getLastModified(item),\n\t\tcachedAt: Date.now(),\n\t};\n\n\tif (ctx.hooks.beforeCache) {\n\t\tresult = (await ctx.hooks.beforeCache(result)) as CachedItemWithBlocks<T>;\n\t}\n\n\tconst durationMs = Date.now() - start;\n\tctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n\t\tslug: item.slug,\n\t\tdurationMs,\n\t});\n\tctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n\treturn result;\n}\n\n/**\n * renderer オプション未指定時のフォールバック。\n * @notion-headless-cms/renderer を動的 import する。\n * adapter-cloudflare / adapter-node は renderer を明示注入するためこのパスは通らない。\n */\nexport async function loadDefaultRenderer(): Promise<RendererFn> {\n\ttry {\n\t\tconst mod = await import(\"@notion-headless-cms/renderer\");\n\t\treturn mod.renderMarkdown as RendererFn;\n\t} catch (err) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage:\n\t\t\t\t\"renderer オプションが未指定で @notion-headless-cms/renderer が見つかりません。\" +\n\t\t\t\t\" createCMS({ renderer }) でレンダラーを注入するか、@notion-headless-cms/renderer をインストールしてください。\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"loadDefaultRenderer\" },\n\t\t});\n\t}\n}\n","export interface RetryConfig {\n\tretryOn: number[];\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n\t/** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n\tjitter?: boolean;\n\tonRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n\tretryOn: [429, 502, 503],\n\tmaxRetries: 4,\n\tbaseDelayMs: 1000,\n\tjitter: true,\n};\n\n/** 指数バックオフ(オプションでジッター付き)でリトライする。retryOn に含まれる HTTP エラーのみ対象。 */\nexport async function withRetry<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig,\n): Promise<T> {\n\tlet lastError: unknown;\n\tfor (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (err) {\n\t\t\tconst status = (err as { status?: number }).status;\n\t\t\tif (status === undefined || !config.retryOn.includes(status)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tlastError = err;\n\t\t\tif (attempt < config.maxRetries) {\n\t\t\t\tconfig.onRetry?.(attempt + 1, status);\n\t\t\t\tconst jitterFactor =\n\t\t\t\t\tconfig.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n\t\t\t\tconst delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t\t}\n\t\t}\n\t}\n\tthrow lastError;\n}\n","import { isStale } from \"./cache\";\nimport type { ContentBlock, ContentResult } from \"./content/blocks\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItem } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n\tAdjacencyOptions,\n\tBaseContentItem,\n\tCachedItem,\n\tCMSHooks,\n\tCollectionClient,\n\tDataSource,\n\tDocumentCacheAdapter,\n\tGetListOptions,\n\tItemWithContent,\n\tLogger,\n\tSortOption,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n */\nexport function collectionKey(collection: string, slug?: string): string {\n\treturn slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n\tcollection: string;\n\tsource: DataSource<T>;\n\tdocCache: DocumentCacheAdapter<T>;\n\trender: RenderContext<T>;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n\tttlMs: number | undefined;\n\tpublishedStatuses: string[];\n\taccessibleStatuses: string[];\n\tretryConfig: RetryConfig;\n\tmaxConcurrent: number;\n\twaitUntil: ((p: Promise<unknown>) => void) | undefined;\n\t/**\n\t * slug として使うフィールド名。\n\t * `createCMS({ collections })` で指定した値。\n\t * 設定時は `source.properties[slugField].notion` を Notion プロパティ名として\n\t * `findByProp` を呼び出す。\n\t */\n\tslugField?: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n\timplements CollectionClient<T>\n{\n\tconstructor(private readonly ctx: CollectionContext<T>) {}\n\n\t// ── 基本取得 ──────────────────────────────────────────────────────────\n\n\tasync getItem(slug: string): Promise<ItemWithContent<T> | null> {\n\t\tconst cached = await this.ctx.docCache.getItem(slug);\n\t\tif (cached && !isStale(cached.cachedAt, this.ctx.ttlMs)) {\n\t\t\tthis.ctx.hooks.onCacheHit?.(slug, cached);\n\t\t\treturn this.attachContent(cached.item, cached);\n\t\t}\n\n\t\tthis.ctx.hooks.onCacheMiss?.(slug);\n\t\tconst item = await this.findRaw(slug);\n\t\tif (!item) return null;\n\n\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\tconst save = this.ctx.docCache.setItem(slug, entry);\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\n\t\treturn this.attachContent(entry.item, entry);\n\t}\n\n\tasync getList(opts?: GetListOptions<T>): Promise<T[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn applyGetListOptions(items, opts);\n\t}\n\n\t// ── SSG / ナビゲーション ─────────────────────────────────────────────\n\n\tasync getStaticParams(): Promise<{ slug: string }[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => ({ slug: item.slug }));\n\t}\n\n\tasync getStaticPaths(): Promise<string[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => item.slug);\n\t}\n\n\tasync adjacent(\n\t\tslug: string,\n\t\topts?: AdjacencyOptions<T>,\n\t): Promise<{ prev: T | null; next: T | null }> {\n\t\tconst items = applyGetListOptions(await this.fetchList(), {\n\t\t\tsort: opts?.sort,\n\t\t});\n\t\tconst index = items.findIndex((it) => it.slug === slug);\n\t\tif (index === -1) return { prev: null, next: null };\n\t\treturn {\n\t\t\tprev: index > 0 ? (items[index - 1] ?? null) : null,\n\t\t\tnext: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n\t\t};\n\t}\n\n\t// ── キャッシュ ────────────────────────────────────────────────────────\n\n\tasync revalidate(scope?: \"all\" | { slug: string }): Promise<void> {\n\t\tif (!this.ctx.docCache.invalidate) return;\n\t\tif (scope === undefined || scope === \"all\") {\n\t\t\tawait this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n\t\t} else {\n\t\t\tawait this.ctx.docCache.invalidate({\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tslug: scope.slug,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync prefetch(opts?: {\n\t\tconcurrency?: number;\n\t\tonProgress?: (done: number, total: number) => void;\n\t}): Promise<{ ok: number; failed: number }> {\n\t\tconst items = await this.fetchListRaw();\n\t\tconst concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n\t\tlet ok = 0;\n\t\tlet failed = 0;\n\n\t\tfor (let i = 0; i < items.length; i += concurrency) {\n\t\t\tconst chunk = items.slice(i, i + concurrency);\n\t\t\tawait Promise.all(\n\t\t\t\tchunk.map(async (item) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rendered = await buildCachedItem(item, this.ctx.render);\n\t\t\t\t\t\tawait this.ctx.docCache.setItem(item.slug, rendered);\n\t\t\t\t\t\tok++;\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tfailed++;\n\t\t\t\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\t\t\t\"prefetch: アイテムの事前レンダリングに失敗\",\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\t\t\tpageId: item.id,\n\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t\topts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n\t\t}\n\n\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\treturn { ok, failed };\n\t}\n\n\t// ── 内部 ──────────────────────────────────────────────────────────────\n\n\tprivate attachContent(item: T, cached: CachedItem<T>): ItemWithContent<T> {\n\t\tconst ctx = this.ctx;\n\t\tlet blocksCache: ContentBlock[] | undefined;\n\t\tlet htmlCache: string | undefined = cached.html;\n\t\tlet markdownCache: string | undefined;\n\n\t\tconst content: ContentResult = {\n\t\t\tget blocks(): ContentBlock[] {\n\t\t\t\tif (!blocksCache) {\n\t\t\t\t\t// lazy: キャッシュに blocks が保存されていない場合、簡易な single-raw に落とす\n\t\t\t\t\tblocksCache = [\n\t\t\t\t\t\t{ type: \"raw\", html: cached.html } satisfies ContentBlock,\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\treturn blocksCache;\n\t\t\t},\n\t\t\tasync html(): Promise<string> {\n\t\t\t\tif (htmlCache !== undefined) return htmlCache;\n\t\t\t\thtmlCache = cached.html;\n\t\t\t\treturn htmlCache;\n\t\t\t},\n\t\t\tasync markdown(): Promise<string> {\n\t\t\t\tif (markdownCache !== undefined) return markdownCache;\n\t\t\t\tmarkdownCache = await ctx.source.loadMarkdown(item);\n\t\t\t\treturn markdownCache;\n\t\t\t},\n\t\t};\n\n\t\t// blocks が CachedItem に乗っていれば優先的に採用する\n\t\tconst maybeBlocks = (cached as CachedItem<T> & { blocks?: ContentBlock[] })\n\t\t\t.blocks;\n\t\tif (maybeBlocks) blocksCache = maybeBlocks;\n\n\t\treturn Object.assign(Object.create(null) as object, item, {\n\t\t\tcontent,\n\t\t}) as ItemWithContent<T>;\n\t}\n\n\tprivate async fetchList(): Promise<T[]> {\n\t\tconst cached = await this.ctx.docCache.getList();\n\t\tif (cached && !isStale(cached.cachedAt, this.ctx.ttlMs)) {\n\t\t\tthis.ctx.hooks.onListCacheHit?.(cached.items, cached.cachedAt);\n\t\t\treturn cached.items;\n\t\t}\n\n\t\tthis.ctx.hooks.onListCacheMiss?.();\n\t\tconst items = await this.fetchListRaw();\n\t\tconst cachedAt = Date.now();\n\t\tconst save = this.ctx.docCache.setList({ items, cachedAt });\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\t\treturn items;\n\t}\n\n\tprivate fetchListRaw(): Promise<T[]> {\n\t\treturn withRetry(\n\t\t\t() =>\n\t\t\t\tthis.ctx.source.list({\n\t\t\t\t\tpublishedStatuses:\n\t\t\t\t\t\tthis.ctx.publishedStatuses.length > 0\n\t\t\t\t\t\t\t? this.ctx.publishedStatuses\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t}),\n\t\t\t{\n\t\t\t\t...this.ctx.retryConfig,\n\t\t\t\tonRetry: (attempt, status) => {\n\t\t\t\t\tthis.ctx.logger?.warn?.(\"getList() リトライ中\", { attempt, status });\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate async findRaw(slug: string): Promise<T | null> {\n\t\tconst retryOpts = {\n\t\t\t...this.ctx.retryConfig,\n\t\t\tonRetry: (attempt: number, status: number) => {\n\t\t\t\tthis.ctx.logger?.warn?.(\"getItem() リトライ中\", {\n\t\t\t\t\tattempt,\n\t\t\t\t\tstatus,\n\t\t\t\t\tslug,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\n\t\t// slug フィールドが指定され、DataSource が findByProp を持つ場合は\n\t\t// Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n\t\tconst slugField = this.ctx.slugField;\n\t\tconst notionPropName = slugField\n\t\t\t? this.ctx.source.properties?.[slugField]?.notion\n\t\t\t: undefined;\n\n\t\tlet item: T | null;\n\t\tconst findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n\t\tconst findBySlug = this.ctx.source.findBySlug?.bind(this.ctx.source);\n\t\tif (notionPropName && findByProp) {\n\t\t\titem = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n\t\t} else if (findBySlug) {\n\t\t\titem = await withRetry(() => findBySlug(slug), retryOpts);\n\t\t} else {\n\t\t\tconst all = await withRetry(() => this.ctx.source.list(), retryOpts);\n\t\t\titem = all.find((i) => i.slug === slug) ?? null;\n\t\t}\n\n\t\tif (!item) return null;\n\t\tif (\n\t\t\tthis.ctx.accessibleStatuses.length > 0 &&\n\t\t\t(!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\treturn item;\n\t}\n}\n\nfunction applyGetListOptions<T extends BaseContentItem>(\n\titems: T[],\n\topts?: GetListOptions<T>,\n): T[] {\n\tif (!opts) return items;\n\tlet result = items;\n\n\tif (opts.statuses && opts.statuses.length > 0) {\n\t\tconst allow = new Set(opts.statuses);\n\t\tresult = result.filter(\n\t\t\t(it) => it.status !== undefined && allow.has(it.status),\n\t\t);\n\t}\n\n\tif (opts.tag) {\n\t\tconst tag = opts.tag;\n\t\tresult = result.filter((it) => {\n\t\t\tconst tags = (it as unknown as { tags?: unknown }).tags;\n\t\t\treturn Array.isArray(tags) && tags.includes(tag);\n\t\t});\n\t}\n\n\tif (opts.where) {\n\t\tconst where = opts.where;\n\t\tresult = result.filter((it) =>\n\t\t\tObject.entries(where).every(\n\t\t\t\t([key, value]) => (it as Record<string, unknown>)[key] === value,\n\t\t\t),\n\t\t);\n\t}\n\n\tif (opts.sort) {\n\t\tresult = [...result].sort(makeComparator(opts.sort));\n\t}\n\n\tconst skip = opts.skip ?? 0;\n\tconst limit = opts.limit;\n\tif (skip > 0 || limit !== undefined) {\n\t\tresult = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n\t}\n\n\treturn result;\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n\tsort: SortOption<T>,\n): (a: T, b: T) => number {\n\tconst by = sort.by;\n\tconst dir = sort.direction === \"asc\" ? 1 : -1;\n\treturn (a, b) => {\n\t\tconst av = (a as Record<string, unknown>)[by];\n\t\tconst bv = (b as Record<string, unknown>)[by];\n\t\tif (av === bv) return 0;\n\t\tif (av === undefined) return 1;\n\t\tif (bv === undefined) return -1;\n\t\t// biome-ignore lint/suspicious/noExplicitAny: 汎用比較\n\t\treturn (av as any) > (bv as any) ? dir : -dir;\n\t};\n}\n","import type { ImageCacheAdapter, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n\t/** マウントするベースパス。デフォルト `/api/cms`。 */\n\tbasePath?: string;\n\t/** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n\timagesPath?: string;\n\t/** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n\trevalidatePath?: string;\n\t/** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n\twebhookSecret?: string;\n\t/** デフォルト実装を無効化する場合 true。 */\n\tdisabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n\timageCache: ImageCacheAdapter;\n\t/** コレクション名で DataSource を取り出し parseWebhook にフォワードする。 */\n\tparseWebhook(\n\t\treq: Request,\n\t\twebhookSecret: string | undefined,\n\t): Promise<InvalidateScope | null>;\n\trevalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n\tbasePath: \"/api/cms\",\n\timagesPath: \"/images\",\n\trevalidatePath: \"/revalidate\",\n} as const;\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` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n\tadapter: HandlerAdapter,\n\topts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n\tconst basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n\tconst imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n\tconst revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n\treturn async (req: Request): Promise<Response> => {\n\t\tconst url = new URL(req.url);\n\t\tconst path = url.pathname;\n\n\t\tif (!path.startsWith(basePath)) {\n\t\t\treturn new Response(\"Not Found\", { status: 404 });\n\t\t}\n\t\tconst rel = path.slice(basePath.length) || \"/\";\n\n\t\t// 画像: GET {basePath}/images/:hash\n\t\tif (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n\t\t\tconst hash = rel.slice(imagesPath.length + 1);\n\t\t\tif (!hash) return new Response(\"Bad Request\", { status: 400 });\n\t\t\tconst object = await adapter.imageCache.get(hash);\n\t\t\tif (!object) return new Response(\"Not Found\", { status: 404 });\n\t\t\tconst headers = new Headers();\n\t\t\tif (object.contentType) headers.set(\"content-type\", object.contentType);\n\t\t\theaders.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n\t\t\treturn new Response(object.data, { headers });\n\t\t}\n\n\t\t// Revalidate: POST {basePath}/revalidate\n\t\tif (req.method === \"POST\" && rel === revalidatePath) {\n\t\t\tconst scope = await adapter.parseWebhook(req, opts.webhookSecret);\n\t\t\tif (!scope) {\n\t\t\t\treturn new Response(JSON.stringify({ ok: false, reason: \"invalid\" }), {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait adapter.revalidate(scope);\n\t\t\treturn new Response(JSON.stringify({ ok: true, scope }), {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t};\n}\n\nfunction trimTrailingSlash(s: string): string {\n\treturn s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { memoryDocumentCache, memoryImageCache } from \"./cache/memory\";\nimport type { CacheConfig, CreateCMSOptions, RendererFn } from \"./types/index\";\n\n/** `nodePreset()` のオプション。 */\nexport interface NodePresetOptions {\n\t/**\n\t * キャッシュ設定。\n\t * - 省略時: memoryDocumentCache + memoryImageCache をデフォルト有効化\n\t * - `\"disabled\"`: キャッシュを完全無効化\n\t * - オブジェクト: 任意の cache adapter を差し込む\n\t */\n\tcache?: CacheConfig | \"disabled\";\n\t/** SWR の TTL (ミリ秒)。`cache` をオブジェクトで渡した場合はそちらが優先される。 */\n\tttlMs?: number;\n\t/** カスタムレンダラー。未指定時は core が @notion-headless-cms/renderer を動的ロード。 */\n\trenderer?: RendererFn;\n}\n\n/**\n * Node.js ランタイム向けの `createCMS` オプションプリセット。\n * メモリキャッシュをデフォルト有効にした `{ cache, renderer }` を返す。\n *\n * @example\n * import { createCMS, nodePreset } from \"@notion-headless-cms/core\";\n * import { cmsDataSources } from \"./generated/cms-schema\";\n *\n * const cms = createCMS({\n * ...nodePreset({ ttlMs: 5 * 60_000 }),\n * dataSources: cmsDataSources,\n * });\n */\nexport function nodePreset(\n\topts: NodePresetOptions = {},\n): Pick<CreateCMSOptions, \"cache\" | \"renderer\"> {\n\tif (opts.cache === \"disabled\") {\n\t\treturn { cache: undefined, renderer: opts.renderer };\n\t}\n\treturn {\n\t\tcache: opts.cache ?? {\n\t\t\tdocument: memoryDocumentCache(),\n\t\t\timage: memoryImageCache(),\n\t\t\tttlMs: opts.ttlMs,\n\t\t},\n\t\trenderer: opts.renderer,\n\t};\n}\n","import { noopDocumentCache, noopImageCache } 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 { nodePreset } from \"./preset-node\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n\tBaseContentItem,\n\tCacheConfig,\n\tCMSHooks,\n\tCollectionClient,\n\tCollectionSemantics,\n\tCreateCMSOptions,\n\tDataSource,\n\tDataSourceMap,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tInferDataSourceItem,\n\tInvalidateScope,\n\tLogger,\n\tRendererFn,\n} from \"./types/index\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<D>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<D extends DataSourceMap> = {\n\t[K in keyof D]: CollectionClient<InferDataSourceItem<D[K]>>;\n} & CMSGlobalOps<D>;\n\n/** `CMSClient` のグローバル名前空間。`$` プレフィックス。 */\nexport interface CMSGlobalOps<D extends DataSourceMap> {\n\t/** 登録されているコレクション名の一覧。 */\n\treadonly $collections: readonly (keyof D & string)[];\n\t/** 全コレクションまたは特定コレクションのキャッシュを無効化する。 */\n\t$revalidate(scope?: InvalidateScope): Promise<void>;\n\t/** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n\t$handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n\t/** ハッシュキーでキャッシュ画像を取得する。 */\n\t$getCachedImage(hash: string): ReturnType<ImageCacheAdapter[\"get\"]>;\n}\n\nfunction resolveDocumentCache(\n\tcache: CacheConfig | undefined,\n\t// biome-ignore lint/suspicious/noExplicitAny: 横断的に利用\n): DocumentCacheAdapter<any> {\n\tif (!cache || cache === \"disabled\" || !cache.document) {\n\t\treturn noopDocumentCache();\n\t}\n\treturn cache.document;\n}\n\nfunction resolveImageCache(cache: CacheConfig | undefined): ImageCacheAdapter {\n\tif (!cache || cache === \"disabled\" || !cache.image) {\n\t\treturn noopImageCache();\n\t}\n\treturn cache.image;\n}\n\nfunction resolveTtl(cache: CacheConfig | undefined): number | undefined {\n\tif (!cache || cache === \"disabled\") return undefined;\n\treturn cache.ttlMs;\n}\n\nfunction hasImageCacheConfigured(cache: CacheConfig | undefined): boolean {\n\tif (!cache || cache === \"disabled\") return false;\n\treturn !!cache.image;\n}\n\n/**\n * `{collection}:{slug}` キー空間で動作するコレクション別キャッシュビューを生成する。\n * 単一の `DocumentCacheAdapter` に複数コレクションを同居させるためのアダプタ。\n */\nfunction scopeDocumentCache<T extends BaseContentItem>(\n\t// biome-ignore lint/suspicious/noExplicitAny: 共有ストレージのため\n\tbase: DocumentCacheAdapter<any>,\n\tcollection: string,\n): DocumentCacheAdapter<T> {\n\tconst itemKey = (slug: string): string => `${collection}:${slug}`;\n\tconst listKey = collection;\n\n\treturn {\n\t\tname: `${base.name}@${collection}`,\n\t\tasync getList() {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: キー別 namespace\n\t\t\tconst anyBase = base as any;\n\t\t\tif (typeof anyBase.getListByKey === \"function\") {\n\t\t\t\treturn anyBase.getListByKey(listKey);\n\t\t\t}\n\t\t\treturn base.getList();\n\t\t},\n\t\tasync setList(data) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: キー別 namespace\n\t\t\tconst anyBase = base as any;\n\t\t\tif (typeof anyBase.setListByKey === \"function\") {\n\t\t\t\treturn anyBase.setListByKey(listKey, data);\n\t\t\t}\n\t\t\treturn base.setList(data);\n\t\t},\n\t\tasync getItem(slug) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: キー別 namespace\n\t\t\tconst anyBase = base as any;\n\t\t\tif (typeof anyBase.getItemByKey === \"function\") {\n\t\t\t\treturn anyBase.getItemByKey(itemKey(slug));\n\t\t\t}\n\t\t\treturn base.getItem(slug);\n\t\t},\n\t\tasync setItem(slug, data) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: キー別 namespace\n\t\t\tconst anyBase = base as any;\n\t\t\tif (typeof anyBase.setItemByKey === \"function\") {\n\t\t\t\treturn anyBase.setItemByKey(itemKey(slug), data);\n\t\t\t}\n\t\t\treturn base.setItem(slug, data);\n\t\t},\n\t\tasync invalidate(scope) {\n\t\t\tif (!base.invalidate) return;\n\t\t\tif (scope === \"all\") {\n\t\t\t\treturn base.invalidate({ collection });\n\t\t\t}\n\t\t\treturn base.invalidate(scope);\n\t\t},\n\t};\n}\n\n/**\n * `preset` オプションを解決して `cache` / `renderer` のデフォルトを補完する内部関数。\n * 明示的な `cache` / `renderer` がある場合はそちらが優先される。\n * `preset` 未指定時は opts をそのまま返す(後方互換)。\n */\nfunction resolvePreset<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CreateCMSOptions<D> {\n\tif (opts.preset === \"disabled\") {\n\t\treturn { ...opts, cache: undefined };\n\t}\n\tif (opts.preset === \"node\") {\n\t\tconst presetResult = nodePreset({ ttlMs: opts.ttlMs });\n\t\treturn {\n\t\t\t...opts,\n\t\t\tcache: opts.cache ?? presetResult.cache,\n\t\t\trenderer: opts.renderer ?? presetResult.renderer,\n\t\t};\n\t}\n\treturn opts;\n}\n\n/**\n * 複数の DataSource を束ねた CMS クライアントを生成する。\n *\n * @example\n * // Node.js(preset を使った簡潔な記法)\n * const cms = createCMS({ dataSources: cmsDataSources, preset: \"node\", ttlMs: 5 * 60_000 });\n *\n * @example\n * // 従来の spread パターン(引き続き動作する)\n * const cms = createCMS({ ...nodePreset({ ttlMs: 5 * 60_000 }), dataSources: cmsDataSources });\n *\n * @example\n * // キャッシュを細かく指定する場合\n * const cms = createCMS({\n * dataSources,\n * cache: { document, image, ttlMs: 60_000 },\n * });\n */\nexport function createCMS<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CMSClient<D> {\n\tif (!opts.dataSources || Object.keys(opts.dataSources).length === 0) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"core/config_invalid\",\n\t\t\tmessage:\n\t\t\t\t\"createCMS: dataSources に少なくとも1つのコレクションを指定してください。\",\n\t\t\tcontext: { operation: \"createCMS\" },\n\t\t});\n\t}\n\n\t// collections が指定されたコレクションは slug が必須。\n\tfor (const [name, col] of Object.entries(opts.collections ?? {})) {\n\t\tconst c = col as CollectionSemantics | undefined;\n\t\tif (c && !c.slug) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"core/config_invalid\",\n\t\t\t\tmessage: `createCMS: コレクション \"${name}\" の collections.slug は必須です。slug として使うフィールド名を指定してください。`,\n\t\t\t\tcontext: { operation: \"createCMS\", collection: name },\n\t\t\t});\n\t\t}\n\t}\n\n\tconst resolved = resolvePreset(opts);\n\n\tconst baseDocCache = resolveDocumentCache(resolved.cache);\n\tconst imgCache = resolveImageCache(resolved.cache);\n\tconst hasImageCache = hasImageCacheConfigured(resolved.cache);\n\tconst ttlMs = resolveTtl(resolved.cache);\n\tconst imageProxyBase =\n\t\topts.content?.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n\tconst contentConfig = opts.content;\n\tconst rendererFn: RendererFn | undefined = resolved.renderer;\n\tconst waitUntil = opts.waitUntil;\n\tconst logger: Logger | undefined = mergeLoggers(\n\t\topts.plugins ?? [],\n\t\topts.logger,\n\t);\n\tconst hooks: CMSHooks<BaseContentItem> = mergeHooks(\n\t\topts.plugins ?? [],\n\t\topts.hooks,\n\t\tlogger,\n\t);\n\tconst maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n\tconst retryConfig: RetryConfig = {\n\t\t...DEFAULT_RETRY_CONFIG,\n\t\t...(opts.rateLimiter ?? {}),\n\t};\n\n\tconst collectionNames = Object.keys(opts.dataSources) as (keyof D & string)[];\n\n\t// biome-ignore lint/suspicious/noExplicitAny: 各 T を保持\n\tconst collections: Record<string, CollectionClient<any>> = {};\n\tfor (const name of collectionNames) {\n\t\tconst source = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\tconst scopedCache = scopeDocumentCache<BaseContentItem>(baseDocCache, name);\n\t\tconst renderCtx: RenderContext<BaseContentItem> = {\n\t\t\tsource,\n\t\t\trendererFn,\n\t\t\timgCache,\n\t\t\thasImageCache,\n\t\t\timageProxyBase,\n\t\t\tcontentConfig,\n\t\t\thooks,\n\t\t\tlogger,\n\t\t};\n\t\tconst col = opts.collections?.[name] as CollectionSemantics | undefined;\n\t\tconst ctx: CollectionContext<BaseContentItem> = {\n\t\t\tcollection: name,\n\t\t\tsource,\n\t\t\tdocCache: scopedCache,\n\t\t\trender: renderCtx,\n\t\t\thooks,\n\t\t\tlogger,\n\t\t\tttlMs,\n\t\t\t// 公開条件は CollectionSemantics(createCMS の collections オプション)が権威\n\t\t\tpublishedStatuses: col?.publishedStatuses\n\t\t\t\t? [...col.publishedStatuses]\n\t\t\t\t: [],\n\t\t\taccessibleStatuses: col?.accessibleStatuses\n\t\t\t\t? [...col.accessibleStatuses]\n\t\t\t\t: [],\n\t\t\tretryConfig,\n\t\t\tmaxConcurrent,\n\t\t\twaitUntil,\n\t\t\tslugField: col?.slug,\n\t\t};\n\t\tcollections[name] = new CollectionClientImpl(ctx);\n\t}\n\n\tconst globalOps: CMSGlobalOps<D> = {\n\t\t$collections: collectionNames,\n\t\tasync $revalidate(scope?: InvalidateScope): Promise<void> {\n\t\t\tif (!baseDocCache.invalidate) return;\n\t\t\tawait baseDocCache.invalidate(scope ?? \"all\");\n\t\t},\n\t\t$handler(handlerOpts?: HandlerOptions) {\n\t\t\treturn createHandler(\n\t\t\t\t{\n\t\t\t\t\timageCache: imgCache,\n\t\t\t\t\tparseWebhook: async (req, webhookSecret) => {\n\t\t\t\t\t\t// 各 DataSource の parseWebhook を順に試す\n\t\t\t\t\t\tfor (const name of collectionNames) {\n\t\t\t\t\t\t\tconst ds = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\t\t\t\t\t\tif (ds.parseWebhook) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst scope = await ds.parseWebhook(req.clone(), {\n\t\t\t\t\t\t\t\t\t\tsecret: webhookSecret,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\treturn scope;\n\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\tlogger?.warn?.(\"parseWebhook 失敗\", {\n\t\t\t\t\t\t\t\t\t\tcollection: name,\n\t\t\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// フォールバック: { slug } だけの汎用 JSON body\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst body = (await req.json()) as {\n\t\t\t\t\t\t\t\tslug?: string;\n\t\t\t\t\t\t\t\tcollection?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif (body.slug && body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection, slug: body.slug };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t},\n\t\t\t\t\trevalidate: (scope) => globalOps.$revalidate(scope),\n\t\t\t\t},\n\t\t\t\thandlerOpts,\n\t\t\t);\n\t\t},\n\t\t$getCachedImage(hash) {\n\t\t\treturn imgCache.get(hash);\n\t\t},\n\t};\n\n\treturn Object.assign(\n\t\tObject.create(null) as object,\n\t\tcollections,\n\t\tglobalOps,\n\t) as CMSClient<D>;\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\tname: string;\n\thooks?: CMSHooks<T>;\n\tlogger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n\tplugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n\treturn plugin;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/cache.ts","../src/image.ts","../src/rendering.ts","../src/retry.ts","../src/collection.ts","../src/handler.ts","../src/preset-node.ts","../src/cms.ts","../src/types/plugin.ts"],"sourcesContent":["/** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */\nexport async function sha256Hex(input: string): Promise<string> {\n\tconst data = new TextEncoder().encode(input);\n\tconst hash = await crypto.subtle.digest(\"SHA-256\", data);\n\treturn Array.from(new Uint8Array(hash))\n\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t.join(\"\");\n}\n\n/**\n * キャッシュが有効期限切れかどうかを判定する。\n * ttlMs が未指定の場合は常に false(無期限有効)を返す。\n */\nexport function isStale(cachedAt: number, ttlMs?: number): boolean {\n\tif (ttlMs === undefined) return false;\n\treturn Date.now() - cachedAt > ttlMs;\n}\n","import { sha256Hex } from \"./cache\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport type { ImageCacheAdapter, StorageBinary } from \"./types/index\";\n\n/** レスポンスヘッダまたはURLの拡張子からContent-Typeを推測する。 */\nfunction inferContentType(\n\turl: string,\n\tresponseContentType: string | null,\n): string {\n\tif (responseContentType?.startsWith(\"image/\")) {\n\t\treturn responseContentType.split(\";\")[0].trim();\n\t}\n\tif (url.includes(\".png\")) return \"image/png\";\n\tif (url.includes(\".gif\")) return \"image/gif\";\n\tif (url.includes(\".webp\")) return \"image/webp\";\n\treturn \"image/jpeg\";\n}\n\n/**\n * Notion画像URLをfetchしてImageCacheAdapterにキャッシュし、プロキシURL を返す。\n * 既存キャッシュがあれば再fetchしない。\n */\nasync function fetchAndCacheImage(\n\tcache: ImageCacheAdapter,\n\tnotionUrl: string,\n\timageProxyBase: string,\n): Promise<string> {\n\tconst hash = await sha256Hex(notionUrl);\n\tconst proxyUrl = `${imageProxyBase}/${hash}`;\n\n\tconst existing = await cache.get(hash);\n\tif (existing) return proxyUrl;\n\n\ttry {\n\t\tconst response = await fetch(notionUrl, {\n\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"cache/image_fetch_failed\",\n\t\t\t\tmessage: `Failed to fetch Notion image: HTTP ${response.status}`,\n\t\t\t\tcontext: {\n\t\t\t\t\toperation: \"fetchAndCacheImage\",\n\t\t\t\t\tnotionUrl,\n\t\t\t\t\thttpStatus: response.status,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst data = await response.arrayBuffer();\n\t\tconst contentType = inferContentType(\n\t\t\tnotionUrl,\n\t\t\tresponse.headers.get(\"content-type\"),\n\t\t);\n\t\tawait cache.set(hash, data, contentType);\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"cache/io_failed\",\n\t\t\tmessage: \"Failed to fetch or cache Notion image.\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"fetchAndCacheImage\", notionUrl },\n\t\t});\n\t}\n\n\treturn proxyUrl;\n}\n\n/** ImageCacheAdapter と imageProxyBase から cacheImage 関数を構築するファクトリ。 */\nexport function buildCacheImageFn(\n\tcache: ImageCacheAdapter,\n\timageProxyBase: string,\n): (notionUrl: string) => Promise<string> {\n\treturn (notionUrl) => fetchAndCacheImage(cache, notionUrl, imageProxyBase);\n}\n\nexport type { StorageBinary };\n","import type { ContentBlock } from \"./content/blocks\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { buildCacheImageFn } from \"./image\";\nimport type {\n\tBaseContentItem,\n\tCachedItem,\n\tCMSHooks,\n\tContentConfig,\n\tDataSource,\n\tImageCacheAdapter,\n\tLogger,\n\tRendererFn,\n} from \"./types/index\";\n\n/** `buildCachedItem` に必要な CMS の依存を束ねたコンテキスト。 */\nexport interface RenderContext<T extends BaseContentItem> {\n\tsource: DataSource<T>;\n\trendererFn: RendererFn | undefined;\n\timgCache: ImageCacheAdapter;\n\thasImageCache: boolean;\n\timageProxyBase: string;\n\tcontentConfig: ContentConfig | undefined;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n}\n\n/**\n * コンテンツアイテムをソースから Markdown ロード → blocks 生成 → HTML レンダリング\n * → フック適用まで実行し、キャッシュ保存用の `CachedItem` を返す。\n */\nexport async function buildCachedItem<T extends BaseContentItem>(\n\titem: T,\n\tctx: RenderContext<T>,\n): Promise<CachedItem<T>> {\n\tconst start = Date.now();\n\tctx.logger?.info?.(\"コンテンツのレンダリング開始\", {\n\t\tslug: item.slug,\n\t\tpageId: item.id,\n\t});\n\tctx.hooks.onRenderStart?.(item.slug);\n\n\tlet markdown: string;\n\ttry {\n\t\tmarkdown = await ctx.source.loadMarkdown(item);\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"source/load_markdown_failed\",\n\t\t\tmessage: \"Failed to load markdown from source.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:loadMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tlet blocks: ContentBlock[] = [];\n\ttry {\n\t\tblocks = await ctx.source.loadBlocks(item);\n\t} catch (err) {\n\t\tctx.logger?.warn?.(\"loadBlocks に失敗したため raw フォールバック\", {\n\t\t\tslug: item.slug,\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\tblocks = [];\n\t}\n\n\tconst cacheImage = ctx.hasImageCache\n\t\t? buildCacheImageFn(ctx.imgCache, ctx.imageProxyBase)\n\t\t: undefined;\n\n\tlet html: string;\n\tconst rendererFn = ctx.rendererFn ?? (await loadDefaultRenderer());\n\ttry {\n\t\thtml = await rendererFn(markdown, {\n\t\t\timageProxyBase: ctx.imageProxyBase,\n\t\t\tcacheImage,\n\t\t\tremarkPlugins: ctx.contentConfig?.remarkPlugins,\n\t\t\trehypePlugins: ctx.contentConfig?.rehypePlugins,\n\t\t});\n\t} catch (err) {\n\t\tif (isCMSError(err)) throw err;\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage: \"Failed to render markdown.\",\n\t\t\tcause: err,\n\t\t\tcontext: {\n\t\t\t\toperation: \"buildCachedItem:renderMarkdown\",\n\t\t\t\tpageId: item.id,\n\t\t\t\tslug: item.slug,\n\t\t\t},\n\t\t});\n\t}\n\n\tif (ctx.hooks.afterRender) {\n\t\thtml = await ctx.hooks.afterRender(html, item);\n\t}\n\n\tlet result: CachedItem<T> = {\n\t\thtml,\n\t\tblocks,\n\t\tmarkdown,\n\t\titem,\n\t\tnotionUpdatedAt: ctx.source.getLastModified(item),\n\t\tcachedAt: Date.now(),\n\t};\n\n\tif (ctx.hooks.beforeCache) {\n\t\tresult = (await ctx.hooks.beforeCache(result)) as CachedItem<T>;\n\t}\n\n\tconst durationMs = Date.now() - start;\n\tctx.logger?.info?.(\"コンテンツのレンダリング完了\", {\n\t\tslug: item.slug,\n\t\tdurationMs,\n\t});\n\tctx.hooks.onRenderEnd?.(item.slug, durationMs);\n\n\treturn result;\n}\n\n/**\n * renderer オプション未指定時のフォールバック。\n * @notion-headless-cms/renderer を動的 import する。\n * createCMS({ renderer }) で明示注入された場合はこのパスを通らない。\n */\nexport async function loadDefaultRenderer(): Promise<RendererFn> {\n\ttry {\n\t\tconst mod = await import(\"@notion-headless-cms/renderer\");\n\t\treturn mod.renderMarkdown as RendererFn;\n\t} catch (err) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"renderer/failed\",\n\t\t\tmessage:\n\t\t\t\t\"renderer オプションが未指定で @notion-headless-cms/renderer が見つかりません。\" +\n\t\t\t\t\" createCMS({ renderer }) でレンダラーを注入するか、@notion-headless-cms/renderer をインストールしてください。\",\n\t\t\tcause: err,\n\t\t\tcontext: { operation: \"loadDefaultRenderer\" },\n\t\t});\n\t}\n}\n","export interface RetryConfig {\n\tretryOn: number[];\n\tmaxRetries: number;\n\tbaseDelayMs: number;\n\t/** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */\n\tjitter?: boolean;\n\tonRetry?: (attempt: number, status: number) => void;\n}\n\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n\tretryOn: [429, 502, 503],\n\tmaxRetries: 4,\n\tbaseDelayMs: 1000,\n\tjitter: true,\n};\n\n/** 指数バックオフ(オプションでジッター付き)でリトライする。retryOn に含まれる HTTP エラーのみ対象。 */\nexport async function withRetry<T>(\n\tfn: () => Promise<T>,\n\tconfig: RetryConfig,\n): Promise<T> {\n\tlet lastError: unknown;\n\tfor (let attempt = 0; attempt <= config.maxRetries; attempt++) {\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} catch (err) {\n\t\t\tconst status = (err as { status?: number }).status;\n\t\t\tif (status === undefined || !config.retryOn.includes(status)) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tlastError = err;\n\t\t\tif (attempt < config.maxRetries) {\n\t\t\t\tconfig.onRetry?.(attempt + 1, status);\n\t\t\t\tconst jitterFactor =\n\t\t\t\t\tconfig.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;\n\t\t\t\tconst delay = config.baseDelayMs * 2 ** attempt * jitterFactor;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t\t}\n\t\t}\n\t}\n\tthrow lastError;\n}\n","import { isStale } from \"./cache\";\nimport type { ContentBlock, ContentResult } from \"./content/blocks\";\nimport type { RenderContext } from \"./rendering\";\nimport { buildCachedItem } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { withRetry } from \"./retry\";\nimport type {\n\tAdjacencyOptions,\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tCMSHooks,\n\tCollectionClient,\n\tDataSource,\n\tDocumentCacheAdapter,\n\tGetListOptions,\n\tItemWithContent,\n\tLogger,\n\tSortOption,\n} from \"./types/index\";\n\n/**\n * コレクション別キャッシュキーを生成する。\n * item: `{collection}:{slug}` / list: `{collection}`\n */\nexport function collectionKey(collection: string, slug?: string): string {\n\treturn slug ? `${collection}:${slug}` : collection;\n}\n\n/** 単一コレクションの DataSource + SWR キャッシュ依存を束ねたコンテキスト。 */\nexport interface CollectionContext<T extends BaseContentItem> {\n\tcollection: string;\n\tsource: DataSource<T>;\n\tdocCache: DocumentCacheAdapter<T>;\n\trender: RenderContext<T>;\n\thooks: CMSHooks<T>;\n\tlogger: Logger | undefined;\n\tttlMs: number | undefined;\n\tpublishedStatuses: string[];\n\taccessibleStatuses: string[];\n\tretryConfig: RetryConfig;\n\tmaxConcurrent: number;\n\twaitUntil: ((p: Promise<unknown>) => void) | undefined;\n\t/**\n\t * slug として使うフィールド名。\n\t * `createCMS({ collections })` で指定した値。\n\t * 設定時は `source.properties[slugField].notion` を Notion プロパティ名として\n\t * `findByProp` を呼び出す。\n\t */\n\tslugField?: string;\n}\n\n/** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */\nexport class CollectionClientImpl<T extends BaseContentItem>\n\timplements CollectionClient<T>\n{\n\tconstructor(private readonly ctx: CollectionContext<T>) {}\n\n\t// ── 基本取得 ──────────────────────────────────────────────────────────\n\n\tasync getItem(slug: string): Promise<ItemWithContent<T> | null> {\n\t\tconst cached = await this.ctx.docCache.getItem(slug);\n\t\tif (cached) {\n\t\t\tif (\n\t\t\t\tthis.ctx.ttlMs !== undefined &&\n\t\t\t\tisStale(cached.cachedAt, this.ctx.ttlMs)\n\t\t\t) {\n\t\t\t\t// TTL 設定あり + 期限切れ: ブロッキングフェッチ(stale を返さない)\n\t\t\t\tthis.ctx.hooks.onCacheMiss?.(slug);\n\t\t\t\tconst item = await this.findRaw(slug);\n\t\t\t\tif (!item) return null;\n\t\t\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\t\t\tawait this.ctx.docCache.setItem(slug, entry);\n\t\t\t\treturn this.attachContent(entry.item, entry);\n\t\t\t}\n\t\t\t// TTL 未設定 or 期限内: キャッシュを即時返却し、バックグラウンドで差分チェック\n\t\t\tconst bg = this.checkAndUpdateItemBg(slug, cached);\n\t\t\tif (this.ctx.waitUntil) {\n\t\t\t\tthis.ctx.waitUntil(bg);\n\t\t\t}\n\t\t\tthis.ctx.hooks.onCacheHit?.(slug, cached);\n\t\t\treturn this.attachContent(cached.item, cached);\n\t\t}\n\n\t\t// キャッシュなし: 同期フェッチ\n\t\tthis.ctx.hooks.onCacheMiss?.(slug);\n\t\tconst item = await this.findRaw(slug);\n\t\tif (!item) return null;\n\n\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\tconst save = this.ctx.docCache.setItem(slug, entry);\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\n\t\treturn this.attachContent(entry.item, entry);\n\t}\n\n\tasync getList(opts?: GetListOptions<T>): Promise<T[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn applyGetListOptions(items, opts);\n\t}\n\n\t// ── SSG / ナビゲーション ─────────────────────────────────────────────\n\n\tasync getStaticParams(): Promise<{ slug: string }[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => ({ slug: item.slug }));\n\t}\n\n\tasync getStaticPaths(): Promise<string[]> {\n\t\tconst items = await this.fetchList();\n\t\treturn items.map((item) => item.slug);\n\t}\n\n\tasync adjacent(\n\t\tslug: string,\n\t\topts?: AdjacencyOptions<T>,\n\t): Promise<{ prev: T | null; next: T | null }> {\n\t\tconst items = applyGetListOptions(await this.fetchList(), {\n\t\t\tsort: opts?.sort,\n\t\t});\n\t\tconst index = items.findIndex((it) => it.slug === slug);\n\t\tif (index === -1) return { prev: null, next: null };\n\t\treturn {\n\t\t\tprev: index > 0 ? (items[index - 1] ?? null) : null,\n\t\t\tnext: index < items.length - 1 ? (items[index + 1] ?? null) : null,\n\t\t};\n\t}\n\n\t// ── キャッシュ ────────────────────────────────────────────────────────\n\n\tasync revalidate(scope?: \"all\" | { slug: string }): Promise<void> {\n\t\tif (!this.ctx.docCache.invalidate) return;\n\t\tif (scope === undefined || scope === \"all\") {\n\t\t\tawait this.ctx.docCache.invalidate({ collection: this.ctx.collection });\n\t\t} else {\n\t\t\tawait this.ctx.docCache.invalidate({\n\t\t\t\tcollection: this.ctx.collection,\n\t\t\t\tslug: scope.slug,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync prefetch(opts?: {\n\t\tconcurrency?: number;\n\t\tonProgress?: (done: number, total: number) => void;\n\t}): Promise<{ ok: number; failed: number }> {\n\t\tconst items = await this.fetchListRaw();\n\t\tconst concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;\n\t\tlet ok = 0;\n\t\tlet failed = 0;\n\n\t\tfor (let i = 0; i < items.length; i += concurrency) {\n\t\t\tconst chunk = items.slice(i, i + concurrency);\n\t\t\tawait Promise.all(\n\t\t\t\tchunk.map(async (item) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rendered = await buildCachedItem(item, this.ctx.render);\n\t\t\t\t\t\tawait this.ctx.docCache.setItem(item.slug, rendered);\n\t\t\t\t\t\tok++;\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tfailed++;\n\t\t\t\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\t\t\t\"prefetch: アイテムの事前レンダリングに失敗\",\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\t\t\tpageId: item.id,\n\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t\topts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);\n\t\t}\n\n\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\treturn { ok, failed };\n\t}\n\n\t// ── 内部 ──────────────────────────────────────────────────────────────\n\n\tprivate attachContent(item: T, cached: CachedItem<T>): ItemWithContent<T> {\n\t\tconst ctx = this.ctx;\n\t\tlet blocksCache: ContentBlock[] | undefined;\n\t\tlet htmlCache: string | undefined = cached.html;\n\t\tlet markdownCache: string | undefined;\n\n\t\tconst content: ContentResult = {\n\t\t\tget blocks(): ContentBlock[] {\n\t\t\t\tif (!blocksCache) {\n\t\t\t\t\t// lazy: キャッシュに blocks が保存されていない場合、簡易な single-raw に落とす\n\t\t\t\t\tblocksCache = [\n\t\t\t\t\t\t{ type: \"raw\", html: cached.html } satisfies ContentBlock,\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\treturn blocksCache;\n\t\t\t},\n\t\t\tasync html(): Promise<string> {\n\t\t\t\tif (htmlCache !== undefined) return htmlCache;\n\t\t\t\thtmlCache = cached.html;\n\t\t\t\treturn htmlCache;\n\t\t\t},\n\t\t\tasync markdown(): Promise<string> {\n\t\t\t\tif (markdownCache !== undefined) return markdownCache;\n\t\t\t\tmarkdownCache = await ctx.source.loadMarkdown(item);\n\t\t\t\treturn markdownCache;\n\t\t\t},\n\t\t};\n\n\t\tif (cached.blocks) blocksCache = cached.blocks;\n\n\t\treturn Object.assign(Object.create(null) as object, item, {\n\t\t\tcontent,\n\t\t}) as ItemWithContent<T>;\n\t}\n\n\tprivate async fetchList(): Promise<T[]> {\n\t\tconst cached = await this.ctx.docCache.getList();\n\t\tif (cached) {\n\t\t\tif (\n\t\t\t\tthis.ctx.ttlMs !== undefined &&\n\t\t\t\tisStale(cached.cachedAt, this.ctx.ttlMs)\n\t\t\t) {\n\t\t\t\t// TTL 設定あり + 期限切れ: ブロッキングフェッチ(stale を返さない)\n\t\t\t\tthis.ctx.hooks.onListCacheMiss?.();\n\t\t\t\tconst items = await this.fetchListRaw();\n\t\t\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\t\t\treturn items;\n\t\t\t}\n\t\t\t// TTL 未設定 or 期限内: キャッシュを即時返却し、バックグラウンドで差分チェック\n\t\t\tconst bg = this.checkAndUpdateListBg(cached);\n\t\t\tif (this.ctx.waitUntil) {\n\t\t\t\tthis.ctx.waitUntil(bg);\n\t\t\t}\n\t\t\tthis.ctx.hooks.onListCacheHit?.(cached.items, cached.cachedAt);\n\t\t\treturn cached.items;\n\t\t}\n\n\t\t// キャッシュなし: 同期フェッチ\n\t\tthis.ctx.hooks.onListCacheMiss?.();\n\t\tconst items = await this.fetchListRaw();\n\t\tconst cachedAt = Date.now();\n\t\tconst save = this.ctx.docCache.setList({ items, cachedAt });\n\t\tif (this.ctx.waitUntil) {\n\t\t\tthis.ctx.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\t\treturn items;\n\t}\n\n\tprivate async checkAndUpdateItemBg(\n\t\tslug: string,\n\t\tcached: CachedItem<T>,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst item = await this.findRaw(slug);\n\t\t\tif (!item) return;\n\t\t\tif (this.ctx.source.getLastModified(item) !== cached.notionUpdatedAt) {\n\t\t\t\t// 更新あり: 再レンダリングしてキャッシュを差し替える\n\t\t\t\tconst entry = await buildCachedItem(item, this.ctx.render);\n\t\t\t\tawait this.ctx.docCache.setItem(slug, entry);\n\t\t\t} else if (this.ctx.ttlMs !== undefined) {\n\t\t\t\t// 変更なし + TTL あり: cachedAt をリセットして次回の期限切れを先送りする\n\t\t\t\tawait this.ctx.docCache.setItem(slug, {\n\t\t\t\t\t...cached,\n\t\t\t\t\tcachedAt: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\"SWR: アイテムのバックグラウンド差分チェックに失敗\",\n\t\t\t\t{\n\t\t\t\t\tslug,\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async checkAndUpdateListBg(cached: CachedItemList<T>): Promise<void> {\n\t\ttry {\n\t\t\tconst items = await this.fetchListRaw();\n\t\t\tif (\n\t\t\t\tthis.ctx.source.getListVersion(items) !==\n\t\t\t\tthis.ctx.source.getListVersion(cached.items)\n\t\t\t) {\n\t\t\t\t// 更新あり: リストを差し替える\n\t\t\t\tawait this.ctx.docCache.setList({ items, cachedAt: Date.now() });\n\t\t\t} else if (this.ctx.ttlMs !== undefined) {\n\t\t\t\t// 変更なし + TTL あり: cachedAt をリセットする\n\t\t\t\tawait this.ctx.docCache.setList({\n\t\t\t\t\t...cached,\n\t\t\t\t\tcachedAt: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tthis.ctx.logger?.warn?.(\n\t\t\t\t\"SWR: リストのバックグラウンド差分チェックに失敗\",\n\t\t\t\t{\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate fetchListRaw(): Promise<T[]> {\n\t\treturn withRetry(\n\t\t\t() =>\n\t\t\t\tthis.ctx.source.list({\n\t\t\t\t\tpublishedStatuses:\n\t\t\t\t\t\tthis.ctx.publishedStatuses.length > 0\n\t\t\t\t\t\t\t? this.ctx.publishedStatuses\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t}),\n\t\t\t{\n\t\t\t\t...this.ctx.retryConfig,\n\t\t\t\tonRetry: (attempt, status) => {\n\t\t\t\t\tthis.ctx.logger?.warn?.(\"getList() リトライ中\", { attempt, status });\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate async findRaw(slug: string): Promise<T | null> {\n\t\tconst retryOpts = {\n\t\t\t...this.ctx.retryConfig,\n\t\t\tonRetry: (attempt: number, status: number) => {\n\t\t\t\tthis.ctx.logger?.warn?.(\"getItem() リトライ中\", {\n\t\t\t\t\tattempt,\n\t\t\t\t\tstatus,\n\t\t\t\t\tslug,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\n\t\t// slug フィールドが指定され、DataSource が findByProp を持つ場合は\n\t\t// Notion プロパティ名を解決して効率的なフィルタクエリを実行する。\n\t\tconst slugField = this.ctx.slugField;\n\t\tconst notionPropName = slugField\n\t\t\t? this.ctx.source.properties?.[slugField]?.notion\n\t\t\t: undefined;\n\n\t\tlet item: T | null;\n\t\tconst findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);\n\t\tif (notionPropName && findByProp) {\n\t\t\titem = await withRetry(() => findByProp(notionPropName, slug), retryOpts);\n\t\t} else {\n\t\t\t// フォールバック: list して線形探索\n\t\t\tconst all = await withRetry(() => this.ctx.source.list(), retryOpts);\n\t\t\titem = all.find((i) => i.slug === slug) ?? null;\n\t\t}\n\n\t\tif (!item) return null;\n\t\tif (\n\t\t\tthis.ctx.accessibleStatuses.length > 0 &&\n\t\t\t(!item.status || !this.ctx.accessibleStatuses.includes(item.status))\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\treturn item;\n\t}\n}\n\nfunction applyGetListOptions<T extends BaseContentItem>(\n\titems: T[],\n\topts?: GetListOptions<T>,\n): T[] {\n\tif (!opts) return items;\n\tlet result = items;\n\n\tif (opts.statuses && opts.statuses.length > 0) {\n\t\tconst allow = new Set(opts.statuses);\n\t\tresult = result.filter(\n\t\t\t(it) => it.status !== undefined && allow.has(it.status),\n\t\t);\n\t}\n\n\tif (opts.tag) {\n\t\tconst tag = opts.tag;\n\t\tresult = result.filter((it) => {\n\t\t\tconst tags = (it as unknown as { tags?: unknown }).tags;\n\t\t\treturn Array.isArray(tags) && tags.includes(tag);\n\t\t});\n\t}\n\n\tif (opts.where) {\n\t\tconst where = opts.where;\n\t\tresult = result.filter((it) =>\n\t\t\tObject.entries(where).every(\n\t\t\t\t([key, value]) => (it as Record<string, unknown>)[key] === value,\n\t\t\t),\n\t\t);\n\t}\n\n\tif (opts.sort) {\n\t\tresult = [...result].sort(makeComparator(opts.sort));\n\t}\n\n\tconst skip = opts.skip ?? 0;\n\tconst limit = opts.limit;\n\tif (skip > 0 || limit !== undefined) {\n\t\tresult = result.slice(skip, limit !== undefined ? skip + limit : undefined);\n\t}\n\n\treturn result;\n}\n\nfunction makeComparator<T extends BaseContentItem>(\n\tsort: SortOption<T>,\n): (a: T, b: T) => number {\n\tconst by = sort.by;\n\tconst dir = sort.direction === \"asc\" ? 1 : -1;\n\treturn (a, b) => {\n\t\tconst av = (a as Record<string, unknown>)[by];\n\t\tconst bv = (b as Record<string, unknown>)[by];\n\t\tif (av === bv) return 0;\n\t\tif (av === undefined) return 1;\n\t\tif (bv === undefined) return -1;\n\t\t// biome-ignore lint/suspicious/noExplicitAny: 汎用比較\n\t\treturn (av as any) > (bv as any) ? dir : -dir;\n\t};\n}\n","import type { ImageCacheAdapter, InvalidateScope } from \"./types/index\";\n\n/** `$handler()` の挙動設定。 */\nexport interface HandlerOptions {\n\t/** マウントするベースパス。デフォルト `/api/cms`。 */\n\tbasePath?: string;\n\t/** 画像プロキシのパス (basePath 相対)。デフォルト `/images/:hash`。 */\n\timagesPath?: string;\n\t/** revalidate webhook のパス (basePath 相対)。デフォルト `/revalidate`。 */\n\trevalidatePath?: string;\n\t/** Webhook 署名検証用シークレット (未指定なら検証スキップ)。 */\n\twebhookSecret?: string;\n\t/** デフォルト実装を無効化する場合 true。 */\n\tdisabled?: boolean;\n}\n\n/** `$handler()` が内部で依存する CMS 機能の最小セット。 */\nexport interface HandlerAdapter {\n\timageCache: ImageCacheAdapter;\n\t/** コレクション名で DataSource を取り出し parseWebhook にフォワードする。 */\n\tparseWebhook(\n\t\treq: Request,\n\t\twebhookSecret: string | undefined,\n\t): Promise<InvalidateScope | null>;\n\trevalidate(scope: InvalidateScope): Promise<void>;\n}\n\nconst DEFAULT_OPTS = {\n\tbasePath: \"/api/cms\",\n\timagesPath: \"/images\",\n\trevalidatePath: \"/revalidate\",\n} as const;\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` — Webhook 受信 + $revalidate()\n */\nexport function createHandler(\n\tadapter: HandlerAdapter,\n\topts: HandlerOptions = {},\n): (req: Request) => Promise<Response> {\n\tconst basePath = trimTrailingSlash(opts.basePath ?? DEFAULT_OPTS.basePath);\n\tconst imagesPath = opts.imagesPath ?? DEFAULT_OPTS.imagesPath;\n\tconst revalidatePath = opts.revalidatePath ?? DEFAULT_OPTS.revalidatePath;\n\n\treturn async (req: Request): Promise<Response> => {\n\t\tconst url = new URL(req.url);\n\t\tconst path = url.pathname;\n\n\t\tif (!path.startsWith(basePath)) {\n\t\t\treturn new Response(\"Not Found\", { status: 404 });\n\t\t}\n\t\tconst rel = path.slice(basePath.length) || \"/\";\n\n\t\t// 画像: GET {basePath}/images/:hash\n\t\tif (req.method === \"GET\" && rel.startsWith(`${imagesPath}/`)) {\n\t\t\tconst hash = rel.slice(imagesPath.length + 1);\n\t\t\tif (!hash) return new Response(\"Bad Request\", { status: 400 });\n\t\t\tconst object = await adapter.imageCache.get(hash);\n\t\t\tif (!object) return new Response(\"Not Found\", { status: 404 });\n\t\t\tconst headers = new Headers();\n\t\t\tif (object.contentType) headers.set(\"content-type\", object.contentType);\n\t\t\theaders.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n\t\t\treturn new Response(object.data, { headers });\n\t\t}\n\n\t\t// Revalidate: POST {basePath}/revalidate\n\t\tif (req.method === \"POST\" && rel === revalidatePath) {\n\t\t\tconst scope = await adapter.parseWebhook(req, opts.webhookSecret);\n\t\t\tif (!scope) {\n\t\t\t\treturn new Response(JSON.stringify({ ok: false, reason: \"invalid\" }), {\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait adapter.revalidate(scope);\n\t\t\treturn new Response(JSON.stringify({ ok: true, scope }), {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(\"Not Found\", { status: 404 });\n\t};\n}\n\nfunction trimTrailingSlash(s: string): string {\n\treturn s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n","import { memoryDocumentCache, memoryImageCache } from \"./cache/memory\";\nimport type { CacheConfig, CreateCMSOptions, RendererFn } from \"./types/index\";\n\n/** `nodePreset()` のオプション。 */\nexport interface NodePresetOptions {\n\t/**\n\t * キャッシュ設定。\n\t * - 省略時: memoryDocumentCache + memoryImageCache をデフォルト有効化\n\t * - `\"disabled\"`: キャッシュを完全無効化\n\t * - オブジェクト: 任意の cache adapter を差し込む\n\t */\n\tcache?: CacheConfig | \"disabled\";\n\t/** SWR の TTL (ミリ秒)。`cache` をオブジェクトで渡した場合はそちらが優先される。 */\n\tttlMs?: number;\n\t/** カスタムレンダラー。未指定時は core が @notion-headless-cms/renderer を動的ロード。 */\n\trenderer?: RendererFn;\n}\n\n/**\n * Node.js ランタイム向けの `createCMS` オプションプリセット。\n * メモリキャッシュをデフォルト有効にした `{ cache, renderer }` を返す。\n *\n * @example\n * import { createCMS, nodePreset } from \"@notion-headless-cms/core\";\n * import { cmsDataSources } from \"./generated/cms-schema\";\n *\n * const cms = createCMS({\n * ...nodePreset({ ttlMs: 5 * 60_000 }),\n * dataSources: cmsDataSources,\n * });\n */\nexport function nodePreset(\n\topts: NodePresetOptions = {},\n): Pick<CreateCMSOptions, \"cache\" | \"renderer\"> {\n\tif (opts.cache === \"disabled\") {\n\t\treturn { cache: undefined, renderer: opts.renderer };\n\t}\n\treturn {\n\t\tcache: opts.cache ?? {\n\t\t\tdocument: memoryDocumentCache(),\n\t\t\timage: memoryImageCache(),\n\t\t\tttlMs: opts.ttlMs,\n\t\t},\n\t\trenderer: opts.renderer,\n\t};\n}\n","import { noopDocumentCache, noopImageCache } 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 { nodePreset } from \"./preset-node\";\nimport type { RenderContext } from \"./rendering\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG } from \"./retry\";\nimport type {\n\tBaseContentItem,\n\tCacheConfig,\n\tCMSHooks,\n\tCollectionClient,\n\tCollectionSemantics,\n\tCreateCMSOptions,\n\tDataSource,\n\tDataSourceMap,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tInferDataSourceItem,\n\tInvalidateScope,\n\tLogger,\n\tRendererFn,\n} from \"./types/index\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\n/** `CMSClient<D>` — コレクション別アクセス + グローバル操作の合成型。 */\nexport type CMSClient<D extends DataSourceMap> = {\n\t[K in keyof D]: CollectionClient<InferDataSourceItem<D[K]>>;\n} & CMSGlobalOps<D>;\n\n/** `CMSClient` のグローバル名前空間。`$` プレフィックス。 */\nexport interface CMSGlobalOps<D extends DataSourceMap> {\n\t/** 登録されているコレクション名の一覧。 */\n\treadonly $collections: readonly (keyof D & string)[];\n\t/** 全コレクションまたは特定コレクションのキャッシュを無効化する。 */\n\t$revalidate(scope?: InvalidateScope): Promise<void>;\n\t/** Web Standard なルーティングハンドラ (画像プロキシ / webhook) を生成する。 */\n\t$handler(opts?: HandlerOptions): (req: Request) => Promise<Response>;\n\t/** ハッシュキーでキャッシュ画像を取得する。 */\n\t$getCachedImage(hash: string): ReturnType<ImageCacheAdapter[\"get\"]>;\n}\n\nfunction resolveDocumentCache(\n\tcache: CacheConfig | undefined,\n\t// biome-ignore lint/suspicious/noExplicitAny: 横断的に利用\n): DocumentCacheAdapter<any> {\n\tif (!cache || cache === \"disabled\" || !cache.document) {\n\t\treturn noopDocumentCache();\n\t}\n\treturn cache.document;\n}\n\nfunction resolveImageCache(cache: CacheConfig | undefined): ImageCacheAdapter {\n\tif (!cache || cache === \"disabled\" || !cache.image) {\n\t\treturn noopImageCache();\n\t}\n\treturn cache.image;\n}\n\nfunction resolveTtl(cache: CacheConfig | undefined): number | undefined {\n\tif (!cache || cache === \"disabled\") return undefined;\n\treturn cache.ttlMs;\n}\n\nfunction hasImageCacheConfigured(cache: CacheConfig | undefined): boolean {\n\tif (!cache || cache === \"disabled\") return false;\n\treturn !!cache.image;\n}\n\n/**\n * `{collection}:{slug}` キー空間で動作するコレクション別キャッシュビューを生成する。\n * 単一の `DocumentCacheAdapter` に複数コレクションを同居させるためのアダプタ。\n */\nfunction scopeDocumentCache<T extends BaseContentItem>(\n\t// biome-ignore lint/suspicious/noExplicitAny: 共有ストレージのため\n\tbase: DocumentCacheAdapter<any>,\n\tcollection: string,\n): DocumentCacheAdapter<T> {\n\tconst itemKey = (slug: string): string => `${collection}:${slug}`;\n\n\treturn {\n\t\tname: `${base.name}@${collection}`,\n\t\tgetList: () => base.getList(),\n\t\tsetList: (data) => base.setList(data),\n\t\tgetItem: (slug) => base.getItem(itemKey(slug)),\n\t\tsetItem: (slug, data) => base.setItem(itemKey(slug), data),\n\t\tasync invalidate(scope) {\n\t\t\tif (!base.invalidate) return;\n\t\t\tif (scope === \"all\") {\n\t\t\t\treturn base.invalidate({ collection });\n\t\t\t}\n\t\t\tif (\"slug\" in scope) {\n\t\t\t\t// アイテムは `{collection}:{slug}` で保存されるため正しいキーに変換する\n\t\t\t\treturn base.invalidate({\n\t\t\t\t\tcollection: scope.collection,\n\t\t\t\t\tslug: itemKey(scope.slug),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn base.invalidate(scope);\n\t\t},\n\t};\n}\n\n/**\n * `preset` オプションを解決して `cache` / `renderer` のデフォルトを補完する内部関数。\n * 明示的な `cache` / `renderer` がある場合はそちらが優先される。\n * `preset` 未指定時は opts をそのまま返す。\n */\nfunction resolvePreset<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CreateCMSOptions<D> {\n\tif (opts.preset === \"disabled\") {\n\t\treturn { ...opts, cache: undefined };\n\t}\n\tif (opts.preset === \"node\") {\n\t\tconst presetResult = nodePreset({ ttlMs: opts.ttlMs });\n\t\treturn {\n\t\t\t...opts,\n\t\t\tcache: opts.cache ?? presetResult.cache,\n\t\t\trenderer: opts.renderer ?? presetResult.renderer,\n\t\t};\n\t}\n\treturn opts;\n}\n\n/**\n * 複数の DataSource を束ねた CMS クライアントを生成する。\n *\n * @example\n * // Node.js(preset を使った簡潔な記法)\n * const cms = createCMS({ dataSources: cmsDataSources, preset: \"node\", ttlMs: 5 * 60_000 });\n *\n * @example\n * // 従来の spread パターン(引き続き動作する)\n * const cms = createCMS({ ...nodePreset({ ttlMs: 5 * 60_000 }), dataSources: cmsDataSources });\n *\n * @example\n * // キャッシュを細かく指定する場合\n * const cms = createCMS({\n * dataSources,\n * cache: { document, image, ttlMs: 60_000 },\n * });\n */\nexport function createCMS<D extends DataSourceMap>(\n\topts: CreateCMSOptions<D>,\n): CMSClient<D> {\n\tif (!opts.dataSources || Object.keys(opts.dataSources).length === 0) {\n\t\tthrow new CMSError({\n\t\t\tcode: \"core/config_invalid\",\n\t\t\tmessage:\n\t\t\t\t\"createCMS: dataSources に少なくとも1つのコレクションを指定してください。\",\n\t\t\tcontext: { operation: \"createCMS\" },\n\t\t});\n\t}\n\n\t// collections が指定されたコレクションは slug が必須。\n\tfor (const [name, col] of Object.entries(opts.collections ?? {})) {\n\t\tconst c = col as CollectionSemantics | undefined;\n\t\tif (c && !c.slug) {\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"core/config_invalid\",\n\t\t\t\tmessage: `createCMS: コレクション \"${name}\" の collections.slug は必須です。slug として使うフィールド名を指定してください。`,\n\t\t\t\tcontext: { operation: \"createCMS\", collection: name },\n\t\t\t});\n\t\t}\n\t}\n\n\tconst resolved = resolvePreset(opts);\n\n\tconst baseDocCache = resolveDocumentCache(resolved.cache);\n\tconst imgCache = resolveImageCache(resolved.cache);\n\tconst hasImageCache = hasImageCacheConfigured(resolved.cache);\n\tconst ttlMs = resolveTtl(resolved.cache);\n\tconst imageProxyBase =\n\t\topts.content?.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n\tconst contentConfig = opts.content;\n\tconst rendererFn: RendererFn | undefined = resolved.renderer;\n\tconst waitUntil = opts.waitUntil;\n\tconst logger: Logger | undefined = mergeLoggers(\n\t\topts.plugins ?? [],\n\t\topts.logger,\n\t);\n\tconst hooks: CMSHooks<BaseContentItem> = mergeHooks(\n\t\topts.plugins ?? [],\n\t\topts.hooks,\n\t\tlogger,\n\t);\n\tconst maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n\tconst retryConfig: RetryConfig = {\n\t\t...DEFAULT_RETRY_CONFIG,\n\t\t...(opts.rateLimiter ?? {}),\n\t};\n\n\tconst collectionNames = Object.keys(opts.dataSources) as (keyof D & string)[];\n\n\t// biome-ignore lint/suspicious/noExplicitAny: 各 T を保持\n\tconst collections: Record<string, CollectionClient<any>> = {};\n\tfor (const name of collectionNames) {\n\t\tconst source = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\tconst scopedCache = scopeDocumentCache<BaseContentItem>(baseDocCache, name);\n\t\tconst renderCtx: RenderContext<BaseContentItem> = {\n\t\t\tsource,\n\t\t\trendererFn,\n\t\t\timgCache,\n\t\t\thasImageCache,\n\t\t\timageProxyBase,\n\t\t\tcontentConfig,\n\t\t\thooks,\n\t\t\tlogger,\n\t\t};\n\t\tconst col = opts.collections?.[name] as CollectionSemantics | undefined;\n\t\tconst ctx: CollectionContext<BaseContentItem> = {\n\t\t\tcollection: name,\n\t\t\tsource,\n\t\t\tdocCache: scopedCache,\n\t\t\trender: renderCtx,\n\t\t\thooks,\n\t\t\tlogger,\n\t\t\tttlMs,\n\t\t\t// 公開条件は CollectionSemantics(createCMS の collections オプション)が権威\n\t\t\tpublishedStatuses: col?.publishedStatuses\n\t\t\t\t? [...col.publishedStatuses]\n\t\t\t\t: [],\n\t\t\taccessibleStatuses: col?.accessibleStatuses\n\t\t\t\t? [...col.accessibleStatuses]\n\t\t\t\t: [],\n\t\t\tretryConfig,\n\t\t\tmaxConcurrent,\n\t\t\twaitUntil,\n\t\t\tslugField: col?.slug,\n\t\t};\n\t\tcollections[name] = new CollectionClientImpl(ctx);\n\t}\n\n\tconst globalOps: CMSGlobalOps<D> = {\n\t\t$collections: collectionNames,\n\t\tasync $revalidate(scope?: InvalidateScope): Promise<void> {\n\t\t\tif (!baseDocCache.invalidate) return;\n\t\t\tawait baseDocCache.invalidate(scope ?? \"all\");\n\t\t},\n\t\t$handler(handlerOpts?: HandlerOptions) {\n\t\t\treturn createHandler(\n\t\t\t\t{\n\t\t\t\t\timageCache: imgCache,\n\t\t\t\t\tparseWebhook: async (req, webhookSecret) => {\n\t\t\t\t\t\t// 各 DataSource の parseWebhook を順に試す\n\t\t\t\t\t\tfor (const name of collectionNames) {\n\t\t\t\t\t\t\tconst ds = opts.dataSources[name] as DataSource<BaseContentItem>;\n\t\t\t\t\t\t\tif (ds.parseWebhook) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst scope = await ds.parseWebhook(req.clone(), {\n\t\t\t\t\t\t\t\t\t\tsecret: webhookSecret,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\treturn scope;\n\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\tlogger?.warn?.(\"parseWebhook 失敗\", {\n\t\t\t\t\t\t\t\t\t\tcollection: name,\n\t\t\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// フォールバック: { slug } だけの汎用 JSON body\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst body = (await req.json()) as {\n\t\t\t\t\t\t\t\tslug?: string;\n\t\t\t\t\t\t\t\tcollection?: string;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif (body.slug && body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection, slug: body.slug };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (body.collection) {\n\t\t\t\t\t\t\t\treturn { collection: body.collection };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t},\n\t\t\t\t\trevalidate: (scope) => globalOps.$revalidate(scope),\n\t\t\t\t},\n\t\t\t\thandlerOpts,\n\t\t\t);\n\t\t},\n\t\t$getCachedImage(hash) {\n\t\t\treturn imgCache.get(hash);\n\t\t},\n\t};\n\n\treturn Object.assign(\n\t\tObject.create(null) as object,\n\t\tcollections,\n\t\tglobalOps,\n\t) as CMSClient<D>;\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\tname: string;\n\thooks?: CMSHooks<T>;\n\tlogger?: Partial<Logger>;\n}\n\nexport function definePlugin<T extends BaseContentItem>(\n\tplugin: CMSPlugin<T>,\n): CMSPlugin<T> {\n\treturn plugin;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,55 +0,0 @@
|
|
|
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
|
-
/** Notion ページ名(title 型プロパティのテキスト)。 */
|
|
19
|
-
title?: string | null;
|
|
20
|
-
/** 最終更新タイムスタンプ(変更検知に必須)。 */
|
|
21
|
-
updatedAt: string;
|
|
22
|
-
/** コンテンツのステータス。ステータスのない DB では省略可能。 */
|
|
23
|
-
status?: string;
|
|
24
|
-
/** 公開日時。日付プロパティのない DB では省略可能。 */
|
|
25
|
-
publishedAt?: string;
|
|
26
|
-
}
|
|
27
|
-
/** ストレージにキャッシュされたレンダリング済みコンテンツ。 */
|
|
28
|
-
interface CachedItem<T extends BaseContentItem = BaseContentItem> {
|
|
29
|
-
html: string;
|
|
30
|
-
item: T;
|
|
31
|
-
notionUpdatedAt: string;
|
|
32
|
-
cachedAt: number;
|
|
33
|
-
}
|
|
34
|
-
/** ストレージにキャッシュされたコンテンツ一覧。 */
|
|
35
|
-
interface CachedItemList<T extends BaseContentItem = BaseContentItem> {
|
|
36
|
-
items: T[];
|
|
37
|
-
cachedAt: number;
|
|
38
|
-
}
|
|
39
|
-
/** ストレージから取得したバイナリオブジェクト。 */
|
|
40
|
-
interface StorageBinary {
|
|
41
|
-
data: ArrayBuffer;
|
|
42
|
-
contentType?: string;
|
|
43
|
-
}
|
|
44
|
-
/** Notionのプロパティ名マッピング(すべてオプション)。 */
|
|
45
|
-
interface CMSSchemaProperties {
|
|
46
|
-
/** Notionのスラッグプロパティ名。デフォルト: 'Slug' */
|
|
47
|
-
slug?: string;
|
|
48
|
-
/** Notionのステータスプロパティ名。デフォルト: 'Status' */
|
|
49
|
-
status?: string;
|
|
50
|
-
/** Notionの公開日プロパティ名。デフォルト: 'CreatedAt' */
|
|
51
|
-
date?: string;
|
|
52
|
-
}
|
|
53
|
-
//#endregion
|
|
54
|
-
export { StorageBinary as a, CachedItemList as i, CMSSchemaProperties as n, CachedItem as r, BaseContentItem as t };
|
|
55
|
-
//# sourceMappingURL=content-BlD8UPas.d.mts.map
|