@notion-headless-cms/core 0.3.8 → 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.
- package/dist/cache/memory.d.mts +2 -50
- package/dist/cache/memory.mjs +59 -45
- package/dist/cache/memory.mjs.map +1 -1
- package/dist/{content-DyrOwjbA.d.mts → content-BIcYVt2y.d.mts} +2 -12
- package/dist/{hooks-CPRRo9IN.d.mts → hooks-C0Pv0WYd.d.mts} +2 -2
- package/dist/hooks.d.mts +1 -1
- package/dist/index.d.mts +158 -306
- package/dist/index.mjs +261 -363
- package/dist/index.mjs.map +1 -1
- package/dist/memory-V04Q09jC.d.mts +146 -0
- package/package.json +4 -8
- package/dist/cache/noop.d.mts +0 -11
- package/dist/cache/noop.mjs +0 -50
- package/dist/cache/noop.mjs.map +0 -1
- package/dist/cache-QrXdXYMs.d.mts +0 -170
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { noopDocumentCache, noopImageCache } from "./cache/noop.mjs";
|
|
1
|
+
import { memoryCache } from "./cache/memory.mjs";
|
|
3
2
|
import { CMSError, isCMSError, isCMSErrorInNamespace } from "./errors.mjs";
|
|
4
3
|
import { mergeHooks, mergeLoggers } from "./hooks.mjs";
|
|
5
4
|
//#region src/cache.ts
|
|
@@ -18,6 +17,47 @@ function isStale(cachedAt, ttlMs) {
|
|
|
18
17
|
return Date.now() - cachedAt > ttlMs;
|
|
19
18
|
}
|
|
20
19
|
//#endregion
|
|
20
|
+
//#region src/cache/noop.ts
|
|
21
|
+
/** 何もキャッシュしないドキュメントオペレーション。常に null を返す。 */
|
|
22
|
+
const noopDoc = {
|
|
23
|
+
getList(_collection) {
|
|
24
|
+
return Promise.resolve(null);
|
|
25
|
+
},
|
|
26
|
+
setList(_collection, _data) {
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
},
|
|
29
|
+
getMeta(_collection, _slug) {
|
|
30
|
+
return Promise.resolve(null);
|
|
31
|
+
},
|
|
32
|
+
setMeta(_collection, _slug, _data) {
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
},
|
|
35
|
+
getContent(_collection, _slug) {
|
|
36
|
+
return Promise.resolve(null);
|
|
37
|
+
},
|
|
38
|
+
setContent(_collection, _slug, _data) {
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
},
|
|
41
|
+
invalidate() {
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** 何もキャッシュしない画像オペレーション。 */
|
|
46
|
+
const noopImg = {
|
|
47
|
+
get(_hash) {
|
|
48
|
+
return Promise.resolve(null);
|
|
49
|
+
},
|
|
50
|
+
set() {
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* 何もキャッシュしないアダプタ。`createCMS({ cache })` 未指定時の内部デフォルト。
|
|
56
|
+
* テストでも使える。
|
|
57
|
+
*/
|
|
58
|
+
const noopDocOps = noopDoc;
|
|
59
|
+
const noopImgOps = noopImg;
|
|
60
|
+
//#endregion
|
|
21
61
|
//#region src/image.ts
|
|
22
62
|
/** レスポンスヘッダまたはURLの拡張子からContent-Typeを推測する。 */
|
|
23
63
|
function inferContentType(url, responseContentType) {
|
|
@@ -28,23 +68,47 @@ function inferContentType(url, responseContentType) {
|
|
|
28
68
|
return "image/jpeg";
|
|
29
69
|
}
|
|
30
70
|
/**
|
|
31
|
-
*
|
|
71
|
+
* URL → SHA-256 hash のメモ化マップ。
|
|
72
|
+
* Notion の画像 URL は同じ画像でも署名が時刻ごとに変わるが、
|
|
73
|
+
* 1 リクエスト内では同一 URL が複数回現れることが多い (重複ハッシュ計算を回避)。
|
|
74
|
+
*
|
|
75
|
+
* メモリリーク防止に最大エントリ数を設けており、超過時は最古から削除する LRU。
|
|
76
|
+
*/
|
|
77
|
+
const HASH_MEMO_LIMIT = 1024;
|
|
78
|
+
const hashMemo = /* @__PURE__ */ new Map();
|
|
79
|
+
async function memoSha256(url) {
|
|
80
|
+
const cached = hashMemo.get(url);
|
|
81
|
+
if (cached !== void 0) {
|
|
82
|
+
hashMemo.delete(url);
|
|
83
|
+
hashMemo.set(url, cached);
|
|
84
|
+
return cached;
|
|
85
|
+
}
|
|
86
|
+
const hash = await sha256Hex(url);
|
|
87
|
+
hashMemo.set(url, hash);
|
|
88
|
+
if (hashMemo.size > HASH_MEMO_LIMIT) {
|
|
89
|
+
const firstKey = hashMemo.keys().next().value;
|
|
90
|
+
if (firstKey !== void 0) hashMemo.delete(firstKey);
|
|
91
|
+
}
|
|
92
|
+
return hash;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Notion画像URLをfetchして ImageCacheOps にキャッシュし、プロキシURL を返す。
|
|
32
96
|
* 既存キャッシュがあれば再fetchしない。
|
|
33
97
|
*/
|
|
34
|
-
async function fetchAndCacheImage(cache, notionUrl, imageProxyBase, logger) {
|
|
35
|
-
const hash = await
|
|
98
|
+
async function fetchAndCacheImage(cache, cacheName, notionUrl, imageProxyBase, logger) {
|
|
99
|
+
const hash = await memoSha256(notionUrl);
|
|
36
100
|
const proxyUrl = `${imageProxyBase}/${hash}`;
|
|
37
101
|
if (await cache.get(hash)) {
|
|
38
102
|
logger?.debug?.("画像キャッシュヒット", {
|
|
39
103
|
operation: "fetchAndCacheImage",
|
|
40
|
-
cacheAdapter:
|
|
104
|
+
cacheAdapter: cacheName,
|
|
41
105
|
imageHash: hash
|
|
42
106
|
});
|
|
43
107
|
return proxyUrl;
|
|
44
108
|
}
|
|
45
109
|
logger?.debug?.("画像キャッシュミス、Notion からフェッチ", {
|
|
46
110
|
operation: "fetchAndCacheImage",
|
|
47
|
-
cacheAdapter:
|
|
111
|
+
cacheAdapter: cacheName,
|
|
48
112
|
imageHash: hash
|
|
49
113
|
});
|
|
50
114
|
try {
|
|
@@ -63,7 +127,7 @@ async function fetchAndCacheImage(cache, notionUrl, imageProxyBase, logger) {
|
|
|
63
127
|
await cache.set(hash, data, contentType);
|
|
64
128
|
logger?.debug?.("画像をキャッシュに保存", {
|
|
65
129
|
operation: "fetchAndCacheImage",
|
|
66
|
-
cacheAdapter:
|
|
130
|
+
cacheAdapter: cacheName,
|
|
67
131
|
imageHash: hash
|
|
68
132
|
});
|
|
69
133
|
} catch (err) {
|
|
@@ -81,15 +145,12 @@ async function fetchAndCacheImage(cache, notionUrl, imageProxyBase, logger) {
|
|
|
81
145
|
return proxyUrl;
|
|
82
146
|
}
|
|
83
147
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* {@link ImageCacheAdapter} に保存後、フロントエンドへの配信用プロキシ URL を返す。
|
|
88
|
-
* Content-Type はレスポンスヘッダまたは URL の拡張子から推測する。
|
|
89
|
-
* タイムアウトは 10 秒固定。
|
|
148
|
+
* `ImageCacheOps` と `imageProxyBase` から `cacheImage` 関数を構築する。
|
|
149
|
+
* 返り値は Notion 画像 URL を受け取り、SHA-256 ハッシュをキャッシュキーとして
|
|
150
|
+
* {@link ImageCacheOps} に保存後、プロキシ URL を返す。
|
|
90
151
|
*/
|
|
91
|
-
function buildCacheImageFn(cache, imageProxyBase, logger) {
|
|
92
|
-
return (notionUrl) => fetchAndCacheImage(cache, notionUrl, imageProxyBase, logger);
|
|
152
|
+
function buildCacheImageFn(cache, cacheName, imageProxyBase, logger) {
|
|
153
|
+
return (notionUrl) => fetchAndCacheImage(cache, cacheName, notionUrl, imageProxyBase, logger);
|
|
93
154
|
}
|
|
94
155
|
//#endregion
|
|
95
156
|
//#region src/rendering.ts
|
|
@@ -140,7 +201,7 @@ async function buildCachedItemContent(item, ctx) {
|
|
|
140
201
|
});
|
|
141
202
|
blocks = [];
|
|
142
203
|
}
|
|
143
|
-
const cacheImage = ctx.hasImageCache ? buildCacheImageFn(ctx.imgCache, ctx.imageProxyBase, ctx.logger) : void 0;
|
|
204
|
+
const cacheImage = ctx.hasImageCache ? buildCacheImageFn(ctx.imgCache, ctx.imgCacheName, ctx.imageProxyBase, ctx.logger) : void 0;
|
|
144
205
|
let html;
|
|
145
206
|
const rendererFn = ctx.rendererFn ?? await loadDefaultRenderer();
|
|
146
207
|
try {
|
|
@@ -239,24 +300,41 @@ async function withRetry(fn, config) {
|
|
|
239
300
|
/**
|
|
240
301
|
* コレクション別キャッシュキーを生成する。
|
|
241
302
|
* item: `{collection}:{slug}` / list: `{collection}`
|
|
303
|
+
*
|
|
304
|
+
* (Cache adapter 内部のキー戦略はアダプタごとに異なるが、
|
|
305
|
+
* 表示や再計算用に core 側でも公開ヘルパーを提供する)
|
|
242
306
|
*/
|
|
243
307
|
function collectionKey(collection, slug) {
|
|
244
308
|
return slug ? `${collection}:${slug}` : collection;
|
|
245
309
|
}
|
|
246
310
|
/** CollectionClient の実装。ユーザーは `createCMS` 経由でインスタンスを受け取る。 */
|
|
247
311
|
var CollectionClientImpl = class {
|
|
312
|
+
cache;
|
|
248
313
|
constructor(ctx) {
|
|
249
314
|
this.ctx = ctx;
|
|
315
|
+
this.cache = {
|
|
316
|
+
invalidate: (slug) => this.invalidateImpl(slug),
|
|
317
|
+
warm: (opts) => this.warmImpl(opts),
|
|
318
|
+
adjacent: (slug, opts) => this.adjacentImpl(slug, opts)
|
|
319
|
+
};
|
|
250
320
|
}
|
|
251
|
-
async
|
|
252
|
-
|
|
321
|
+
async get(slug, opts = {}) {
|
|
322
|
+
if (opts.fresh) {
|
|
323
|
+
this.ctx.hooks.onCacheMiss?.(slug);
|
|
324
|
+
const item = await this.findRaw(slug);
|
|
325
|
+
if (!item) return null;
|
|
326
|
+
const meta = await this.persistMeta(slug, item);
|
|
327
|
+
await this.invalidateContent(slug);
|
|
328
|
+
return this.attachLazyContent(meta);
|
|
329
|
+
}
|
|
330
|
+
const cachedMeta = await this.ctx.docCache.getMeta(this.ctx.collection, slug);
|
|
253
331
|
if (cachedMeta) {
|
|
254
332
|
if (this.ctx.ttlMs !== void 0 && isStale(cachedMeta.cachedAt, this.ctx.ttlMs)) {
|
|
255
333
|
this.ctx.logger?.debug?.("キャッシュ期限切れ(TTL)、フェッチ", {
|
|
256
|
-
operation: "
|
|
334
|
+
operation: "get",
|
|
257
335
|
slug,
|
|
258
336
|
collection: this.ctx.collection,
|
|
259
|
-
cacheAdapter: this.ctx.
|
|
337
|
+
cacheAdapter: this.ctx.docCacheName
|
|
260
338
|
});
|
|
261
339
|
this.ctx.hooks.onCacheMiss?.(slug);
|
|
262
340
|
const item = await this.findRaw(slug);
|
|
@@ -268,154 +346,55 @@ var CollectionClientImpl = class {
|
|
|
268
346
|
const bg = this.checkAndUpdateItemBg(slug, cachedMeta);
|
|
269
347
|
if (this.ctx.waitUntil) this.ctx.waitUntil(bg);
|
|
270
348
|
this.ctx.logger?.debug?.("キャッシュヒット", {
|
|
271
|
-
operation: "
|
|
349
|
+
operation: "get",
|
|
272
350
|
slug,
|
|
273
351
|
collection: this.ctx.collection,
|
|
274
|
-
cacheAdapter: this.ctx.
|
|
352
|
+
cacheAdapter: this.ctx.docCacheName,
|
|
275
353
|
cachedAt: cachedMeta.cachedAt
|
|
276
354
|
});
|
|
277
355
|
this.ctx.hooks.onCacheHit?.(slug, cachedMeta);
|
|
278
356
|
return this.attachLazyContent(cachedMeta);
|
|
279
357
|
}
|
|
280
358
|
this.ctx.logger?.debug?.("キャッシュミス、フェッチ", {
|
|
281
|
-
operation: "
|
|
359
|
+
operation: "get",
|
|
282
360
|
slug,
|
|
283
361
|
collection: this.ctx.collection,
|
|
284
|
-
cacheAdapter: this.ctx.
|
|
362
|
+
cacheAdapter: this.ctx.docCacheName
|
|
285
363
|
});
|
|
286
364
|
this.ctx.hooks.onCacheMiss?.(slug);
|
|
287
365
|
const item = await this.findRaw(slug);
|
|
288
|
-
if (!item)
|
|
289
|
-
this.ctx.logger?.debug?.("アイテムが見つかりません", {
|
|
290
|
-
operation: "getItem",
|
|
291
|
-
slug,
|
|
292
|
-
collection: this.ctx.collection
|
|
293
|
-
});
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
366
|
+
if (!item) return null;
|
|
296
367
|
const meta = await this.persistMeta(slug, item, { background: true });
|
|
297
368
|
return this.attachLazyContent(meta);
|
|
298
369
|
}
|
|
299
|
-
async
|
|
300
|
-
|
|
301
|
-
if (cachedMeta) {
|
|
302
|
-
if (this.ctx.ttlMs !== void 0 && isStale(cachedMeta.cachedAt, this.ctx.ttlMs)) {
|
|
303
|
-
const item = await this.findRaw(slug);
|
|
304
|
-
if (!item) return null;
|
|
305
|
-
const meta = await this.persistMeta(slug, item);
|
|
306
|
-
await this.invalidateContent(slug);
|
|
307
|
-
return meta.item;
|
|
308
|
-
}
|
|
309
|
-
const bg = this.checkAndUpdateItemBg(slug, cachedMeta);
|
|
310
|
-
if (this.ctx.waitUntil) this.ctx.waitUntil(bg);
|
|
311
|
-
this.ctx.hooks.onCacheHit?.(slug, cachedMeta);
|
|
312
|
-
return cachedMeta.item;
|
|
313
|
-
}
|
|
314
|
-
this.ctx.hooks.onCacheMiss?.(slug);
|
|
315
|
-
const item = await this.findRaw(slug);
|
|
316
|
-
if (!item) return null;
|
|
317
|
-
return (await this.persistMeta(slug, item, { background: true })).item;
|
|
318
|
-
}
|
|
319
|
-
async getItemContent(slug) {
|
|
320
|
-
const meta = await this.ctx.docCache.getItemMeta(slug);
|
|
321
|
-
const item = meta?.item ?? await this.findRaw(slug);
|
|
322
|
-
if (!item) return null;
|
|
323
|
-
if (!meta) await this.persistMeta(slug, item);
|
|
324
|
-
return toContentPayload(await this.loadOrBuildContent(slug, item));
|
|
325
|
-
}
|
|
326
|
-
async getList(opts) {
|
|
327
|
-
const items = applyGetListOptions(await this.fetchList(), opts);
|
|
328
|
-
return {
|
|
329
|
-
items,
|
|
330
|
-
version: this.ctx.source.getListVersion(items)
|
|
331
|
-
};
|
|
370
|
+
async list(opts) {
|
|
371
|
+
return applyListOptions(await this.fetchList(), opts);
|
|
332
372
|
}
|
|
333
|
-
async
|
|
373
|
+
async params() {
|
|
334
374
|
return (await this.fetchList()).map((item) => ({ slug: item.slug }));
|
|
335
375
|
}
|
|
336
|
-
async
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
};
|
|
346
|
-
return {
|
|
347
|
-
prev: index > 0 ? items[index - 1] ?? null : null,
|
|
348
|
-
next: index < items.length - 1 ? items[index + 1] ?? null : null
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
async revalidate(slug) {
|
|
352
|
-
this.ctx.logger?.debug?.("アイテムキャッシュを無効化", {
|
|
353
|
-
operation: "revalidate",
|
|
354
|
-
collection: this.ctx.collection,
|
|
355
|
-
cacheAdapter: this.ctx.docCache.name,
|
|
356
|
-
slug
|
|
357
|
-
});
|
|
358
|
-
if (!this.ctx.docCache.invalidate) return;
|
|
359
|
-
await this.ctx.docCache.invalidate({
|
|
360
|
-
collection: this.ctx.collection,
|
|
361
|
-
slug
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
async revalidateAll() {
|
|
365
|
-
this.ctx.logger?.debug?.("コレクション全体のキャッシュを無効化", {
|
|
366
|
-
operation: "revalidateAll",
|
|
367
|
-
collection: this.ctx.collection,
|
|
368
|
-
cacheAdapter: this.ctx.docCache.name
|
|
369
|
-
});
|
|
370
|
-
if (!this.ctx.docCache.invalidate) return;
|
|
371
|
-
await this.ctx.docCache.invalidate({ collection: this.ctx.collection });
|
|
372
|
-
}
|
|
373
|
-
async checkForUpdate({ slug, since }) {
|
|
374
|
-
const fresh = await this.findRaw(slug);
|
|
375
|
-
if (!fresh) return { changed: false };
|
|
376
|
-
const lm = this.ctx.source.getLastModified(fresh);
|
|
377
|
-
if (lm === since) {
|
|
378
|
-
this.ctx.logger?.debug?.("checkForUpdate: 差分なし", {
|
|
379
|
-
operation: "checkForUpdate",
|
|
380
|
-
slug,
|
|
376
|
+
async invalidateImpl(slug) {
|
|
377
|
+
if (slug !== void 0) {
|
|
378
|
+
this.ctx.logger?.debug?.("アイテムキャッシュを無効化", {
|
|
379
|
+
operation: "cache.invalidate",
|
|
380
|
+
collection: this.ctx.collection,
|
|
381
|
+
cacheAdapter: this.ctx.docCacheName,
|
|
382
|
+
slug
|
|
383
|
+
});
|
|
384
|
+
await this.ctx.docCache.invalidate({
|
|
381
385
|
collection: this.ctx.collection,
|
|
382
|
-
|
|
386
|
+
slug
|
|
383
387
|
});
|
|
384
|
-
return
|
|
388
|
+
return;
|
|
385
389
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
this.ctx.hooks.onCacheRevalidated?.(slug, meta);
|
|
389
|
-
this.ctx.logger?.debug?.("checkForUpdate: 差分を検出", {
|
|
390
|
-
operation: "checkForUpdate",
|
|
391
|
-
slug,
|
|
390
|
+
this.ctx.logger?.debug?.("コレクション全体のキャッシュを無効化", {
|
|
391
|
+
operation: "cache.invalidate",
|
|
392
392
|
collection: this.ctx.collection,
|
|
393
|
-
|
|
394
|
-
notionUpdatedAt: lm
|
|
395
|
-
});
|
|
396
|
-
const bg = this.rebuildContentBg(slug, fresh);
|
|
397
|
-
if (this.ctx.waitUntil) this.ctx.waitUntil(bg);
|
|
398
|
-
return {
|
|
399
|
-
changed: true,
|
|
400
|
-
meta: fresh
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
async checkListForUpdate({ since, filter }) {
|
|
404
|
-
const allItems = await this.fetchListRaw();
|
|
405
|
-
const items = applyGetListOptions(allItems, filter);
|
|
406
|
-
const version = this.ctx.source.getListVersion(items);
|
|
407
|
-
if (version === since) return { changed: false };
|
|
408
|
-
await this.ctx.docCache.setList({
|
|
409
|
-
items: allItems,
|
|
410
|
-
cachedAt: Date.now()
|
|
393
|
+
cacheAdapter: this.ctx.docCacheName
|
|
411
394
|
});
|
|
412
|
-
|
|
413
|
-
changed: true,
|
|
414
|
-
items,
|
|
415
|
-
version
|
|
416
|
-
};
|
|
395
|
+
await this.ctx.docCache.invalidate({ collection: this.ctx.collection });
|
|
417
396
|
}
|
|
418
|
-
async
|
|
397
|
+
async warmImpl(opts) {
|
|
419
398
|
const items = await this.fetchListRaw();
|
|
420
399
|
const concurrency = opts?.concurrency ?? this.ctx.maxConcurrent;
|
|
421
400
|
let ok = 0;
|
|
@@ -426,11 +405,11 @@ var CollectionClientImpl = class {
|
|
|
426
405
|
try {
|
|
427
406
|
await this.persistMeta(item.slug, item);
|
|
428
407
|
const content = await buildCachedItemContent(item, this.ctx.render);
|
|
429
|
-
await this.ctx.docCache.
|
|
408
|
+
await this.ctx.docCache.setContent(this.ctx.collection, item.slug, content);
|
|
430
409
|
ok++;
|
|
431
410
|
} catch (err) {
|
|
432
411
|
failed++;
|
|
433
|
-
this.ctx.logger?.warn?.("
|
|
412
|
+
this.ctx.logger?.warn?.("warm: アイテムの事前レンダリングに失敗", {
|
|
434
413
|
slug: item.slug,
|
|
435
414
|
pageId: item.id,
|
|
436
415
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -439,7 +418,7 @@ var CollectionClientImpl = class {
|
|
|
439
418
|
}));
|
|
440
419
|
opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);
|
|
441
420
|
}
|
|
442
|
-
await this.ctx.docCache.setList({
|
|
421
|
+
await this.ctx.docCache.setList(this.ctx.collection, {
|
|
443
422
|
items,
|
|
444
423
|
cachedAt: Date.now()
|
|
445
424
|
});
|
|
@@ -448,16 +427,27 @@ var CollectionClientImpl = class {
|
|
|
448
427
|
failed
|
|
449
428
|
};
|
|
450
429
|
}
|
|
430
|
+
async adjacentImpl(slug, opts) {
|
|
431
|
+
const items = applyListOptions(await this.fetchList(), { sort: opts?.sort });
|
|
432
|
+
const index = items.findIndex((it) => it.slug === slug);
|
|
433
|
+
if (index === -1) return {
|
|
434
|
+
prev: null,
|
|
435
|
+
next: null
|
|
436
|
+
};
|
|
437
|
+
return {
|
|
438
|
+
prev: index > 0 ? items[index - 1] ?? null : null,
|
|
439
|
+
next: index < items.length - 1 ? items[index + 1] ?? null : null
|
|
440
|
+
};
|
|
441
|
+
}
|
|
451
442
|
async persistMeta(slug, item, opts = {}) {
|
|
452
443
|
let meta = buildCachedItemMeta(item, this.ctx.source);
|
|
453
444
|
if (this.ctx.hooks.beforeCacheMeta) meta = await this.ctx.hooks.beforeCacheMeta(meta);
|
|
454
|
-
const save = this.ctx.docCache.
|
|
445
|
+
const save = this.ctx.docCache.setMeta(this.ctx.collection, slug, meta);
|
|
455
446
|
if (opts.background && this.ctx.waitUntil) this.ctx.waitUntil(save);
|
|
456
447
|
else await save;
|
|
457
448
|
return meta;
|
|
458
449
|
}
|
|
459
450
|
async invalidateContent(slug) {
|
|
460
|
-
if (!this.ctx.docCache.invalidate) return;
|
|
461
451
|
await this.ctx.docCache.invalidate({
|
|
462
452
|
collection: this.ctx.collection,
|
|
463
453
|
slug,
|
|
@@ -470,21 +460,18 @@ var CollectionClientImpl = class {
|
|
|
470
460
|
*/
|
|
471
461
|
async loadOrBuildContent(slug, item) {
|
|
472
462
|
const expected = this.ctx.source.getLastModified(item);
|
|
473
|
-
const cached = await this.ctx.docCache.
|
|
463
|
+
const cached = await this.ctx.docCache.getContent(this.ctx.collection, slug);
|
|
474
464
|
if (cached && cached.notionUpdatedAt === expected) return cached;
|
|
475
465
|
const fresh = await buildCachedItemContent(item, this.ctx.render);
|
|
476
|
-
await this.ctx.docCache.
|
|
466
|
+
await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);
|
|
477
467
|
this.ctx.hooks.onContentRevalidated?.(slug, fresh);
|
|
478
468
|
return fresh;
|
|
479
469
|
}
|
|
480
|
-
/**
|
|
481
|
-
* メタは既知(差分検出済み or 直前にフェッチ済み)の状態で本文だけ
|
|
482
|
-
* バックグラウンド再生成する。エラーは握りつぶしてログのみ。
|
|
483
|
-
*/
|
|
470
|
+
/** メタ既知の状態で本文だけバックグラウンド再生成する。エラーは握りつぶす。 */
|
|
484
471
|
async rebuildContentBg(slug, item) {
|
|
485
472
|
try {
|
|
486
473
|
const fresh = await buildCachedItemContent(item, this.ctx.render);
|
|
487
|
-
await this.ctx.docCache.
|
|
474
|
+
await this.ctx.docCache.setContent(this.ctx.collection, slug, fresh);
|
|
488
475
|
this.ctx.hooks.onContentRevalidated?.(slug, fresh);
|
|
489
476
|
} catch (err) {
|
|
490
477
|
this.ctx.logger?.warn?.("本文のバックグラウンド再生成に失敗", {
|
|
@@ -502,30 +489,24 @@ var CollectionClientImpl = class {
|
|
|
502
489
|
if (!payloadPromise) payloadPromise = this.loadOrBuildContent(slug, item);
|
|
503
490
|
return payloadPromise;
|
|
504
491
|
};
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
return (await loadPayload()).html;
|
|
511
|
-
},
|
|
512
|
-
async markdown() {
|
|
513
|
-
return (await loadPayload()).markdown;
|
|
514
|
-
}
|
|
515
|
-
} });
|
|
492
|
+
const render = async (opts) => {
|
|
493
|
+
const payload = await loadPayload();
|
|
494
|
+
return opts?.format === "markdown" ? payload.markdown : payload.html;
|
|
495
|
+
};
|
|
496
|
+
return Object.assign(Object.create(null), item, { render });
|
|
516
497
|
}
|
|
517
498
|
async fetchList() {
|
|
518
|
-
const cached = await this.ctx.docCache.getList();
|
|
499
|
+
const cached = await this.ctx.docCache.getList(this.ctx.collection);
|
|
519
500
|
if (cached) {
|
|
520
501
|
if (this.ctx.ttlMs !== void 0 && isStale(cached.cachedAt, this.ctx.ttlMs)) {
|
|
521
502
|
this.ctx.logger?.debug?.("リストキャッシュ期限切れ(TTL)、フェッチ", {
|
|
522
|
-
operation: "
|
|
503
|
+
operation: "list",
|
|
523
504
|
collection: this.ctx.collection,
|
|
524
|
-
cacheAdapter: this.ctx.
|
|
505
|
+
cacheAdapter: this.ctx.docCacheName
|
|
525
506
|
});
|
|
526
507
|
this.ctx.hooks.onListCacheMiss?.();
|
|
527
508
|
const items = await this.fetchListRaw();
|
|
528
|
-
await this.ctx.docCache.setList({
|
|
509
|
+
await this.ctx.docCache.setList(this.ctx.collection, {
|
|
529
510
|
items,
|
|
530
511
|
cachedAt: Date.now()
|
|
531
512
|
});
|
|
@@ -534,22 +515,22 @@ var CollectionClientImpl = class {
|
|
|
534
515
|
const bg = this.checkAndUpdateListBg(cached);
|
|
535
516
|
if (this.ctx.waitUntil) this.ctx.waitUntil(bg);
|
|
536
517
|
this.ctx.logger?.debug?.("リストキャッシュヒット", {
|
|
537
|
-
operation: "
|
|
518
|
+
operation: "list",
|
|
538
519
|
collection: this.ctx.collection,
|
|
539
|
-
cacheAdapter: this.ctx.
|
|
520
|
+
cacheAdapter: this.ctx.docCacheName
|
|
540
521
|
});
|
|
541
522
|
this.ctx.hooks.onListCacheHit?.(cached);
|
|
542
523
|
return cached.items;
|
|
543
524
|
}
|
|
544
525
|
this.ctx.logger?.debug?.("リストキャッシュミス、フェッチ", {
|
|
545
|
-
operation: "
|
|
526
|
+
operation: "list",
|
|
546
527
|
collection: this.ctx.collection,
|
|
547
|
-
cacheAdapter: this.ctx.
|
|
528
|
+
cacheAdapter: this.ctx.docCacheName
|
|
548
529
|
});
|
|
549
530
|
this.ctx.hooks.onListCacheMiss?.();
|
|
550
531
|
const items = await this.fetchListRaw();
|
|
551
532
|
const cachedAt = Date.now();
|
|
552
|
-
const save = this.ctx.docCache.setList({
|
|
533
|
+
const save = this.ctx.docCache.setList(this.ctx.collection, {
|
|
553
534
|
items,
|
|
554
535
|
cachedAt
|
|
555
536
|
});
|
|
@@ -565,7 +546,7 @@ var CollectionClientImpl = class {
|
|
|
565
546
|
const meta = await this.persistMeta(slug, item);
|
|
566
547
|
await this.invalidateContent(slug);
|
|
567
548
|
this.ctx.logger?.debug?.("SWR: 差分を検出、メタを差し替え", {
|
|
568
|
-
operation: "
|
|
549
|
+
operation: "get:bg",
|
|
569
550
|
slug,
|
|
570
551
|
collection: this.ctx.collection,
|
|
571
552
|
notionUpdatedAt: cached.notionUpdatedAt
|
|
@@ -573,12 +554,12 @@ var CollectionClientImpl = class {
|
|
|
573
554
|
this.ctx.hooks.onCacheRevalidated?.(slug, meta);
|
|
574
555
|
await this.rebuildContentBg(slug, item);
|
|
575
556
|
} else if (this.ctx.ttlMs !== void 0) {
|
|
576
|
-
await this.ctx.docCache.
|
|
557
|
+
await this.ctx.docCache.setMeta(this.ctx.collection, slug, {
|
|
577
558
|
...cached,
|
|
578
559
|
cachedAt: Date.now()
|
|
579
560
|
});
|
|
580
561
|
this.ctx.logger?.debug?.("SWR: 差分なし、TTL をリセット", {
|
|
581
|
-
operation: "
|
|
562
|
+
operation: "get:bg",
|
|
582
563
|
slug,
|
|
583
564
|
collection: this.ctx.collection
|
|
584
565
|
});
|
|
@@ -599,19 +580,19 @@ var CollectionClientImpl = class {
|
|
|
599
580
|
items,
|
|
600
581
|
cachedAt: Date.now()
|
|
601
582
|
};
|
|
602
|
-
await this.ctx.docCache.setList(listEntry);
|
|
583
|
+
await this.ctx.docCache.setList(this.ctx.collection, listEntry);
|
|
603
584
|
this.ctx.logger?.debug?.("SWR: リスト差分を検出、キャッシュを差し替え", {
|
|
604
|
-
operation: "
|
|
585
|
+
operation: "list:bg",
|
|
605
586
|
collection: this.ctx.collection
|
|
606
587
|
});
|
|
607
588
|
this.ctx.hooks.onListCacheRevalidated?.(listEntry);
|
|
608
589
|
} else if (this.ctx.ttlMs !== void 0) {
|
|
609
|
-
await this.ctx.docCache.setList({
|
|
590
|
+
await this.ctx.docCache.setList(this.ctx.collection, {
|
|
610
591
|
...cached,
|
|
611
592
|
cachedAt: Date.now()
|
|
612
593
|
});
|
|
613
594
|
this.ctx.logger?.debug?.("SWR: リスト差分なし、TTL をリセット", {
|
|
614
|
-
operation: "
|
|
595
|
+
operation: "list:bg",
|
|
615
596
|
collection: this.ctx.collection
|
|
616
597
|
});
|
|
617
598
|
}
|
|
@@ -626,7 +607,7 @@ var CollectionClientImpl = class {
|
|
|
626
607
|
return withRetry(() => this.ctx.source.list({ publishedStatuses: this.ctx.publishedStatuses.length > 0 ? this.ctx.publishedStatuses : void 0 }), {
|
|
627
608
|
...this.ctx.retryConfig,
|
|
628
609
|
onRetry: (attempt, status) => {
|
|
629
|
-
this.ctx.logger?.warn?.("
|
|
610
|
+
this.ctx.logger?.warn?.("list() リトライ中", {
|
|
630
611
|
attempt,
|
|
631
612
|
status
|
|
632
613
|
});
|
|
@@ -637,15 +618,14 @@ var CollectionClientImpl = class {
|
|
|
637
618
|
const retryOpts = {
|
|
638
619
|
...this.ctx.retryConfig,
|
|
639
620
|
onRetry: (attempt, status) => {
|
|
640
|
-
this.ctx.logger?.warn?.("
|
|
621
|
+
this.ctx.logger?.warn?.("get() リトライ中", {
|
|
641
622
|
attempt,
|
|
642
623
|
status,
|
|
643
624
|
slug
|
|
644
625
|
});
|
|
645
626
|
}
|
|
646
627
|
};
|
|
647
|
-
const
|
|
648
|
-
const notionPropName = slugField ? this.ctx.source.properties?.[slugField]?.notion : void 0;
|
|
628
|
+
const notionPropName = this.ctx.source.properties?.[this.ctx.slugField]?.notion;
|
|
649
629
|
let item;
|
|
650
630
|
const findByProp = this.ctx.source.findByProp?.bind(this.ctx.source);
|
|
651
631
|
if (notionPropName && findByProp) item = await withRetry(() => findByProp(notionPropName, slug), retryOpts);
|
|
@@ -655,19 +635,11 @@ var CollectionClientImpl = class {
|
|
|
655
635
|
return item;
|
|
656
636
|
}
|
|
657
637
|
};
|
|
658
|
-
function
|
|
659
|
-
return {
|
|
660
|
-
html: c.html,
|
|
661
|
-
markdown: c.markdown,
|
|
662
|
-
blocks: c.blocks,
|
|
663
|
-
notionUpdatedAt: c.notionUpdatedAt
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
function applyGetListOptions(items, opts) {
|
|
638
|
+
function applyListOptions(items, opts) {
|
|
667
639
|
if (!opts) return items;
|
|
668
640
|
let result = items;
|
|
669
|
-
if (opts.
|
|
670
|
-
const allow = new Set(opts.
|
|
641
|
+
if (opts.status) {
|
|
642
|
+
const allow = new Set(Array.isArray(opts.status) ? opts.status : [opts.status]);
|
|
671
643
|
result = result.filter((it) => it.status !== void 0 && allow.has(it.status));
|
|
672
644
|
}
|
|
673
645
|
if (opts.tag) {
|
|
@@ -689,7 +661,7 @@ function applyGetListOptions(items, opts) {
|
|
|
689
661
|
}
|
|
690
662
|
function makeComparator(sort) {
|
|
691
663
|
const by = sort.by;
|
|
692
|
-
const dir = sort.
|
|
664
|
+
const dir = sort.dir === "asc" ? 1 : -1;
|
|
693
665
|
return (a, b) => {
|
|
694
666
|
const av = a[by];
|
|
695
667
|
const bv = b[by];
|
|
@@ -757,97 +729,41 @@ function trimTrailingSlash(s) {
|
|
|
757
729
|
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
758
730
|
}
|
|
759
731
|
//#endregion
|
|
760
|
-
//#region src/preset-node.ts
|
|
761
|
-
/**
|
|
762
|
-
* Node.js ランタイム向けの `createCMS` オプションプリセット。
|
|
763
|
-
* メモリキャッシュをデフォルト有効にした `{ cache, renderer }` を返す。
|
|
764
|
-
*
|
|
765
|
-
* @example
|
|
766
|
-
* import { createCMS, nodePreset } from "@notion-headless-cms/core";
|
|
767
|
-
* import { cmsDataSources } from "./generated/cms-schema";
|
|
768
|
-
*
|
|
769
|
-
* const cms = createCMS({
|
|
770
|
-
* ...nodePreset({ ttlMs: 5 * 60_000 }),
|
|
771
|
-
* dataSources: cmsDataSources,
|
|
772
|
-
* });
|
|
773
|
-
*/
|
|
774
|
-
function nodePreset(opts = {}) {
|
|
775
|
-
if (opts.cache === "disabled") return {
|
|
776
|
-
cache: void 0,
|
|
777
|
-
renderer: opts.renderer
|
|
778
|
-
};
|
|
779
|
-
return {
|
|
780
|
-
cache: opts.cache ?? {
|
|
781
|
-
document: memoryDocumentCache(),
|
|
782
|
-
image: memoryImageCache(),
|
|
783
|
-
ttlMs: opts.ttlMs
|
|
784
|
-
},
|
|
785
|
-
renderer: opts.renderer
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
//#endregion
|
|
789
732
|
//#region src/cms.ts
|
|
790
733
|
const DEFAULT_IMAGE_PROXY_BASE = "/api/images";
|
|
791
|
-
function resolveDocumentCache(cache) {
|
|
792
|
-
if (!cache || cache === "disabled" || !cache.document) return noopDocumentCache();
|
|
793
|
-
return cache.document;
|
|
794
|
-
}
|
|
795
|
-
function resolveImageCache(cache) {
|
|
796
|
-
if (!cache || cache === "disabled" || !cache.image) return noopImageCache();
|
|
797
|
-
return cache.image;
|
|
798
|
-
}
|
|
799
|
-
function resolveTtl(cache) {
|
|
800
|
-
if (!cache || cache === "disabled") return void 0;
|
|
801
|
-
return cache.ttlMs;
|
|
802
|
-
}
|
|
803
|
-
function hasImageCacheConfigured(cache) {
|
|
804
|
-
if (!cache || cache === "disabled") return false;
|
|
805
|
-
return !!cache.image;
|
|
806
|
-
}
|
|
807
734
|
/**
|
|
808
|
-
* `
|
|
809
|
-
*
|
|
735
|
+
* `cache` オプションから document / image オペレーションを解決する。
|
|
736
|
+
*
|
|
737
|
+
* - 配列で渡された場合は各 adapter の `handles` を見て先勝ち (最初に見つかったもの) で振り分ける
|
|
738
|
+
* - 単体で渡された場合は `handles` の領域だけ反映、片側は noop
|
|
739
|
+
* - 未指定なら両方 noop
|
|
810
740
|
*/
|
|
811
|
-
function
|
|
812
|
-
const
|
|
813
|
-
let
|
|
814
|
-
let
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return {
|
|
825
|
-
name: `${base.name}@${collection}`,
|
|
826
|
-
getList: async () => {
|
|
827
|
-
if (!listInitialized) {
|
|
828
|
-
listInitialized = true;
|
|
829
|
-
listSlot = await base.getList();
|
|
830
|
-
}
|
|
831
|
-
return listSlot;
|
|
832
|
-
},
|
|
833
|
-
setList: (data) => {
|
|
834
|
-
listSlot = data;
|
|
835
|
-
listInitialized = true;
|
|
836
|
-
return Promise.resolve();
|
|
837
|
-
},
|
|
838
|
-
getItemMeta: (slug) => base.getItemMeta(itemKey(slug)),
|
|
839
|
-
setItemMeta: (slug, data) => base.setItemMeta(itemKey(slug), data),
|
|
840
|
-
getItemContent: (slug) => base.getItemContent(itemKey(slug)),
|
|
841
|
-
setItemContent: (slug, data) => base.setItemContent(itemKey(slug), data),
|
|
842
|
-
async invalidate(scope) {
|
|
843
|
-
const kind = scope === "all" ? "all" : scope.kind ?? "all";
|
|
844
|
-
if (kind === "all" || kind === "meta") {
|
|
845
|
-
listSlot = null;
|
|
846
|
-
listInitialized = true;
|
|
847
|
-
}
|
|
848
|
-
if (!base.invalidate) return;
|
|
849
|
-
return base.invalidate(mapInvalidateScope(scope));
|
|
741
|
+
function resolveCache(cache) {
|
|
742
|
+
const adapters = cache === void 0 ? [] : Array.isArray(cache) ? cache : [cache];
|
|
743
|
+
let doc = noopDocOps;
|
|
744
|
+
let docName = "noop-document";
|
|
745
|
+
let img = noopImgOps;
|
|
746
|
+
let imgName = "noop-image";
|
|
747
|
+
let docFound = false;
|
|
748
|
+
let imgFound = false;
|
|
749
|
+
for (const adapter of adapters) {
|
|
750
|
+
if (!docFound && adapter.handles.includes("document") && adapter.doc) {
|
|
751
|
+
doc = adapter.doc;
|
|
752
|
+
docName = adapter.name;
|
|
753
|
+
docFound = true;
|
|
850
754
|
}
|
|
755
|
+
if (!imgFound && adapter.handles.includes("image") && adapter.img) {
|
|
756
|
+
img = adapter.img;
|
|
757
|
+
imgName = adapter.name;
|
|
758
|
+
imgFound = true;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
doc,
|
|
763
|
+
docName,
|
|
764
|
+
img,
|
|
765
|
+
imgName,
|
|
766
|
+
hasImg: imgFound
|
|
851
767
|
};
|
|
852
768
|
}
|
|
853
769
|
const LOG_LEVEL_ORDER = {
|
|
@@ -870,68 +786,53 @@ function applyLogLevel(logger, minLevel) {
|
|
|
870
786
|
return filtered;
|
|
871
787
|
}
|
|
872
788
|
/**
|
|
873
|
-
*
|
|
874
|
-
* 明示的な `cache` / `renderer` がある場合はそちらが優先される。
|
|
875
|
-
* `preset` 未指定時は opts をそのまま返す。
|
|
876
|
-
*/
|
|
877
|
-
function resolvePreset(opts) {
|
|
878
|
-
if (opts.preset === "disabled") return {
|
|
879
|
-
...opts,
|
|
880
|
-
cache: void 0
|
|
881
|
-
};
|
|
882
|
-
if (opts.preset === "node") {
|
|
883
|
-
const presetResult = nodePreset({ ttlMs: opts.ttlMs });
|
|
884
|
-
return {
|
|
885
|
-
...opts,
|
|
886
|
-
cache: opts.cache ?? presetResult.cache,
|
|
887
|
-
renderer: opts.renderer ?? presetResult.renderer
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
return opts;
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* 複数の DataSource を束ねた CMS クライアントを生成する。
|
|
789
|
+
* 複数の `CollectionDef` を束ねた CMS クライアントを生成する。
|
|
894
790
|
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
* const cms = createCMS({ dataSources: cmsDataSources, preset: "node", ttlMs: 5 * 60_000 });
|
|
791
|
+
* 通常はユーザーが直接呼ぶことはなく、CLI 生成の `nhc.ts` の `createCMS`
|
|
792
|
+
* (低レベルのこの関数をラップしたもの) を経由する。
|
|
898
793
|
*
|
|
899
794
|
* @example
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
795
|
+
* createCMS({
|
|
796
|
+
* collections: {
|
|
797
|
+
* posts: {
|
|
798
|
+
* source: createNotionCollection({ token, dataSourceId, properties }),
|
|
799
|
+
* slugField: "slug",
|
|
800
|
+
* statusField: "status",
|
|
801
|
+
* publishedStatuses: ["公開済み"],
|
|
802
|
+
* }
|
|
803
|
+
* },
|
|
804
|
+
* cache: memoryCache({ ttlMs: 5 * 60_000 }),
|
|
908
805
|
* });
|
|
909
806
|
*/
|
|
910
807
|
function createCMS(opts) {
|
|
911
|
-
if (!opts.
|
|
808
|
+
if (!opts.collections || Object.keys(opts.collections).length === 0) throw new CMSError({
|
|
912
809
|
code: "core/config_invalid",
|
|
913
|
-
message: "createCMS:
|
|
810
|
+
message: "createCMS: collections に少なくとも 1 つのコレクションを指定してください。",
|
|
914
811
|
context: { operation: "createCMS" }
|
|
915
812
|
});
|
|
916
|
-
for (const [name,
|
|
917
|
-
|
|
918
|
-
if (c && !c.slug) throw new CMSError({
|
|
813
|
+
for (const [name, def] of Object.entries(opts.collections)) {
|
|
814
|
+
if (!def.source) throw new CMSError({
|
|
919
815
|
code: "core/config_invalid",
|
|
920
|
-
message: `createCMS: コレクション "${name}" の
|
|
816
|
+
message: `createCMS: コレクション "${name}" の source は必須です。`,
|
|
817
|
+
context: {
|
|
818
|
+
operation: "createCMS",
|
|
819
|
+
collection: name
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
if (!def.slugField) throw new CMSError({
|
|
823
|
+
code: "core/config_invalid",
|
|
824
|
+
message: `createCMS: コレクション "${name}" の slugField は必須です。`,
|
|
921
825
|
context: {
|
|
922
826
|
operation: "createCMS",
|
|
923
827
|
collection: name
|
|
924
828
|
}
|
|
925
829
|
});
|
|
926
830
|
}
|
|
927
|
-
const
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
const hasImageCache = hasImageCacheConfigured(resolved.cache);
|
|
931
|
-
const ttlMs = resolveTtl(resolved.cache);
|
|
932
|
-
const imageProxyBase = opts.content?.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;
|
|
831
|
+
const cacheRes = resolveCache(opts.cache);
|
|
832
|
+
const ttlMs = opts.ttlMs;
|
|
833
|
+
const imageProxyBase = opts.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;
|
|
933
834
|
const contentConfig = opts.content;
|
|
934
|
-
const rendererFn =
|
|
835
|
+
const rendererFn = opts.renderer;
|
|
935
836
|
const waitUntil = opts.waitUntil;
|
|
936
837
|
const baseLogger = mergeLoggers(opts.plugins ?? [], opts.logger);
|
|
937
838
|
const logger = opts.logLevel ? applyLogLevel(baseLogger, opts.logLevel) : baseLogger;
|
|
@@ -941,62 +842,59 @@ function createCMS(opts) {
|
|
|
941
842
|
...DEFAULT_RETRY_CONFIG,
|
|
942
843
|
...opts.rateLimiter ?? {}
|
|
943
844
|
};
|
|
944
|
-
const collectionNames = Object.keys(opts.
|
|
845
|
+
const collectionNames = Object.keys(opts.collections);
|
|
945
846
|
const collections = {};
|
|
946
|
-
const scopedCaches = [];
|
|
947
847
|
for (const name of collectionNames) {
|
|
948
|
-
const
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
const col = opts.collections?.[name];
|
|
952
|
-
const colHooks = col?.hooks;
|
|
848
|
+
const def = opts.collections[name];
|
|
849
|
+
const source = def.source;
|
|
850
|
+
const colHooks = def.hooks;
|
|
953
851
|
const collectionHooks = colHooks ? mergeHooks([{
|
|
954
852
|
name: `${name}:global`,
|
|
955
853
|
hooks
|
|
956
854
|
}], colHooks, logger) : hooks;
|
|
855
|
+
const renderCtx = {
|
|
856
|
+
source,
|
|
857
|
+
rendererFn,
|
|
858
|
+
imgCache: cacheRes.img,
|
|
859
|
+
imgCacheName: cacheRes.imgName,
|
|
860
|
+
hasImageCache: cacheRes.hasImg,
|
|
861
|
+
imageProxyBase,
|
|
862
|
+
contentConfig,
|
|
863
|
+
hooks: collectionHooks,
|
|
864
|
+
logger
|
|
865
|
+
};
|
|
957
866
|
collections[name] = new CollectionClientImpl({
|
|
958
867
|
collection: name,
|
|
959
868
|
source,
|
|
960
|
-
docCache:
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
rendererFn,
|
|
964
|
-
imgCache,
|
|
965
|
-
hasImageCache,
|
|
966
|
-
imageProxyBase,
|
|
967
|
-
contentConfig,
|
|
968
|
-
hooks: collectionHooks,
|
|
969
|
-
logger
|
|
970
|
-
},
|
|
869
|
+
docCache: cacheRes.doc,
|
|
870
|
+
docCacheName: cacheRes.docName,
|
|
871
|
+
render: renderCtx,
|
|
971
872
|
hooks: collectionHooks,
|
|
972
873
|
logger,
|
|
973
874
|
ttlMs,
|
|
974
|
-
publishedStatuses:
|
|
975
|
-
accessibleStatuses:
|
|
875
|
+
publishedStatuses: def.publishedStatuses ? [...def.publishedStatuses] : [],
|
|
876
|
+
accessibleStatuses: def.accessibleStatuses ? [...def.accessibleStatuses] : [],
|
|
976
877
|
retryConfig,
|
|
977
878
|
maxConcurrent,
|
|
978
879
|
waitUntil,
|
|
979
|
-
slugField:
|
|
880
|
+
slugField: def.slugField
|
|
980
881
|
});
|
|
981
882
|
}
|
|
982
883
|
const globalOps = {
|
|
983
884
|
$collections: collectionNames,
|
|
984
|
-
async $
|
|
885
|
+
async $invalidate(scope) {
|
|
985
886
|
logger?.debug?.("グローバルキャッシュを無効化", {
|
|
986
|
-
operation: "$
|
|
987
|
-
cacheAdapter:
|
|
887
|
+
operation: "$invalidate",
|
|
888
|
+
cacheAdapter: cacheRes.docName
|
|
988
889
|
});
|
|
989
|
-
|
|
990
|
-
if (!cache.invalidate) continue;
|
|
991
|
-
await cache.invalidate(scope ?? "all");
|
|
992
|
-
}
|
|
890
|
+
await cacheRes.doc.invalidate(scope ?? "all");
|
|
993
891
|
},
|
|
994
892
|
$handler(handlerOpts) {
|
|
995
893
|
return createHandler({
|
|
996
|
-
imageCache:
|
|
894
|
+
imageCache: cacheRes.img,
|
|
997
895
|
parseWebhook: async (req, webhookSecret) => {
|
|
998
896
|
for (const name of collectionNames) {
|
|
999
|
-
const ds = opts.
|
|
897
|
+
const ds = opts.collections[name].source;
|
|
1000
898
|
if (ds.parseWebhook) try {
|
|
1001
899
|
return await ds.parseWebhook(req.clone(), { secret: webhookSecret });
|
|
1002
900
|
} catch (err) {
|
|
@@ -1016,11 +914,11 @@ function createCMS(opts) {
|
|
|
1016
914
|
} catch {}
|
|
1017
915
|
return null;
|
|
1018
916
|
},
|
|
1019
|
-
revalidate: (scope) => globalOps.$
|
|
917
|
+
revalidate: (scope) => globalOps.$invalidate(scope)
|
|
1020
918
|
}, handlerOpts);
|
|
1021
919
|
},
|
|
1022
920
|
$getCachedImage(hash) {
|
|
1023
|
-
return
|
|
921
|
+
return cacheRes.img.get(hash);
|
|
1024
922
|
}
|
|
1025
923
|
};
|
|
1026
924
|
return Object.assign(Object.create(null), collections, globalOps);
|
|
@@ -1031,6 +929,6 @@ function definePlugin(plugin) {
|
|
|
1031
929
|
return plugin;
|
|
1032
930
|
}
|
|
1033
931
|
//#endregion
|
|
1034
|
-
export { CMSError, CollectionClientImpl, DEFAULT_RETRY_CONFIG, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale,
|
|
932
|
+
export { CMSError, CollectionClientImpl, DEFAULT_RETRY_CONFIG, collectionKey, createCMS, createHandler, definePlugin, isCMSError, isCMSErrorInNamespace, isStale, memoryCache, mergeHooks, mergeLoggers, noopDocOps, noopImgOps, sha256Hex, withRetry };
|
|
1035
933
|
|
|
1036
934
|
//# sourceMappingURL=index.mjs.map
|