@notion-headless-cms/core 0.3.7 → 0.3.9

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.
@@ -1,47 +1,2 @@
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
-
4
- //#region src/cache/memory.d.ts
5
- interface MemoryDocumentCacheOptions {
6
- /** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
7
- maxItems?: number;
8
- }
9
- interface MemoryImageCacheOptions {
10
- /** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
11
- maxItems?: number;
12
- /** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */
13
- maxSizeBytes?: number;
14
- }
15
- /** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
16
- declare class MemoryDocumentCache<T extends BaseContentItem = BaseContentItem> implements DocumentCacheAdapter<T> {
17
- readonly name = "memory-document";
18
- private list;
19
- private items;
20
- private readonly maxItems;
21
- constructor(options?: MemoryDocumentCacheOptions);
22
- getList(): Promise<CachedItemList<T> | null>;
23
- setList(data: CachedItemList<T>): Promise<void>;
24
- getItem(slug: string): Promise<CachedItem<T> | null>;
25
- setItem(slug: string, data: CachedItem<T>): Promise<void>;
26
- invalidate(scope: InvalidateScope): Promise<void>;
27
- private enforceLimit;
28
- }
29
- /** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
30
- declare class MemoryImageCache implements ImageCacheAdapter {
31
- readonly name = "memory-image";
32
- private store;
33
- private totalBytes;
34
- private readonly maxItems;
35
- private readonly maxSizeBytes;
36
- constructor(options?: MemoryImageCacheOptions);
37
- get(hash: string): Promise<StorageBinary | null>;
38
- set(hash: string, data: ArrayBuffer, contentType: string): Promise<void>;
39
- private enforceLimit;
40
- }
41
- /** インメモリキャッシュ(ドキュメント用)を生成する。 */
42
- declare function memoryDocumentCache<T extends BaseContentItem = BaseContentItem>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T>;
43
- /** インメモリキャッシュ(画像用)を生成する。 */
44
- declare function memoryImageCache(options?: MemoryImageCacheOptions): ImageCacheAdapter;
45
- //#endregion
46
- export { MemoryDocumentCache, MemoryDocumentCacheOptions, MemoryImageCache, MemoryImageCacheOptions, memoryDocumentCache, memoryImageCache };
47
- //# sourceMappingURL=memory.d.mts.map
1
+ import { i as memoryCache, n as MemoryDocumentOptions, r as MemoryImageOptions, t as MemoryCacheOptions } from "../memory-V04Q09jC.mjs";
2
+ export { MemoryCacheOptions, MemoryDocumentOptions, MemoryImageOptions, memoryCache };
@@ -9,58 +9,91 @@ function touch(map, key) {
9
9
  map.delete(key);
10
10
  map.set(key, v);
11
11
  }
12
- /** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
13
- var MemoryDocumentCache = class {
14
- name = "memory-document";
15
- list = null;
16
- items = /* @__PURE__ */ new Map();
12
+ const itemKey = (collection, slug) => `${collection}:${slug}`;
13
+ /** インメモリのドキュメントオペレーション実装。プロセス再起動でクリアされる。 */
14
+ var MemoryDocumentOps = class {
15
+ lists = /* @__PURE__ */ new Map();
16
+ metas = /* @__PURE__ */ new Map();
17
+ contents = /* @__PURE__ */ new Map();
17
18
  maxItems;
18
19
  constructor(options) {
19
20
  this.maxItems = options?.maxItems;
20
21
  }
21
- getList() {
22
- return Promise.resolve(this.list);
22
+ getList(collection) {
23
+ return Promise.resolve(this.lists.get(collection) ?? null);
23
24
  }
24
- setList(data) {
25
- this.list = data;
25
+ setList(collection, data) {
26
+ this.lists.set(collection, data);
26
27
  return Promise.resolve();
27
28
  }
28
- getItem(slug) {
29
- const entry = this.items.get(slug);
30
- if (entry) touch(this.items, slug);
29
+ getMeta(collection, slug) {
30
+ const key = itemKey(collection, slug);
31
+ const entry = this.metas.get(key);
32
+ if (entry) touch(this.metas, key);
31
33
  return Promise.resolve(entry ?? null);
32
34
  }
33
- setItem(slug, data) {
34
- if (this.items.has(slug)) this.items.delete(slug);
35
- this.items.set(slug, data);
35
+ setMeta(collection, slug, data) {
36
+ const key = itemKey(collection, slug);
37
+ if (this.metas.has(key)) this.metas.delete(key);
38
+ this.metas.set(key, data);
36
39
  this.enforceLimit();
37
40
  return Promise.resolve();
38
41
  }
39
- async invalidate(scope) {
42
+ getContent(collection, slug) {
43
+ const key = itemKey(collection, slug);
44
+ const entry = this.contents.get(key);
45
+ if (entry) touch(this.contents, key);
46
+ return Promise.resolve(entry ?? null);
47
+ }
48
+ setContent(collection, slug, data) {
49
+ const key = itemKey(collection, slug);
50
+ if (this.contents.has(key)) this.contents.delete(key);
51
+ this.contents.set(key, data);
52
+ this.enforceLimit();
53
+ return Promise.resolve();
54
+ }
55
+ invalidate(scope) {
40
56
  if (scope === "all") {
41
- this.list = null;
42
- this.items.clear();
43
- return;
57
+ this.lists.clear();
58
+ this.metas.clear();
59
+ this.contents.clear();
60
+ return Promise.resolve();
61
+ }
62
+ const kind = scope.kind ?? "all";
63
+ const collection = scope.collection;
64
+ if ("slug" in scope) {
65
+ const key = itemKey(collection, scope.slug);
66
+ if (kind === "all" || kind === "meta") this.metas.delete(key);
67
+ if (kind === "all" || kind === "content") this.contents.delete(key);
68
+ return Promise.resolve();
69
+ }
70
+ if (kind === "all" || kind === "meta") {
71
+ this.lists.delete(collection);
72
+ const prefix = `${collection}:`;
73
+ for (const key of [...this.metas.keys()]) if (key.startsWith(prefix)) this.metas.delete(key);
44
74
  }
45
- this.list = null;
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);
75
+ if (kind === "all" || kind === "content") {
76
+ const prefix = `${collection}:`;
77
+ for (const key of [...this.contents.keys()]) if (key.startsWith(prefix)) this.contents.delete(key);
50
78
  }
79
+ return Promise.resolve();
51
80
  }
52
81
  enforceLimit() {
53
82
  if (this.maxItems === void 0) return;
54
- while (this.items.size > this.maxItems) {
55
- const firstKey = this.items.keys().next().value;
83
+ while (this.metas.size > this.maxItems) {
84
+ const firstKey = this.metas.keys().next().value;
85
+ if (firstKey === void 0) break;
86
+ this.metas.delete(firstKey);
87
+ }
88
+ while (this.contents.size > this.maxItems) {
89
+ const firstKey = this.contents.keys().next().value;
56
90
  if (firstKey === void 0) break;
57
- this.items.delete(firstKey);
91
+ this.contents.delete(firstKey);
58
92
  }
59
93
  }
60
94
  };
61
- /** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
62
- var MemoryImageCache = class {
63
- name = "memory-image";
95
+ /** インメモリの画像オペレーション実装。 */
96
+ var MemoryImageOps = class {
64
97
  store = /* @__PURE__ */ new Map();
65
98
  totalBytes = 0;
66
99
  maxItems;
@@ -98,15 +131,22 @@ var MemoryImageCache = class {
98
131
  }
99
132
  }
100
133
  };
101
- /** インメモリキャッシュ(ドキュメント用)を生成する。 */
102
- function memoryDocumentCache(options) {
103
- return new MemoryDocumentCache(options);
104
- }
105
- /** インメモリキャッシュ(画像用)を生成する。 */
106
- function memoryImageCache(options) {
107
- return new MemoryImageCache(options);
134
+ /**
135
+ * インメモリのキャッシュアダプタ。document + image 両方を担当する。
136
+ * プロセス再起動でクリアされるため、ローカル開発・SSG ビルド・テスト用途。
137
+ *
138
+ * @example
139
+ * cache: memoryCache({ ttlMs: 5 * 60_000, maxItems: 1000 })
140
+ */
141
+ function memoryCache(options) {
142
+ return {
143
+ name: "memory",
144
+ handles: ["document", "image"],
145
+ doc: new MemoryDocumentOps(options),
146
+ img: new MemoryImageOps(options)
147
+ };
108
148
  }
109
149
  //#endregion
110
- export { MemoryDocumentCache, MemoryImageCache, memoryDocumentCache, memoryImageCache };
150
+ export { memoryCache };
111
151
 
112
152
  //# 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// 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"}
1
+ {"version":3,"file":"memory.mjs","names":[],"sources":["../../src/cache/memory.ts"],"sourcesContent":["import type {\n\tBaseContentItem,\n\tCacheAdapter,\n\tCachedItemContent,\n\tCachedItemList,\n\tCachedItemMeta,\n\tDocumentCacheOps,\n\tImageCacheOps,\n\tInvalidateScope,\n\tStorageBinary,\n} from \"../types/index\";\n\nexport interface MemoryDocumentOptions {\n\t/** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxItems?: number;\n}\n\nexport interface MemoryImageOptions {\n\t/** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxItems?: number;\n\t/** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */\n\tmaxSizeBytes?: number;\n}\n\nexport interface MemoryCacheOptions\n\textends MemoryDocumentOptions,\n\t\tMemoryImageOptions {}\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\nconst itemKey = (collection: string, slug: string): string =>\n\t`${collection}:${slug}`;\n\n/** インメモリのドキュメントオペレーション実装。プロセス再起動でクリアされる。 */\nclass MemoryDocumentOps implements DocumentCacheOps {\n\t// biome-ignore lint/suspicious/noExplicitAny: 複数コレクション分の T を一括保持\n\tprivate lists = new Map<string, CachedItemList<any>>();\n\t// biome-ignore lint/suspicious/noExplicitAny: 同上\n\tprivate metas = new Map<string, CachedItemMeta<any>>();\n\tprivate contents = new Map<string, CachedItemContent>();\n\tprivate readonly maxItems: number | undefined;\n\n\tconstructor(options?: MemoryDocumentOptions) {\n\t\tthis.maxItems = options?.maxItems;\n\t}\n\n\tgetList<T extends BaseContentItem>(\n\t\tcollection: string,\n\t): Promise<CachedItemList<T> | null> {\n\t\treturn Promise.resolve(\n\t\t\t(this.lists.get(collection) as CachedItemList<T> | undefined) ?? null,\n\t\t);\n\t}\n\n\tsetList<T extends BaseContentItem>(\n\t\tcollection: string,\n\t\tdata: CachedItemList<T>,\n\t): Promise<void> {\n\t\tthis.lists.set(collection, data);\n\t\treturn Promise.resolve();\n\t}\n\n\tgetMeta<T extends BaseContentItem>(\n\t\tcollection: string,\n\t\tslug: string,\n\t): Promise<CachedItemMeta<T> | null> {\n\t\tconst key = itemKey(collection, slug);\n\t\tconst entry = this.metas.get(key) as CachedItemMeta<T> | undefined;\n\t\tif (entry) touch(this.metas, key);\n\t\treturn Promise.resolve(entry ?? null);\n\t}\n\n\tsetMeta<T extends BaseContentItem>(\n\t\tcollection: string,\n\t\tslug: string,\n\t\tdata: CachedItemMeta<T>,\n\t): Promise<void> {\n\t\tconst key = itemKey(collection, slug);\n\t\tif (this.metas.has(key)) this.metas.delete(key);\n\t\tthis.metas.set(key, data);\n\t\tthis.enforceLimit();\n\t\treturn Promise.resolve();\n\t}\n\n\tgetContent(\n\t\tcollection: string,\n\t\tslug: string,\n\t): Promise<CachedItemContent | null> {\n\t\tconst key = itemKey(collection, slug);\n\t\tconst entry = this.contents.get(key);\n\t\tif (entry) touch(this.contents, key);\n\t\treturn Promise.resolve(entry ?? null);\n\t}\n\n\tsetContent(\n\t\tcollection: string,\n\t\tslug: string,\n\t\tdata: CachedItemContent,\n\t): Promise<void> {\n\t\tconst key = itemKey(collection, slug);\n\t\tif (this.contents.has(key)) this.contents.delete(key);\n\t\tthis.contents.set(key, data);\n\t\tthis.enforceLimit();\n\t\treturn Promise.resolve();\n\t}\n\n\tinvalidate(scope: InvalidateScope): Promise<void> {\n\t\tif (scope === \"all\") {\n\t\t\tthis.lists.clear();\n\t\t\tthis.metas.clear();\n\t\t\tthis.contents.clear();\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\tconst kind = scope.kind ?? \"all\";\n\t\tconst collection = scope.collection;\n\t\tif (\"slug\" in scope) {\n\t\t\tconst key = itemKey(collection, scope.slug);\n\t\t\tif (kind === \"all\" || kind === \"meta\") this.metas.delete(key);\n\t\t\tif (kind === \"all\" || kind === \"content\") this.contents.delete(key);\n\t\t\t// 単一スラッグ無効化ではリストは触らない(リスト全体の整合は別管理)\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\t// コレクション全体\n\t\tif (kind === \"all\" || kind === \"meta\") {\n\t\t\tthis.lists.delete(collection);\n\t\t\tconst prefix = `${collection}:`;\n\t\t\tfor (const key of [...this.metas.keys()]) {\n\t\t\t\tif (key.startsWith(prefix)) this.metas.delete(key);\n\t\t\t}\n\t\t}\n\t\tif (kind === \"all\" || kind === \"content\") {\n\t\t\tconst prefix = `${collection}:`;\n\t\t\tfor (const key of [...this.contents.keys()]) {\n\t\t\t\tif (key.startsWith(prefix)) this.contents.delete(key);\n\t\t\t}\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\n\tprivate enforceLimit(): void {\n\t\tif (this.maxItems === undefined) return;\n\t\twhile (this.metas.size > this.maxItems) {\n\t\t\tconst firstKey = this.metas.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tthis.metas.delete(firstKey);\n\t\t}\n\t\twhile (this.contents.size > this.maxItems) {\n\t\t\tconst firstKey = this.contents.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tthis.contents.delete(firstKey);\n\t\t}\n\t}\n}\n\n/** インメモリの画像オペレーション実装。 */\nclass MemoryImageOps implements ImageCacheOps {\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?: MemoryImageOptions) {\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/**\n * インメモリのキャッシュアダプタ。document + image 両方を担当する。\n * プロセス再起動でクリアされるため、ローカル開発・SSG ビルド・テスト用途。\n *\n * @example\n * cache: memoryCache({ ttlMs: 5 * 60_000, maxItems: 1000 })\n */\nexport function memoryCache(options?: MemoryCacheOptions): CacheAdapter {\n\treturn {\n\t\tname: \"memory\",\n\t\thandles: [\"document\", \"image\"] as const,\n\t\tdoc: new MemoryDocumentOps(options),\n\t\timg: new MemoryImageOps(options),\n\t};\n}\n"],"mappings":"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"}
@@ -53,15 +53,16 @@ type InlineNode = {
53
53
  type: "break";
54
54
  };
55
55
  /**
56
- * `getItem({ include: { content: true } })` で返される本文。
57
- * blocks は常に同梱。html / markdown は遅延生成。
56
+ * `getItem()` で返される本文アクセサ。すべて遅延ロード(async)。
57
+ * メタデータと別キーから本文をフェッチするため、最初の呼び出しで I/O が発生し、
58
+ * 同一インスタンス内ではメモ化される。
58
59
  */
59
60
  interface ContentResult {
60
- /** 本文の AST (第一級)。 */
61
- blocks: ContentBlock[];
62
- /** 遅延 HTML。renderer が必要な場合のみ呼ぶ。 */
61
+ /** 本文 AST。 */
62
+ blocks(): Promise<ContentBlock[]>;
63
+ /** HTML。 */
63
64
  html(): Promise<string>;
64
- /** 遅延 Markdown。 */
65
+ /** Markdown。 */
65
66
  markdown(): Promise<string>;
66
67
  }
67
68
  /** 画像参照 (DataSource.resolveImageUrl に渡す)。 */
@@ -98,14 +99,28 @@ interface BaseContentItem {
98
99
  /** 公開日時。日付プロパティのない DB では省略可能。 */
99
100
  publishedAt?: string;
100
101
  }
101
- /** ストレージにキャッシュされたレンダリング済みコンテンツ。 */
102
- interface CachedItem<T extends BaseContentItem = BaseContentItem> {
103
- html: string;
102
+ /**
103
+ * メタデータのみの軽量キャッシュエントリ。
104
+ * `checkForUpdate` の差分判定や一覧表示など、本文を必要としないパスで使う。
105
+ */
106
+ interface CachedItemMeta<T extends BaseContentItem = BaseContentItem> {
104
107
  item: T;
108
+ /** Notion 側の最終更新時刻(差分検知用)。 */
109
+ notionUpdatedAt: string;
110
+ /** キャッシュ書き込み時刻(TTL 判定用、ms)。 */
111
+ cachedAt: number;
112
+ }
113
+ /**
114
+ * 本文(HTML / Markdown / blocks)のキャッシュエントリ。
115
+ * メタデータと別ストレージキーで保存し、必要時のみロードする。
116
+ */
117
+ interface CachedItemContent {
118
+ html: string;
119
+ markdown: string;
120
+ blocks: ContentBlock[];
121
+ /** メタデータ整合性検証用に同じ値を保持する。 */
105
122
  notionUpdatedAt: string;
106
123
  cachedAt: number;
107
- blocks?: ContentBlock[];
108
- markdown?: string;
109
124
  }
110
125
  /** ストレージにキャッシュされたコンテンツ一覧。 */
111
126
  interface CachedItemList<T extends BaseContentItem = BaseContentItem> {
@@ -127,5 +142,5 @@ interface CMSSchemaProperties {
127
142
  date?: string;
128
143
  }
129
144
  //#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
145
+ export { CachedItemMeta as a, ContentResult as c, CachedItemList as i, ImageRef as l, CMSSchemaProperties as n, StorageBinary as o, CachedItemContent as r, ContentBlock as s, BaseContentItem as t, InlineNode as u };
146
+ //# sourceMappingURL=content-BIcYVt2y.d.mts.map
@@ -1,14 +1,22 @@
1
- import { i as CachedItemList, r as CachedItem, t as BaseContentItem } from "./content-WydAfQtk.mjs";
1
+ import { a as CachedItemMeta, i as CachedItemList, r as CachedItemContent, t as BaseContentItem } from "./content-BIcYVt2y.mjs";
2
2
 
3
3
  //#region src/types/hooks.d.ts
4
4
  type MaybePromise<T> = T | Promise<T>;
5
5
  interface CMSHooks<T extends BaseContentItem = BaseContentItem> {
6
- beforeCache?: (item: CachedItem<T>) => MaybePromise<CachedItem<T>>;
6
+ /**
7
+ * 本文キャッシュ書き込み直前に呼ばれる。html を加工したい場合などに使う。
8
+ * 戻り値が新しい `CachedItemContent` として保存される。
9
+ */
10
+ beforeCacheContent?: (content: CachedItemContent, item: T) => MaybePromise<CachedItemContent>;
11
+ /** メタデータキャッシュ書き込み直前に呼ばれる。 */
12
+ beforeCacheMeta?: (meta: CachedItemMeta<T>) => MaybePromise<CachedItemMeta<T>>;
7
13
  afterRender?: (html: string, item: T) => MaybePromise<string>;
8
- onCacheHit?: (slug: string, item: CachedItem<T>) => void;
14
+ onCacheHit?: (slug: string, meta: CachedItemMeta<T>) => void;
9
15
  onCacheMiss?: (slug: string) => void;
10
- /** SWR バックグラウンド差分チェックで更新を検出し、キャッシュを差し替えたときに呼ばれる。 */
11
- onCacheRevalidated?: (slug: string, item: CachedItem<T>) => void;
16
+ /** SWR バックグラウンド差分チェックで更新を検出し、メタを差し替えたときに呼ばれる。 */
17
+ onCacheRevalidated?: (slug: string, meta: CachedItemMeta<T>) => void;
18
+ /** 本文キャッシュが(lazy ロード or バックグラウンド再生成で)更新されたときに呼ばれる。 */
19
+ onContentRevalidated?: (slug: string, content: CachedItemContent) => void;
12
20
  onListCacheHit?: (list: CachedItemList<T>) => void;
13
21
  onListCacheMiss?: () => void;
14
22
  /** SWR バックグラウンド差分チェックでリスト更新を検出し、キャッシュを差し替えたときに呼ばれる。 */
@@ -57,8 +65,8 @@ declare function definePlugin<T extends BaseContentItem>(plugin: CMSPlugin<T>):
57
65
  //#region src/hooks.d.ts
58
66
  /**
59
67
  * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。
60
- * beforeCache / afterRender はパイプライン(前の出力が次の入力)。
61
- * onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
68
+ * beforeCacheMeta / beforeCacheContent / afterRender はパイプライン(前の出力が次の入力)。
69
+ * オブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
62
70
  */
63
71
  declare function mergeHooks<T extends BaseContentItem>(plugins: CMSPlugin<T>[], directHooks?: CMSHooks<T>, logger?: Logger): CMSHooks<T>;
64
72
  /** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */
@@ -67,4 +75,4 @@ declare function mergeLoggers(plugins: Array<{
67
75
  }>, directLogger?: Logger): Logger | undefined;
68
76
  //#endregion
69
77
  export { Logger as a, definePlugin as i, mergeLoggers as n, CMSHooks as o, CMSPlugin as r, MaybePromise as s, mergeHooks as t };
70
- //# sourceMappingURL=hooks-D8Lgf-Co.d.mts.map
78
+ //# sourceMappingURL=hooks-C0Pv0WYd.d.mts.map
package/dist/hooks.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as mergeLoggers, t as mergeHooks } from "./hooks-D8Lgf-Co.mjs";
1
+ import { n as mergeLoggers, t as mergeHooks } from "./hooks-C0Pv0WYd.mjs";
2
2
  export { mergeHooks, mergeLoggers };
package/dist/hooks.mjs CHANGED
@@ -1,18 +1,20 @@
1
1
  //#region src/hooks.ts
2
2
  /**
3
3
  * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。
4
- * beforeCache / afterRender はパイプライン(前の出力が次の入力)。
5
- * onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
4
+ * beforeCacheMeta / beforeCacheContent / afterRender はパイプライン(前の出力が次の入力)。
5
+ * オブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
6
6
  */
7
7
  function mergeHooks(plugins, directHooks, logger) {
8
8
  const allHooks = [...plugins.map((p) => p.hooks ?? {}), ...directHooks ? [directHooks] : []];
9
9
  if (allHooks.length === 0) return {};
10
10
  return {
11
- beforeCache: buildPipeline(allHooks, "beforeCache"),
11
+ beforeCacheMeta: buildMetaPipeline(allHooks),
12
+ beforeCacheContent: buildContentPipeline(allHooks),
12
13
  afterRender: buildRenderPipeline(allHooks),
13
14
  onCacheHit: buildObserver(allHooks, "onCacheHit", logger),
14
15
  onCacheMiss: buildObserver(allHooks, "onCacheMiss", logger),
15
16
  onCacheRevalidated: buildObserver(allHooks, "onCacheRevalidated", logger),
17
+ onContentRevalidated: buildObserver(allHooks, "onContentRevalidated", logger),
16
18
  onListCacheHit: buildObserver(allHooks, "onListCacheHit", logger),
17
19
  onListCacheMiss: buildObserver(allHooks, "onListCacheMiss", logger),
18
20
  onListCacheRevalidated: buildObserver(allHooks, "onListCacheRevalidated", logger),
@@ -21,15 +23,24 @@ function mergeHooks(plugins, directHooks, logger) {
21
23
  onRenderEnd: buildObserver(allHooks, "onRenderEnd", logger)
22
24
  };
23
25
  }
24
- function buildPipeline(hooks, key) {
25
- const fns = hooks.map((h) => h[key]).filter(Boolean);
26
+ function buildMetaPipeline(hooks) {
27
+ const fns = hooks.map((h) => h.beforeCacheMeta).filter(Boolean);
26
28
  if (fns.length === 0) return void 0;
27
- return async (item) => {
28
- let current = item;
29
+ return async (meta) => {
30
+ let current = meta;
29
31
  for (const fn of fns) current = await fn(current);
30
32
  return current;
31
33
  };
32
34
  }
35
+ function buildContentPipeline(hooks) {
36
+ const fns = hooks.map((h) => h.beforeCacheContent).filter(Boolean);
37
+ if (fns.length === 0) return void 0;
38
+ return async (content, item) => {
39
+ let current = content;
40
+ for (const fn of fns) current = await fn(current, item);
41
+ return current;
42
+ };
43
+ }
33
44
  function buildRenderPipeline(hooks) {
34
45
  const fns = hooks.map((h) => h.afterRender).filter(Boolean);
35
46
  if (fns.length === 0) return void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.mjs","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["import type { BaseContentItem } from \"./types/content\";\nimport type { CMSHooks, MaybePromise } from \"./types/hooks\";\nimport type { Logger } from \"./types/logger\";\nimport type { CMSPlugin } from \"./types/plugin\";\n\n/**\n * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。\n * beforeCache / afterRender はパイプライン(前の出力が次の入力)。\n * onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。\n */\nexport function mergeHooks<T extends BaseContentItem>(\n\tplugins: CMSPlugin<T>[],\n\tdirectHooks?: CMSHooks<T>,\n\tlogger?: Logger,\n): CMSHooks<T> {\n\tconst allHooks: CMSHooks<T>[] = [\n\t\t...plugins.map((p) => p.hooks ?? {}),\n\t\t...(directHooks ? [directHooks] : []),\n\t];\n\n\tif (allHooks.length === 0) return {};\n\n\treturn {\n\t\tbeforeCache: buildPipeline(allHooks, \"beforeCache\"),\n\t\tafterRender: buildRenderPipeline(allHooks),\n\t\tonCacheHit: buildObserver(allHooks, \"onCacheHit\", logger),\n\t\tonCacheMiss: buildObserver(allHooks, \"onCacheMiss\", logger),\n\t\tonCacheRevalidated: buildObserver(allHooks, \"onCacheRevalidated\", logger),\n\t\tonListCacheHit: buildObserver(allHooks, \"onListCacheHit\", logger),\n\t\tonListCacheMiss: buildObserver(allHooks, \"onListCacheMiss\", logger),\n\t\tonListCacheRevalidated: buildObserver(\n\t\t\tallHooks,\n\t\t\t\"onListCacheRevalidated\",\n\t\t\tlogger,\n\t\t),\n\t\tonError: buildObserver(allHooks, \"onError\", logger),\n\t\tonRenderStart: buildObserver(allHooks, \"onRenderStart\", logger),\n\t\tonRenderEnd: buildObserver(allHooks, \"onRenderEnd\", logger),\n\t};\n}\n\nfunction buildPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n\tkey: \"beforeCache\",\n): CMSHooks<T>[\"beforeCache\"] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"beforeCache\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (item) => {\n\t\tlet current = item;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current) as MaybePromise<typeof item>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildRenderPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n): CMSHooks<T>[\"afterRender\"] {\n\tconst fns = hooks.map((h) => h.afterRender).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"afterRender\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (html, item) => {\n\t\tlet current = html;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current, item) as MaybePromise<string>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildObserver<T extends BaseContentItem, K extends keyof CMSHooks<T>>(\n\thooks: CMSHooks<T>[],\n\tkey: K,\n\tlogger?: Logger,\n): CMSHooks<T>[K] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean);\n\tif (fns.length === 0) return undefined;\n\treturn ((...args: unknown[]) => {\n\t\tfor (const fn of fns) {\n\t\t\ttry {\n\t\t\t\t(fn as (...a: unknown[]) => void)(...args);\n\t\t\t} catch (err) {\n\t\t\t\tlogger?.error?.(\"観測フックで例外が発生\", {\n\t\t\t\t\thook: String(key),\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}) as CMSHooks<T>[K];\n}\n\n/** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */\nexport function mergeLoggers(\n\tplugins: Array<{ logger?: Partial<Logger> }>,\n\tdirectLogger?: Logger,\n): Logger | undefined {\n\tconst loggers: Partial<Logger>[] = [\n\t\t...plugins.map((p) => p.logger ?? {}),\n\t\t...(directLogger ? [directLogger] : []),\n\t];\n\tif (loggers.length === 0) return undefined;\n\n\tconst merged: Logger = {};\n\tfor (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n\t\tconst fns = loggers.map((l) => l[level]).filter(Boolean) as NonNullable<\n\t\t\tLogger[typeof level]\n\t\t>[];\n\t\tif (fns.length > 0) {\n\t\t\tmerged[level] = (message, context) => {\n\t\t\t\tfor (const fn of fns) fn(message, context);\n\t\t\t};\n\t\t}\n\t}\n\treturn merged;\n}\n"],"mappings":";;;;;;AAUA,SAAgB,WACf,SACA,aACA,QACc;CACd,MAAM,WAA0B,CAC/B,GAAG,QAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,CAAC,EACpC,GAAI,cAAc,CAAC,YAAY,GAAG,EAAE,CACpC;AAED,KAAI,SAAS,WAAW,EAAG,QAAO,EAAE;AAEpC,QAAO;EACN,aAAa,cAAc,UAAU,cAAc;EACnD,aAAa,oBAAoB,SAAS;EAC1C,YAAY,cAAc,UAAU,cAAc,OAAO;EACzD,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D,oBAAoB,cAAc,UAAU,sBAAsB,OAAO;EACzE,gBAAgB,cAAc,UAAU,kBAAkB,OAAO;EACjE,iBAAiB,cAAc,UAAU,mBAAmB,OAAO;EACnE,wBAAwB,cACvB,UACA,0BACA,OACA;EACD,SAAS,cAAc,UAAU,WAAW,OAAO;EACnD,eAAe,cAAc,UAAU,iBAAiB,OAAO;EAC/D,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D;;AAGF,SAAS,cACR,OACA,KAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AAGpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,SAAS;EACtB,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,QAAQ;AAE7B,SAAO;;;AAIT,SAAS,oBACR,OAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,YAAY,CAAC,OAAO,QAAQ;AAG3D,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,MAAM,SAAS;EAC5B,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,SAAS,KAAK;AAEnC,SAAO;;;AAIT,SAAS,cACR,OACA,KACA,QACiB;CACjB,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AACpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,UAAS,GAAG,SAAoB;AAC/B,OAAK,MAAM,MAAM,IAChB,KAAI;AACF,MAAiC,GAAG,KAAK;WAClC,KAAK;AACb,WAAQ,QAAQ,eAAe;IAC9B,MAAM,OAAO,IAAI;IACjB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,CAAC;;;;;AAON,SAAgB,aACf,SACA,cACqB;CACrB,MAAM,UAA6B,CAClC,GAAG,QAAQ,KAAK,MAAM,EAAE,UAAU,EAAE,CAAC,EACrC,GAAI,eAAe,CAAC,aAAa,GAAG,EAAE,CACtC;AACD,KAAI,QAAQ,WAAW,EAAG,QAAO,KAAA;CAEjC,MAAM,SAAiB,EAAE;AACzB,MAAK,MAAM,SAAS;EAAC;EAAS;EAAQ;EAAQ;EAAQ,EAAW;EAChE,MAAM,MAAM,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,OAAO,QAAQ;AAGxD,MAAI,IAAI,SAAS,EAChB,QAAO,UAAU,SAAS,YAAY;AACrC,QAAK,MAAM,MAAM,IAAK,IAAG,SAAS,QAAQ;;;AAI7C,QAAO"}
1
+ {"version":3,"file":"hooks.mjs","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["import type { BaseContentItem } from \"./types/content\";\nimport type { CMSHooks, MaybePromise } from \"./types/hooks\";\nimport type { Logger } from \"./types/logger\";\nimport type { CMSPlugin } from \"./types/plugin\";\n\n/**\n * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。\n * beforeCacheMeta / beforeCacheContent / afterRender はパイプライン(前の出力が次の入力)。\n * オブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。\n */\nexport function mergeHooks<T extends BaseContentItem>(\n\tplugins: CMSPlugin<T>[],\n\tdirectHooks?: CMSHooks<T>,\n\tlogger?: Logger,\n): CMSHooks<T> {\n\tconst allHooks: CMSHooks<T>[] = [\n\t\t...plugins.map((p) => p.hooks ?? {}),\n\t\t...(directHooks ? [directHooks] : []),\n\t];\n\n\tif (allHooks.length === 0) return {};\n\n\treturn {\n\t\tbeforeCacheMeta: buildMetaPipeline(allHooks),\n\t\tbeforeCacheContent: buildContentPipeline(allHooks),\n\t\tafterRender: buildRenderPipeline(allHooks),\n\t\tonCacheHit: buildObserver(allHooks, \"onCacheHit\", logger),\n\t\tonCacheMiss: buildObserver(allHooks, \"onCacheMiss\", logger),\n\t\tonCacheRevalidated: buildObserver(allHooks, \"onCacheRevalidated\", logger),\n\t\tonContentRevalidated: buildObserver(\n\t\t\tallHooks,\n\t\t\t\"onContentRevalidated\",\n\t\t\tlogger,\n\t\t),\n\t\tonListCacheHit: buildObserver(allHooks, \"onListCacheHit\", logger),\n\t\tonListCacheMiss: buildObserver(allHooks, \"onListCacheMiss\", logger),\n\t\tonListCacheRevalidated: buildObserver(\n\t\t\tallHooks,\n\t\t\t\"onListCacheRevalidated\",\n\t\t\tlogger,\n\t\t),\n\t\tonError: buildObserver(allHooks, \"onError\", logger),\n\t\tonRenderStart: buildObserver(allHooks, \"onRenderStart\", logger),\n\t\tonRenderEnd: buildObserver(allHooks, \"onRenderEnd\", logger),\n\t};\n}\n\nfunction buildMetaPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n): CMSHooks<T>[\"beforeCacheMeta\"] {\n\tconst fns = hooks\n\t\t.map((h) => h.beforeCacheMeta)\n\t\t.filter(Boolean) as NonNullable<CMSHooks<T>[\"beforeCacheMeta\"]>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (meta) => {\n\t\tlet current = meta;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current) as MaybePromise<typeof meta>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildContentPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n): CMSHooks<T>[\"beforeCacheContent\"] {\n\tconst fns = hooks\n\t\t.map((h) => h.beforeCacheContent)\n\t\t.filter(Boolean) as NonNullable<CMSHooks<T>[\"beforeCacheContent\"]>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (content, item) => {\n\t\tlet current = content;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current, item) as MaybePromise<typeof content>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildRenderPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n): CMSHooks<T>[\"afterRender\"] {\n\tconst fns = hooks.map((h) => h.afterRender).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"afterRender\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (html, item) => {\n\t\tlet current = html;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current, item) as MaybePromise<string>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildObserver<T extends BaseContentItem, K extends keyof CMSHooks<T>>(\n\thooks: CMSHooks<T>[],\n\tkey: K,\n\tlogger?: Logger,\n): CMSHooks<T>[K] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean);\n\tif (fns.length === 0) return undefined;\n\treturn ((...args: unknown[]) => {\n\t\tfor (const fn of fns) {\n\t\t\ttry {\n\t\t\t\t(fn as (...a: unknown[]) => void)(...args);\n\t\t\t} catch (err) {\n\t\t\t\tlogger?.error?.(\"観測フックで例外が発生\", {\n\t\t\t\t\thook: String(key),\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}) as CMSHooks<T>[K];\n}\n\n/** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */\nexport function mergeLoggers(\n\tplugins: Array<{ logger?: Partial<Logger> }>,\n\tdirectLogger?: Logger,\n): Logger | undefined {\n\tconst loggers: Partial<Logger>[] = [\n\t\t...plugins.map((p) => p.logger ?? {}),\n\t\t...(directLogger ? [directLogger] : []),\n\t];\n\tif (loggers.length === 0) return undefined;\n\n\tconst merged: Logger = {};\n\tfor (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n\t\tconst fns = loggers.map((l) => l[level]).filter(Boolean) as NonNullable<\n\t\t\tLogger[typeof level]\n\t\t>[];\n\t\tif (fns.length > 0) {\n\t\t\tmerged[level] = (message, context) => {\n\t\t\t\tfor (const fn of fns) fn(message, context);\n\t\t\t};\n\t\t}\n\t}\n\treturn merged;\n}\n"],"mappings":";;;;;;AAUA,SAAgB,WACf,SACA,aACA,QACc;CACd,MAAM,WAA0B,CAC/B,GAAG,QAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,CAAC,EACpC,GAAI,cAAc,CAAC,YAAY,GAAG,EAAE,CACpC;AAED,KAAI,SAAS,WAAW,EAAG,QAAO,EAAE;AAEpC,QAAO;EACN,iBAAiB,kBAAkB,SAAS;EAC5C,oBAAoB,qBAAqB,SAAS;EAClD,aAAa,oBAAoB,SAAS;EAC1C,YAAY,cAAc,UAAU,cAAc,OAAO;EACzD,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D,oBAAoB,cAAc,UAAU,sBAAsB,OAAO;EACzE,sBAAsB,cACrB,UACA,wBACA,OACA;EACD,gBAAgB,cAAc,UAAU,kBAAkB,OAAO;EACjE,iBAAiB,cAAc,UAAU,mBAAmB,OAAO;EACnE,wBAAwB,cACvB,UACA,0BACA,OACA;EACD,SAAS,cAAc,UAAU,WAAW,OAAO;EACnD,eAAe,cAAc,UAAU,iBAAiB,OAAO;EAC/D,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D;;AAGF,SAAS,kBACR,OACiC;CACjC,MAAM,MAAM,MACV,KAAK,MAAM,EAAE,gBAAgB,CAC7B,OAAO,QAAQ;AACjB,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,SAAS;EACtB,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,QAAQ;AAE7B,SAAO;;;AAIT,SAAS,qBACR,OACoC;CACpC,MAAM,MAAM,MACV,KAAK,MAAM,EAAE,mBAAmB,CAChC,OAAO,QAAQ;AACjB,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,SAAS,SAAS;EAC/B,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,SAAS,KAAK;AAEnC,SAAO;;;AAIT,SAAS,oBACR,OAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,YAAY,CAAC,OAAO,QAAQ;AAG3D,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,MAAM,SAAS;EAC5B,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,SAAS,KAAK;AAEnC,SAAO;;;AAIT,SAAS,cACR,OACA,KACA,QACiB;CACjB,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AACpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,UAAS,GAAG,SAAoB;AAC/B,OAAK,MAAM,MAAM,IAChB,KAAI;AACF,MAAiC,GAAG,KAAK;WAClC,KAAK;AACb,WAAQ,QAAQ,eAAe;IAC9B,MAAM,OAAO,IAAI;IACjB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,CAAC;;;;;AAON,SAAgB,aACf,SACA,cACqB;CACrB,MAAM,UAA6B,CAClC,GAAG,QAAQ,KAAK,MAAM,EAAE,UAAU,EAAE,CAAC,EACrC,GAAI,eAAe,CAAC,aAAa,GAAG,EAAE,CACtC;AACD,KAAI,QAAQ,WAAW,EAAG,QAAO,KAAA;CAEjC,MAAM,SAAiB,EAAE;AACzB,MAAK,MAAM,SAAS;EAAC;EAAS;EAAQ;EAAQ;EAAQ,EAAW;EAChE,MAAM,MAAM,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,OAAO,QAAQ;AAGxD,MAAI,IAAI,SAAS,EAChB,QAAO,UAAU,SAAS,YAAY;AACrC,QAAK,MAAM,MAAM,IAAK,IAAG,SAAS,QAAQ;;;AAI7C,QAAO"}