@notion-headless-cms/core 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cache/memory.d.mts +52 -0
  2. package/dist/cache/memory.mjs +112 -0
  3. package/dist/cache/memory.mjs.map +1 -0
  4. package/dist/cache/{noop.d.ts → noop.d.mts} +5 -3
  5. package/dist/cache/noop.mjs +44 -0
  6. package/dist/cache/noop.mjs.map +1 -0
  7. package/dist/cache-B-MG4yyg.d.mts +45 -0
  8. package/dist/content-BrwEY2_p.d.mts +53 -0
  9. package/dist/{errors.d.ts → errors.d.mts} +18 -16
  10. package/dist/errors.mjs +24 -0
  11. package/dist/errors.mjs.map +1 -0
  12. package/dist/hooks-DCSAQkST.d.mts +60 -0
  13. package/dist/hooks.d.mts +2 -0
  14. package/dist/hooks.mjs +75 -0
  15. package/dist/hooks.mjs.map +1 -0
  16. package/dist/index.d.mts +449 -0
  17. package/dist/index.mjs +616 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/package.json +17 -16
  20. package/dist/cache/memory.d.ts +0 -54
  21. package/dist/cache/memory.js +0 -15
  22. package/dist/cache/memory.js.map +0 -1
  23. package/dist/cache/noop.js +0 -9
  24. package/dist/cache/noop.js.map +0 -1
  25. package/dist/cache-DvbyemBK.d.ts +0 -33
  26. package/dist/chunk-4KGKWKKI.js +0 -80
  27. package/dist/chunk-4KGKWKKI.js.map +0 -1
  28. package/dist/chunk-6DG63XUF.js +0 -42
  29. package/dist/chunk-6DG63XUF.js.map +0 -1
  30. package/dist/chunk-6LHROEPI.js +0 -104
  31. package/dist/chunk-6LHROEPI.js.map +0 -1
  32. package/dist/chunk-V6ML4QE5.js +0 -26
  33. package/dist/chunk-V6ML4QE5.js.map +0 -1
  34. package/dist/content-Biqf0l_o.d.ts +0 -51
  35. package/dist/errors.js +0 -11
  36. package/dist/errors.js.map +0 -1
  37. package/dist/hooks-B83RUclt.d.ts +0 -41
  38. package/dist/hooks.d.ts +0 -2
  39. package/dist/hooks.js +0 -9
  40. package/dist/hooks.js.map +0 -1
  41. package/dist/index.d.ts +0 -278
  42. package/dist/index.js +0 -598
  43. package/dist/index.js.map +0 -1
package/dist/index.js DELETED
@@ -1,598 +0,0 @@
1
- import {
2
- CMSError,
3
- isCMSError,
4
- isCMSErrorInNamespace
5
- } from "./chunk-V6ML4QE5.js";
6
- import {
7
- mergeHooks,
8
- mergeLoggers
9
- } from "./chunk-4KGKWKKI.js";
10
- import {
11
- memoryCache,
12
- memoryDocumentCache,
13
- memoryImageCache
14
- } from "./chunk-6LHROEPI.js";
15
- import {
16
- noopDocumentCache,
17
- noopImageCache
18
- } from "./chunk-6DG63XUF.js";
19
-
20
- // src/cache.ts
21
- async function sha256Hex(input) {
22
- const data = new TextEncoder().encode(input);
23
- const hash = await crypto.subtle.digest("SHA-256", data);
24
- return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
25
- }
26
- function isStale(cachedAt, ttlMs) {
27
- if (ttlMs === void 0) return false;
28
- return Date.now() - cachedAt > ttlMs;
29
- }
30
-
31
- // src/image.ts
32
- function inferContentType(url, responseContentType) {
33
- if (responseContentType?.startsWith("image/")) {
34
- return responseContentType.split(";")[0].trim();
35
- }
36
- if (url.includes(".png")) return "image/png";
37
- if (url.includes(".gif")) return "image/gif";
38
- if (url.includes(".webp")) return "image/webp";
39
- return "image/jpeg";
40
- }
41
- async function fetchAndCacheImage(cache, notionUrl, imageProxyBase) {
42
- const hash = await sha256Hex(notionUrl);
43
- const proxyUrl = `${imageProxyBase}/${hash}`;
44
- const existing = await cache.get(hash);
45
- if (existing) return proxyUrl;
46
- try {
47
- const response = await fetch(notionUrl, {
48
- signal: AbortSignal.timeout(1e4)
49
- });
50
- if (!response.ok) {
51
- throw new CMSError({
52
- code: "cache/image_fetch_failed",
53
- message: `Failed to fetch Notion image: HTTP ${response.status}`,
54
- context: {
55
- operation: "fetchAndCacheImage",
56
- notionUrl,
57
- httpStatus: response.status
58
- }
59
- });
60
- }
61
- const data = await response.arrayBuffer();
62
- const contentType = inferContentType(
63
- notionUrl,
64
- response.headers.get("content-type")
65
- );
66
- await cache.set(hash, data, contentType);
67
- } catch (err) {
68
- if (isCMSError(err)) throw err;
69
- throw new CMSError({
70
- code: "cache/io_failed",
71
- message: "Failed to fetch or cache Notion image.",
72
- cause: err,
73
- context: { operation: "fetchAndCacheImage", notionUrl }
74
- });
75
- }
76
- return proxyUrl;
77
- }
78
- function buildCacheImageFn(cache, imageProxyBase) {
79
- return (notionUrl) => fetchAndCacheImage(cache, notionUrl, imageProxyBase);
80
- }
81
-
82
- // src/query.ts
83
- var QueryBuilder = class {
84
- source;
85
- defaultStatuses;
86
- _statuses = [];
87
- _tags = [];
88
- _predicate;
89
- _sortField;
90
- _sortDir = "asc";
91
- _page = 1;
92
- _perPage = 20;
93
- constructor(source, defaultStatuses = []) {
94
- this.source = source;
95
- this.defaultStatuses = defaultStatuses;
96
- }
97
- status(s) {
98
- this._statuses = Array.isArray(s) ? s : [s];
99
- return this;
100
- }
101
- tag(t) {
102
- this._tags = Array.isArray(t) ? t : [t];
103
- return this;
104
- }
105
- where(predicate) {
106
- this._predicate = predicate;
107
- return this;
108
- }
109
- sortBy(field, dir = "asc") {
110
- this._sortField = field;
111
- this._sortDir = dir;
112
- return this;
113
- }
114
- paginate(opts) {
115
- this._page = opts.page;
116
- this._perPage = opts.perPage;
117
- return this;
118
- }
119
- async execute() {
120
- const statuses = this._statuses.length > 0 ? this._statuses : this.defaultStatuses.length > 0 ? this.defaultStatuses : void 0;
121
- if (this.source.query && !this._predicate) {
122
- const result = await this.source.query({
123
- filter: {
124
- statuses,
125
- tags: this._tags.length > 0 ? this._tags : void 0
126
- },
127
- sort: this._sortField ? [{ property: this._sortField, direction: this._sortDir }] : void 0,
128
- pageSize: this._perPage
129
- });
130
- const items2 = result.items;
131
- return {
132
- items: items2,
133
- total: items2.length,
134
- page: this._page,
135
- perPage: this._perPage,
136
- hasNext: result.hasMore,
137
- hasPrev: this._page > 1
138
- };
139
- }
140
- let items = await this.source.list({
141
- publishedStatuses: statuses
142
- });
143
- if (this._tags.length > 0) {
144
- items = items.filter((item) => {
145
- const itemTags = item.tags;
146
- if (!Array.isArray(itemTags)) return false;
147
- return this._tags.some((tag) => itemTags.includes(tag));
148
- });
149
- }
150
- if (this._predicate) {
151
- items = items.filter(this._predicate);
152
- }
153
- if (this._sortField) {
154
- const field = this._sortField;
155
- const dir = this._sortDir;
156
- items = [...items].sort((a, b) => {
157
- const av = a[field];
158
- const bv = b[field];
159
- const cmp = av < bv ? -1 : av > bv ? 1 : 0;
160
- return dir === "asc" ? cmp : -cmp;
161
- });
162
- }
163
- const total = items.length;
164
- const start = (this._page - 1) * this._perPage;
165
- const paged = items.slice(start, start + this._perPage);
166
- return {
167
- items: paged,
168
- total,
169
- page: this._page,
170
- perPage: this._perPage,
171
- hasNext: start + this._perPage < total,
172
- hasPrev: this._page > 1
173
- };
174
- }
175
- async executeOne() {
176
- const result = await this.execute();
177
- return result.items[0] ?? null;
178
- }
179
- /** 前後アイテムを返す。sortBy() で指定したソート順を適用する。 */
180
- async adjacent(slug) {
181
- const statuses = this._statuses.length > 0 ? this._statuses : this.defaultStatuses.length > 0 ? this.defaultStatuses : void 0;
182
- let items = await this.source.list({ publishedStatuses: statuses });
183
- if (this._sortField) {
184
- const field = this._sortField;
185
- const dir = this._sortDir;
186
- items = [...items].sort((a, b) => {
187
- const av = a[field];
188
- const bv = b[field];
189
- const cmp = av < bv ? -1 : av > bv ? 1 : 0;
190
- return dir === "asc" ? cmp : -cmp;
191
- });
192
- }
193
- const idx = items.findIndex((item) => item.slug === slug);
194
- if (idx === -1) return { prev: null, next: null };
195
- return {
196
- prev: idx > 0 ? items[idx - 1] : null,
197
- next: idx < items.length - 1 ? items[idx + 1] : null
198
- };
199
- }
200
- /** 最初の 1 件を返す。`.paginate({ page: 1, perPage: 1 }).executeOne()` の短縮形。 */
201
- first() {
202
- return this.paginate({ page: 1, perPage: 1 }).executeOne();
203
- }
204
- };
205
-
206
- // src/retry.ts
207
- var DEFAULT_RETRY_CONFIG = {
208
- retryOn: [429, 502, 503],
209
- maxRetries: 4,
210
- baseDelayMs: 1e3,
211
- jitter: true
212
- };
213
- async function withRetry(fn, config) {
214
- let lastError;
215
- for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
216
- try {
217
- return await fn();
218
- } catch (err) {
219
- const status = err.status;
220
- if (status === void 0 || !config.retryOn.includes(status)) {
221
- throw err;
222
- }
223
- lastError = err;
224
- if (attempt < config.maxRetries) {
225
- config.onRetry?.(attempt + 1, status);
226
- const jitterFactor = config.jitter !== false ? 0.5 + Math.random() * 0.5 : 1;
227
- const delay = config.baseDelayMs * 2 ** attempt * jitterFactor;
228
- await new Promise((resolve) => setTimeout(resolve, delay));
229
- }
230
- }
231
- }
232
- throw lastError;
233
- }
234
-
235
- // src/cms.ts
236
- var DEFAULT_IMAGE_PROXY_BASE = "/api/images";
237
- function resolveDocumentCache(cache) {
238
- if (!cache || cache === "disabled" || !cache.document) {
239
- return noopDocumentCache();
240
- }
241
- return cache.document;
242
- }
243
- function resolveImageCache(cache) {
244
- if (!cache || cache === "disabled" || !cache.image) {
245
- return noopImageCache();
246
- }
247
- return cache.image;
248
- }
249
- function resolveTtl(cache) {
250
- if (!cache || cache === "disabled") return void 0;
251
- return cache.ttlMs;
252
- }
253
- function hasImageCacheConfigured(cache) {
254
- if (!cache || cache === "disabled") return false;
255
- return !!cache.image;
256
- }
257
- var CMS = class {
258
- source;
259
- docCache;
260
- imgCache;
261
- hasImageCache;
262
- ttlMs;
263
- publishedStatuses;
264
- accessibleStatuses;
265
- imageProxyBase;
266
- contentConfig;
267
- rendererFn;
268
- waitUntil;
269
- hooks;
270
- logger;
271
- retryConfig;
272
- maxConcurrent;
273
- cache;
274
- constructor(opts) {
275
- this.source = opts.source;
276
- this.docCache = resolveDocumentCache(opts.cache);
277
- this.imgCache = resolveImageCache(opts.cache);
278
- this.hasImageCache = hasImageCacheConfigured(opts.cache);
279
- this.ttlMs = resolveTtl(opts.cache);
280
- this.publishedStatuses = opts.schema?.publishedStatuses ?? (opts.source.publishedStatuses ? [...opts.source.publishedStatuses] : []);
281
- this.accessibleStatuses = opts.schema?.accessibleStatuses ?? (opts.source.accessibleStatuses ? [...opts.source.accessibleStatuses] : []);
282
- this.imageProxyBase = opts.content?.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;
283
- this.contentConfig = opts.content;
284
- this.rendererFn = opts.renderer;
285
- this.waitUntil = opts.waitUntil;
286
- this.logger = mergeLoggers(opts.plugins ?? [], opts.logger);
287
- this.hooks = mergeHooks(opts.plugins ?? [], opts.hooks, this.logger);
288
- this.maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;
289
- this.retryConfig = {
290
- ...DEFAULT_RETRY_CONFIG,
291
- ...opts.rateLimiter ?? {}
292
- };
293
- this.cache = {
294
- getList: this.cachedList.bind(this),
295
- get: this.cachedGet.bind(this),
296
- prefetchAll: this.prefetchAll.bind(this),
297
- revalidate: this.revalidate.bind(this),
298
- sync: this.syncFromWebhook.bind(this),
299
- checkList: this.checkListUpdate.bind(this),
300
- checkItem: this.checkItemUpdate.bind(this)
301
- };
302
- }
303
- // ── コンテンツ取得 ──────────────────────────────────────────────────────
304
- /** 公開済みコンテンツ一覧をソースから直接取得する。 */
305
- list() {
306
- return withRetry(
307
- () => this.source.list({
308
- publishedStatuses: this.publishedStatuses.length > 0 ? this.publishedStatuses : void 0
309
- }),
310
- {
311
- ...this.retryConfig,
312
- onRetry: (attempt, status) => {
313
- this.logger?.warn?.("list() \u30EA\u30C8\u30E9\u30A4\u4E2D", { attempt, status });
314
- }
315
- }
316
- );
317
- }
318
- /** スラッグでコンテンツをソースから直接取得する。 */
319
- async find(slug) {
320
- const item = await withRetry(() => this.source.findBySlug(slug), {
321
- ...this.retryConfig,
322
- onRetry: (attempt, status) => {
323
- this.logger?.warn?.("find() \u30EA\u30C8\u30E9\u30A4\u4E2D", { attempt, status, slug });
324
- }
325
- });
326
- if (!item) return null;
327
- if (this.accessibleStatuses.length > 0 && (!item.status || !this.accessibleStatuses.includes(item.status))) {
328
- return null;
329
- }
330
- return item;
331
- }
332
- /** 複数スラッグをまとめてソースから直接取得する。accessibleStatuses フィルタを適用する。 */
333
- async findMany(slugs) {
334
- const results = /* @__PURE__ */ new Map();
335
- await Promise.all(
336
- slugs.map(async (slug) => {
337
- const item = await this.find(slug);
338
- if (item) results.set(slug, item);
339
- })
340
- );
341
- return results;
342
- }
343
- /** アイテムが publishedStatuses に含まれるステータスかどうかを返す。 */
344
- isPublished(item) {
345
- if (this.publishedStatuses.length === 0) return true;
346
- return !!item.status && this.publishedStatuses.includes(item.status);
347
- }
348
- /** コンテンツを Markdown → HTML にレンダリングし、CachedItem として返す。キャッシュには保存しない。 */
349
- async render(item) {
350
- return this.buildCachedItem(item);
351
- }
352
- /** QueryBuilder を返す。ステータス・タグ・ページネーションなどを連鎖で指定できる。 */
353
- query() {
354
- return new QueryBuilder(this.source, this.publishedStatuses);
355
- }
356
- /** 静的生成用のスラッグ一覧を返す。 */
357
- async getStaticSlugs() {
358
- const items = await this.list();
359
- return items.map((item) => item.slug);
360
- }
361
- // ── 画像配信 ──────────────────────────────────────────────────────────
362
- /** ハッシュキーでキャッシュ画像を取得する。 */
363
- getCachedImage(hash) {
364
- return this.imgCache.get(hash);
365
- }
366
- /** ハッシュキーでキャッシュ画像を Response として返す。 */
367
- async createCachedImageResponse(hash) {
368
- const object = await this.imgCache.get(hash);
369
- if (!object) return null;
370
- const headers = new Headers();
371
- if (object.contentType) headers.set("content-type", object.contentType);
372
- headers.set("cache-control", "public, max-age=31536000, immutable");
373
- return new Response(object.data, { headers });
374
- }
375
- // ── キャッシュ優先取得(Stale-While-Revalidate) ─────────────────────
376
- async cachedList() {
377
- const cached = await this.docCache.getList();
378
- if (cached && !isStale(cached.cachedAt, this.ttlMs)) {
379
- this.hooks.onListCacheHit?.(cached.items, cached.cachedAt);
380
- return { items: cached.items, isStale: false, cachedAt: cached.cachedAt };
381
- }
382
- this.hooks.onListCacheMiss?.();
383
- const items = await this.list();
384
- const cachedAt = Date.now();
385
- const save = this.docCache.setList({ items, cachedAt });
386
- if (this.waitUntil) {
387
- this.waitUntil(save);
388
- } else {
389
- await save;
390
- }
391
- return { items, isStale: !!cached, cachedAt };
392
- }
393
- async cachedGet(slug) {
394
- const cached = await this.docCache.getItem(slug);
395
- if (cached && !isStale(cached.cachedAt, this.ttlMs)) {
396
- this.hooks.onCacheHit?.(slug, cached);
397
- return cached;
398
- }
399
- this.hooks.onCacheMiss?.(slug);
400
- const item = await this.find(slug);
401
- if (!item) return null;
402
- const entry = await this.buildCachedItem(item);
403
- const save = this.docCache.setItem(slug, entry);
404
- if (this.waitUntil) {
405
- this.waitUntil(save);
406
- } else {
407
- await save;
408
- }
409
- return entry;
410
- }
411
- // ── キャッシュ管理 ────────────────────────────────────────────────────
412
- async prefetchAll(opts) {
413
- const items = await this.list();
414
- const concurrency = opts?.concurrency ?? this.maxConcurrent;
415
- let ok = 0;
416
- let failed = 0;
417
- for (let i = 0; i < items.length; i += concurrency) {
418
- const chunk = items.slice(i, i + concurrency);
419
- await Promise.all(
420
- chunk.map(async (item) => {
421
- try {
422
- const rendered = await this.buildCachedItem(item);
423
- await this.docCache.setItem(item.slug, rendered);
424
- ok++;
425
- } catch (err) {
426
- failed++;
427
- this.logger?.warn?.(
428
- "prefetchAll: \u30A2\u30A4\u30C6\u30E0\u306E\u4E8B\u524D\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u306B\u5931\u6557",
429
- {
430
- slug: item.slug,
431
- pageId: item.id,
432
- error: err instanceof Error ? err.message : String(err)
433
- }
434
- );
435
- }
436
- })
437
- );
438
- opts?.onProgress?.(Math.min(i + concurrency, items.length), items.length);
439
- }
440
- await this.docCache.setList({ items, cachedAt: Date.now() });
441
- return { ok, failed };
442
- }
443
- async revalidate(scope) {
444
- if (!this.docCache.invalidate) return;
445
- await this.docCache.invalidate(scope ?? "all");
446
- }
447
- async syncFromWebhook(payload) {
448
- const updated = [];
449
- if (payload?.slug) {
450
- const item = await this.find(payload.slug);
451
- if (item) {
452
- const rendered = await this.buildCachedItem(item);
453
- await this.docCache.setItem(item.slug, rendered);
454
- updated.push(item.slug);
455
- }
456
- } else {
457
- const result = await this.prefetchAll();
458
- if (result.ok > 0) {
459
- const items = await this.list();
460
- for (const item of items) updated.push(item.slug);
461
- }
462
- }
463
- return { updated };
464
- }
465
- async checkListUpdate(version) {
466
- const items = await this.list();
467
- const serverVersion = buildListVersion(items);
468
- if (serverVersion === version) return { changed: false };
469
- await this.docCache.setList({ items, cachedAt: Date.now() });
470
- return { changed: true, items };
471
- }
472
- async checkItemUpdate(slug, lastEdited) {
473
- const item = await this.find(slug);
474
- if (!item) return { changed: false };
475
- if (!this.isPublished(item)) return { changed: false };
476
- if (item.updatedAt === lastEdited) return { changed: false };
477
- const entry = await this.buildCachedItem(item);
478
- await this.docCache.setItem(slug, entry);
479
- return {
480
- changed: true,
481
- html: entry.html,
482
- item: entry.item,
483
- notionUpdatedAt: entry.notionUpdatedAt
484
- };
485
- }
486
- // ── プライベートヘルパー ────────────────────────────────────────────────
487
- async buildCachedItem(item) {
488
- const start = Date.now();
489
- this.logger?.info?.("\u30B3\u30F3\u30C6\u30F3\u30C4\u306E\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u958B\u59CB", {
490
- slug: item.slug,
491
- pageId: item.id
492
- });
493
- this.hooks.onRenderStart?.(item.slug);
494
- let markdown;
495
- try {
496
- markdown = await this.source.loadMarkdown(item);
497
- } catch (err) {
498
- if (isCMSError(err)) throw err;
499
- throw new CMSError({
500
- code: "source/load_markdown_failed",
501
- message: "Failed to load markdown from source.",
502
- cause: err,
503
- context: {
504
- operation: "buildCachedItem:loadMarkdown",
505
- pageId: item.id,
506
- slug: item.slug
507
- }
508
- });
509
- }
510
- const cacheImage = this.hasImageCache ? buildCacheImageFn(this.imgCache, this.imageProxyBase) : void 0;
511
- let html;
512
- const rendererFn = this.rendererFn ?? await loadDefaultRenderer();
513
- try {
514
- html = await rendererFn(markdown, {
515
- imageProxyBase: this.imageProxyBase,
516
- cacheImage,
517
- remarkPlugins: this.contentConfig?.remarkPlugins,
518
- rehypePlugins: this.contentConfig?.rehypePlugins
519
- });
520
- } catch (err) {
521
- if (isCMSError(err)) throw err;
522
- throw new CMSError({
523
- code: "renderer/failed",
524
- message: "Failed to render markdown.",
525
- cause: err,
526
- context: {
527
- operation: "buildCachedItem:renderMarkdown",
528
- pageId: item.id,
529
- slug: item.slug
530
- }
531
- });
532
- }
533
- if (this.hooks.afterRender) {
534
- html = await this.hooks.afterRender(html, item);
535
- }
536
- let result = {
537
- html,
538
- item,
539
- notionUpdatedAt: item.updatedAt,
540
- cachedAt: Date.now()
541
- };
542
- if (this.hooks.beforeCache) {
543
- result = await this.hooks.beforeCache(result);
544
- }
545
- const durationMs = Date.now() - start;
546
- this.logger?.info?.("\u30B3\u30F3\u30C6\u30F3\u30C4\u306E\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u5B8C\u4E86", {
547
- slug: item.slug,
548
- durationMs
549
- });
550
- this.hooks.onRenderEnd?.(item.slug, durationMs);
551
- return result;
552
- }
553
- };
554
- async function loadDefaultRenderer() {
555
- try {
556
- const mod = await import("@notion-headless-cms/renderer");
557
- return mod.renderMarkdown;
558
- } catch (err) {
559
- throw new CMSError({
560
- code: "renderer/failed",
561
- message: "renderer \u30AA\u30D7\u30B7\u30E7\u30F3\u304C\u672A\u6307\u5B9A\u3067 @notion-headless-cms/renderer \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002 createCMS({ renderer }) \u3067\u30EC\u30F3\u30C0\u30E9\u30FC\u3092\u6CE8\u5165\u3059\u308B\u304B\u3001@notion-headless-cms/renderer \u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
562
- cause: err,
563
- context: { operation: "loadDefaultRenderer" }
564
- });
565
- }
566
- }
567
- function buildListVersion(items) {
568
- return items.map((item) => `${item.id}:${item.updatedAt}`).join("|");
569
- }
570
- function createCMS(opts) {
571
- return new CMS(opts);
572
- }
573
-
574
- // src/types/plugin.ts
575
- function definePlugin(plugin) {
576
- return plugin;
577
- }
578
- export {
579
- CMS,
580
- CMSError,
581
- DEFAULT_RETRY_CONFIG,
582
- QueryBuilder,
583
- createCMS,
584
- definePlugin,
585
- isCMSError,
586
- isCMSErrorInNamespace,
587
- isStale,
588
- memoryCache,
589
- memoryDocumentCache,
590
- memoryImageCache,
591
- mergeHooks,
592
- mergeLoggers,
593
- noopDocumentCache,
594
- noopImageCache,
595
- sha256Hex,
596
- withRetry
597
- };
598
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cache.ts","../src/image.ts","../src/query.ts","../src/retry.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 { BaseContentItem } from \"./types/content\";\nimport type { DataSourceAdapter } from \"./types/source\";\n\nexport interface QueryResult<T> {\n\titems: T[];\n\ttotal: number;\n\tpage: number;\n\tperPage: number;\n\thasNext: boolean;\n\thasPrev: boolean;\n}\n\nexport class QueryBuilder<T extends BaseContentItem> {\n\tprivate readonly source: DataSourceAdapter<T>;\n\tprivate readonly defaultStatuses: string[];\n\n\tprivate _statuses: string[] = [];\n\tprivate _tags: string[] = [];\n\tprivate _predicate: ((item: T) => boolean) | undefined;\n\tprivate _sortField: (keyof T & string) | undefined;\n\tprivate _sortDir: \"asc\" | \"desc\" = \"asc\";\n\tprivate _page = 1;\n\tprivate _perPage = 20;\n\n\tconstructor(source: DataSourceAdapter<T>, defaultStatuses: string[] = []) {\n\t\tthis.source = source;\n\t\tthis.defaultStatuses = defaultStatuses;\n\t}\n\n\tstatus(s: string | string[]): this {\n\t\tthis._statuses = Array.isArray(s) ? s : [s];\n\t\treturn this;\n\t}\n\n\ttag(t: string | string[]): this {\n\t\tthis._tags = Array.isArray(t) ? t : [t];\n\t\treturn this;\n\t}\n\n\twhere(predicate: (item: T) => boolean): this {\n\t\tthis._predicate = predicate;\n\t\treturn this;\n\t}\n\n\tsortBy(field: keyof T & string, dir: \"asc\" | \"desc\" = \"asc\"): this {\n\t\tthis._sortField = field;\n\t\tthis._sortDir = dir;\n\t\treturn this;\n\t}\n\n\tpaginate(opts: { page: number; perPage: number }): this {\n\t\tthis._page = opts.page;\n\t\tthis._perPage = opts.perPage;\n\t\treturn this;\n\t}\n\n\tasync execute(): Promise<QueryResult<T>> {\n\t\tconst statuses =\n\t\t\tthis._statuses.length > 0\n\t\t\t\t? this._statuses\n\t\t\t\t: this.defaultStatuses.length > 0\n\t\t\t\t\t? this.defaultStatuses\n\t\t\t\t\t: undefined;\n\n\t\t// push-down: source.query が存在 かつ where() 未使用 → Notion API に委譲\n\t\tif (this.source.query && !this._predicate) {\n\t\t\tconst result = await this.source.query({\n\t\t\t\tfilter: {\n\t\t\t\t\tstatuses,\n\t\t\t\t\ttags: this._tags.length > 0 ? this._tags : undefined,\n\t\t\t\t},\n\t\t\t\tsort: this._sortField\n\t\t\t\t\t? [{ property: this._sortField, direction: this._sortDir }]\n\t\t\t\t\t: undefined,\n\t\t\t\tpageSize: this._perPage,\n\t\t\t});\n\t\t\tconst items = result.items;\n\t\t\treturn {\n\t\t\t\titems,\n\t\t\t\ttotal: items.length,\n\t\t\t\tpage: this._page,\n\t\t\t\tperPage: this._perPage,\n\t\t\t\thasNext: result.hasMore,\n\t\t\t\thasPrev: this._page > 1,\n\t\t\t};\n\t\t}\n\n\t\t// フォールバック: インメモリフィルタ\n\t\tlet items = await this.source.list({\n\t\t\tpublishedStatuses: statuses,\n\t\t});\n\n\t\tif (this._tags.length > 0) {\n\t\t\titems = items.filter((item) => {\n\t\t\t\tconst itemTags = (item as Record<string, unknown>).tags;\n\t\t\t\tif (!Array.isArray(itemTags)) return false;\n\t\t\t\treturn this._tags.some((tag) => (itemTags as string[]).includes(tag));\n\t\t\t});\n\t\t}\n\n\t\tif (this._predicate) {\n\t\t\titems = items.filter(this._predicate);\n\t\t}\n\n\t\tif (this._sortField) {\n\t\t\tconst field = this._sortField;\n\t\t\tconst dir = this._sortDir;\n\t\t\titems = [...items].sort((a, b) => {\n\t\t\t\tconst av = a[field] as string | number;\n\t\t\t\tconst bv = b[field] as string | number;\n\t\t\t\tconst cmp = av < bv ? -1 : av > bv ? 1 : 0;\n\t\t\t\treturn dir === \"asc\" ? cmp : -cmp;\n\t\t\t});\n\t\t}\n\n\t\tconst total = items.length;\n\t\tconst start = (this._page - 1) * this._perPage;\n\t\tconst paged = items.slice(start, start + this._perPage);\n\n\t\treturn {\n\t\t\titems: paged,\n\t\t\ttotal,\n\t\t\tpage: this._page,\n\t\t\tperPage: this._perPage,\n\t\t\thasNext: start + this._perPage < total,\n\t\t\thasPrev: this._page > 1,\n\t\t};\n\t}\n\n\tasync executeOne(): Promise<T | null> {\n\t\tconst result = await this.execute();\n\t\treturn result.items[0] ?? null;\n\t}\n\n\t/** 前後アイテムを返す。sortBy() で指定したソート順を適用する。 */\n\tasync adjacent(slug: string): Promise<{ prev: T | null; next: T | null }> {\n\t\tconst statuses =\n\t\t\tthis._statuses.length > 0\n\t\t\t\t? this._statuses\n\t\t\t\t: this.defaultStatuses.length > 0\n\t\t\t\t\t? this.defaultStatuses\n\t\t\t\t\t: undefined;\n\t\tlet items = await this.source.list({ publishedStatuses: statuses });\n\n\t\tif (this._sortField) {\n\t\t\tconst field = this._sortField;\n\t\t\tconst dir = this._sortDir;\n\t\t\titems = [...items].sort((a, b) => {\n\t\t\t\tconst av = a[field] as string | number;\n\t\t\t\tconst bv = b[field] as string | number;\n\t\t\t\tconst cmp = av < bv ? -1 : av > bv ? 1 : 0;\n\t\t\t\treturn dir === \"asc\" ? cmp : -cmp;\n\t\t\t});\n\t\t}\n\n\t\tconst idx = items.findIndex((item) => item.slug === slug);\n\t\tif (idx === -1) return { prev: null, next: null };\n\t\treturn {\n\t\t\tprev: idx > 0 ? items[idx - 1] : null,\n\t\t\tnext: idx < items.length - 1 ? items[idx + 1] : null,\n\t\t};\n\t}\n\n\t/** 最初の 1 件を返す。`.paginate({ page: 1, perPage: 1 }).executeOne()` の短縮形。 */\n\tfirst(): Promise<T | null> {\n\t\treturn this.paginate({ page: 1, perPage: 1 }).executeOne();\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 { noopDocumentCache, noopImageCache } from \"./cache/noop\";\nimport { CMSError, isCMSError } from \"./errors\";\nimport { mergeHooks, mergeLoggers } from \"./hooks\";\nimport { buildCacheImageFn } from \"./image\";\nimport { QueryBuilder } from \"./query\";\nimport type { RetryConfig } from \"./retry\";\nimport { DEFAULT_RETRY_CONFIG, withRetry } from \"./retry\";\nimport type {\n\tBaseContentItem,\n\tCacheConfig,\n\tCachedItem,\n\tCMSHooks,\n\tCreateCMSOptions,\n\tDataSourceAdapter,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tLogger,\n\tRendererFn,\n\tStorageBinary,\n} from \"./types/index\";\n\nconst DEFAULT_IMAGE_PROXY_BASE = \"/api/images\";\n\nfunction resolveDocumentCache<T extends BaseContentItem>(\n\tcache: CacheConfig<T> | undefined,\n): DocumentCacheAdapter<T> {\n\tif (!cache || cache === \"disabled\" || !cache.document) {\n\t\treturn noopDocumentCache<T>();\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/** キャッシュ系の公開名前空間。SWR 読み取りとキャッシュ管理を統合する。 */\nexport interface CacheAccessor<T extends BaseContentItem> {\n\t/** キャッシュ優先でコンテンツ一覧を返す(SWR)。 */\n\tgetList(): Promise<{ items: T[]; isStale: boolean; cachedAt: number }>;\n\t/** キャッシュ優先で単一コンテンツを返す(SWR)。HTML 付きの CachedItem を返す。 */\n\tget(slug: string): Promise<CachedItem<T> | null>;\n\t/** 全コンテンツを事前レンダリングしてキャッシュに保存する。 */\n\tprefetchAll(opts?: {\n\t\tconcurrency?: number;\n\t\tonProgress?: (done: number, total: number) => void;\n\t}): Promise<{ ok: number; failed: number }>;\n\t/** 指定スコープのキャッシュを無効化する。 */\n\trevalidate(scope?: \"all\" | { slug: string }): Promise<void>;\n\t/** Webhook ペイロードを元にキャッシュを同期する。 */\n\tsync(payload?: { slug?: string }): Promise<{ updated: string[] }>;\n\t/** リスト全体の変更を検知する。version はリスト取得時の buildListVersion の値。 */\n\tcheckList(\n\t\tversion: string,\n\t): Promise<{ changed: false } | { changed: true; items: T[] }>;\n\t/** 単一アイテムの変更を検知し、変更があれば再レンダリングしてキャッシュを更新する。 */\n\tcheckItem(\n\t\tslug: string,\n\t\tlastEdited: string,\n\t): Promise<\n\t\t| { changed: false }\n\t\t| { changed: true; html: string; item: T; notionUpdatedAt: string }\n\t>;\n}\n\n/**\n * Notion をバックエンドとして使う汎用ヘッドレス CMS クラス。\n *\n * @example\n * const cms = createCMS({\n * source: notionAdapter({ token: '...', dataSourceId: '...' }),\n * });\n * const items = await cms.list();\n * const entry = await cms.cache.get('my-post');\n */\nexport class CMS<T extends BaseContentItem = BaseContentItem> {\n\tprivate readonly source: DataSourceAdapter<T>;\n\tprivate readonly docCache: DocumentCacheAdapter<T>;\n\tprivate readonly imgCache: ImageCacheAdapter;\n\tprivate readonly hasImageCache: boolean;\n\tprivate readonly ttlMs: number | undefined;\n\tprivate readonly publishedStatuses: string[];\n\tprivate readonly accessibleStatuses: string[];\n\tprivate readonly imageProxyBase: string;\n\tprivate readonly contentConfig: CreateCMSOptions<T>[\"content\"];\n\tprivate readonly rendererFn: RendererFn | undefined;\n\tprivate readonly waitUntil: ((p: Promise<unknown>) => void) | undefined;\n\tprivate readonly hooks: CMSHooks<T>;\n\tprivate readonly logger: Logger | undefined;\n\tprivate readonly retryConfig: RetryConfig;\n\tprivate readonly maxConcurrent: number;\n\n\treadonly cache: CacheAccessor<T>;\n\n\tconstructor(opts: CreateCMSOptions<T>) {\n\t\tthis.source = opts.source;\n\t\tthis.docCache = resolveDocumentCache(opts.cache);\n\t\tthis.imgCache = resolveImageCache(opts.cache);\n\t\tthis.hasImageCache = hasImageCacheConfigured(opts.cache);\n\t\tthis.ttlMs = resolveTtl(opts.cache);\n\t\tthis.publishedStatuses =\n\t\t\topts.schema?.publishedStatuses ??\n\t\t\t(opts.source.publishedStatuses ? [...opts.source.publishedStatuses] : []);\n\t\tthis.accessibleStatuses =\n\t\t\topts.schema?.accessibleStatuses ??\n\t\t\t(opts.source.accessibleStatuses\n\t\t\t\t? [...opts.source.accessibleStatuses]\n\t\t\t\t: []);\n\t\tthis.imageProxyBase =\n\t\t\topts.content?.imageProxyBase ?? DEFAULT_IMAGE_PROXY_BASE;\n\t\tthis.contentConfig = opts.content;\n\t\tthis.rendererFn = opts.renderer;\n\t\tthis.waitUntil = opts.waitUntil;\n\t\tthis.logger = mergeLoggers(opts.plugins ?? [], opts.logger);\n\t\tthis.hooks = mergeHooks(opts.plugins ?? [], opts.hooks, this.logger);\n\t\tthis.maxConcurrent = opts.rateLimiter?.maxConcurrent ?? 3;\n\t\tthis.retryConfig = {\n\t\t\t...DEFAULT_RETRY_CONFIG,\n\t\t\t...(opts.rateLimiter ?? {}),\n\t\t};\n\n\t\tthis.cache = {\n\t\t\tgetList: this.cachedList.bind(this),\n\t\t\tget: this.cachedGet.bind(this),\n\t\t\tprefetchAll: this.prefetchAll.bind(this),\n\t\t\trevalidate: this.revalidate.bind(this),\n\t\t\tsync: this.syncFromWebhook.bind(this),\n\t\t\tcheckList: this.checkListUpdate.bind(this),\n\t\t\tcheckItem: this.checkItemUpdate.bind(this),\n\t\t};\n\t}\n\n\t// ── コンテンツ取得 ──────────────────────────────────────────────────────\n\n\t/** 公開済みコンテンツ一覧をソースから直接取得する。 */\n\tlist(): Promise<T[]> {\n\t\treturn withRetry(\n\t\t\t() =>\n\t\t\t\tthis.source.list({\n\t\t\t\t\tpublishedStatuses:\n\t\t\t\t\t\tthis.publishedStatuses.length > 0\n\t\t\t\t\t\t\t? this.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.retryConfig,\n\t\t\t\tonRetry: (attempt, status) => {\n\t\t\t\t\tthis.logger?.warn?.(\"list() リトライ中\", { attempt, status });\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\t/** スラッグでコンテンツをソースから直接取得する。 */\n\tasync find(slug: string): Promise<T | null> {\n\t\tconst item = await withRetry(() => this.source.findBySlug(slug), {\n\t\t\t...this.retryConfig,\n\t\t\tonRetry: (attempt, status) => {\n\t\t\t\tthis.logger?.warn?.(\"find() リトライ中\", { attempt, status, slug });\n\t\t\t},\n\t\t});\n\t\tif (!item) return null;\n\t\tif (\n\t\t\tthis.accessibleStatuses.length > 0 &&\n\t\t\t(!item.status || !this.accessibleStatuses.includes(item.status))\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\treturn item;\n\t}\n\n\t/** 複数スラッグをまとめてソースから直接取得する。accessibleStatuses フィルタを適用する。 */\n\tasync findMany(slugs: string[]): Promise<Map<string, T>> {\n\t\tconst results = new Map<string, T>();\n\t\tawait Promise.all(\n\t\t\tslugs.map(async (slug) => {\n\t\t\t\tconst item = await this.find(slug);\n\t\t\t\tif (item) results.set(slug, item);\n\t\t\t}),\n\t\t);\n\t\treturn results;\n\t}\n\n\t/** アイテムが publishedStatuses に含まれるステータスかどうかを返す。 */\n\tisPublished(item: T): boolean {\n\t\tif (this.publishedStatuses.length === 0) return true;\n\t\treturn !!item.status && this.publishedStatuses.includes(item.status);\n\t}\n\n\t/** コンテンツを Markdown → HTML にレンダリングし、CachedItem として返す。キャッシュには保存しない。 */\n\tasync render(item: T): Promise<CachedItem<T>> {\n\t\treturn this.buildCachedItem(item);\n\t}\n\n\t/** QueryBuilder を返す。ステータス・タグ・ページネーションなどを連鎖で指定できる。 */\n\tquery(): QueryBuilder<T> {\n\t\treturn new QueryBuilder(this.source, this.publishedStatuses);\n\t}\n\n\t/** 静的生成用のスラッグ一覧を返す。 */\n\tasync getStaticSlugs(): Promise<string[]> {\n\t\tconst items = await this.list();\n\t\treturn items.map((item) => item.slug);\n\t}\n\n\t// ── 画像配信 ──────────────────────────────────────────────────────────\n\n\t/** ハッシュキーでキャッシュ画像を取得する。 */\n\tgetCachedImage(hash: string): Promise<StorageBinary | null> {\n\t\treturn this.imgCache.get(hash);\n\t}\n\n\t/** ハッシュキーでキャッシュ画像を Response として返す。 */\n\tasync createCachedImageResponse(hash: string): Promise<Response | null> {\n\t\tconst object = await this.imgCache.get(hash);\n\t\tif (!object) return null;\n\t\tconst headers = new Headers();\n\t\tif (object.contentType) headers.set(\"content-type\", object.contentType);\n\t\theaders.set(\"cache-control\", \"public, max-age=31536000, immutable\");\n\t\treturn new Response(object.data, { headers });\n\t}\n\n\t// ── キャッシュ優先取得(Stale-While-Revalidate) ─────────────────────\n\n\tprivate async cachedList(): Promise<{\n\t\titems: T[];\n\t\tisStale: boolean;\n\t\tcachedAt: number;\n\t}> {\n\t\tconst cached = await this.docCache.getList();\n\t\tif (cached && !isStale(cached.cachedAt, this.ttlMs)) {\n\t\t\tthis.hooks.onListCacheHit?.(cached.items, cached.cachedAt);\n\t\t\treturn { items: cached.items, isStale: false, cachedAt: cached.cachedAt };\n\t\t}\n\n\t\tthis.hooks.onListCacheMiss?.();\n\t\tconst items = await this.list();\n\t\tconst cachedAt = Date.now();\n\t\tconst save = this.docCache.setList({ items, cachedAt });\n\t\tif (this.waitUntil) {\n\t\t\tthis.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\t\treturn { items, isStale: !!cached, cachedAt };\n\t}\n\n\tprivate async cachedGet(slug: string): Promise<CachedItem<T> | null> {\n\t\tconst cached = await this.docCache.getItem(slug);\n\t\tif (cached && !isStale(cached.cachedAt, this.ttlMs)) {\n\t\t\tthis.hooks.onCacheHit?.(slug, cached);\n\t\t\treturn cached;\n\t\t}\n\n\t\tthis.hooks.onCacheMiss?.(slug);\n\t\tconst item = await this.find(slug);\n\t\tif (!item) return null;\n\t\tconst entry = await this.buildCachedItem(item);\n\t\tconst save = this.docCache.setItem(slug, entry);\n\t\tif (this.waitUntil) {\n\t\t\tthis.waitUntil(save);\n\t\t} else {\n\t\t\tawait save;\n\t\t}\n\t\treturn entry;\n\t}\n\n\t// ── キャッシュ管理 ────────────────────────────────────────────────────\n\n\tprivate async prefetchAll(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.list();\n\t\tconst concurrency = opts?.concurrency ?? this.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 this.buildCachedItem(item);\n\t\t\t\t\t\tawait this.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.logger?.warn?.(\n\t\t\t\t\t\t\t\"prefetchAll: アイテムの事前レンダリングに失敗\",\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.docCache.setList({ items, cachedAt: Date.now() });\n\t\treturn { ok, failed };\n\t}\n\n\tprivate async revalidate(scope?: \"all\" | { slug: string }): Promise<void> {\n\t\tif (!this.docCache.invalidate) return;\n\t\tawait this.docCache.invalidate(scope ?? \"all\");\n\t}\n\n\tprivate async syncFromWebhook(payload?: {\n\t\tslug?: string;\n\t}): Promise<{ updated: string[] }> {\n\t\tconst updated: string[] = [];\n\n\t\tif (payload?.slug) {\n\t\t\tconst item = await this.find(payload.slug);\n\t\t\tif (item) {\n\t\t\t\tconst rendered = await this.buildCachedItem(item);\n\t\t\t\tawait this.docCache.setItem(item.slug, rendered);\n\t\t\t\tupdated.push(item.slug);\n\t\t\t}\n\t\t} else {\n\t\t\tconst result = await this.prefetchAll();\n\t\t\tif (result.ok > 0) {\n\t\t\t\tconst items = await this.list();\n\t\t\t\tfor (const item of items) updated.push(item.slug);\n\t\t\t}\n\t\t}\n\n\t\treturn { updated };\n\t}\n\n\tprivate async checkListUpdate(\n\t\tversion: string,\n\t): Promise<{ changed: false } | { changed: true; items: T[] }> {\n\t\tconst items = await this.list();\n\t\tconst serverVersion = buildListVersion(items);\n\t\tif (serverVersion === version) return { changed: false };\n\t\tawait this.docCache.setList({ items, cachedAt: Date.now() });\n\t\treturn { changed: true, items };\n\t}\n\n\tprivate async checkItemUpdate(\n\t\tslug: string,\n\t\tlastEdited: string,\n\t): Promise<\n\t\t| { changed: false }\n\t\t| { changed: true; html: string; item: T; notionUpdatedAt: string }\n\t> {\n\t\tconst item = await this.find(slug);\n\t\tif (!item) return { changed: false };\n\t\tif (!this.isPublished(item)) return { changed: false };\n\t\tif (item.updatedAt === lastEdited) return { changed: false };\n\n\t\tconst entry = await this.buildCachedItem(item);\n\t\tawait this.docCache.setItem(slug, entry);\n\n\t\treturn {\n\t\t\tchanged: true,\n\t\t\thtml: entry.html,\n\t\t\titem: entry.item,\n\t\t\tnotionUpdatedAt: entry.notionUpdatedAt,\n\t\t};\n\t}\n\n\t// ── プライベートヘルパー ────────────────────────────────────────────────\n\n\tprivate async buildCachedItem(item: T): Promise<CachedItem<T>> {\n\t\tconst start = Date.now();\n\t\tthis.logger?.info?.(\"コンテンツのレンダリング開始\", {\n\t\t\tslug: item.slug,\n\t\t\tpageId: item.id,\n\t\t});\n\t\tthis.hooks.onRenderStart?.(item.slug);\n\n\t\tlet markdown: string;\n\t\ttry {\n\t\t\tmarkdown = await this.source.loadMarkdown(item);\n\t\t} catch (err) {\n\t\t\tif (isCMSError(err)) throw err;\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"source/load_markdown_failed\",\n\t\t\t\tmessage: \"Failed to load markdown from source.\",\n\t\t\t\tcause: err,\n\t\t\t\tcontext: {\n\t\t\t\t\toperation: \"buildCachedItem:loadMarkdown\",\n\t\t\t\t\tpageId: item.id,\n\t\t\t\t\tslug: item.slug,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst cacheImage = this.hasImageCache\n\t\t\t? buildCacheImageFn(this.imgCache, this.imageProxyBase)\n\t\t\t: undefined;\n\n\t\tlet html: string;\n\t\tconst rendererFn = this.rendererFn ?? (await loadDefaultRenderer());\n\t\ttry {\n\t\t\thtml = await rendererFn(markdown, {\n\t\t\t\timageProxyBase: this.imageProxyBase,\n\t\t\t\tcacheImage,\n\t\t\t\tremarkPlugins: this.contentConfig?.remarkPlugins,\n\t\t\t\trehypePlugins: this.contentConfig?.rehypePlugins,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (isCMSError(err)) throw err;\n\t\t\tthrow new CMSError({\n\t\t\t\tcode: \"renderer/failed\",\n\t\t\t\tmessage: \"Failed to render markdown.\",\n\t\t\t\tcause: err,\n\t\t\t\tcontext: {\n\t\t\t\t\toperation: \"buildCachedItem:renderMarkdown\",\n\t\t\t\t\tpageId: item.id,\n\t\t\t\t\tslug: item.slug,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\t// afterRender フック\n\t\tif (this.hooks.afterRender) {\n\t\t\thtml = await this.hooks.afterRender(html, item);\n\t\t}\n\n\t\tlet result: CachedItem<T> = {\n\t\t\thtml,\n\t\t\titem,\n\t\t\tnotionUpdatedAt: item.updatedAt,\n\t\t\tcachedAt: Date.now(),\n\t\t};\n\n\t\t// beforeCache フック\n\t\tif (this.hooks.beforeCache) {\n\t\t\tresult = await this.hooks.beforeCache(result);\n\t\t}\n\n\t\tconst durationMs = Date.now() - start;\n\t\tthis.logger?.info?.(\"コンテンツのレンダリング完了\", {\n\t\t\tslug: item.slug,\n\t\t\tdurationMs,\n\t\t});\n\t\tthis.hooks.onRenderEnd?.(item.slug, durationMs);\n\n\t\treturn result;\n\t}\n}\n\n/**\n * renderer オプション未指定時のフォールバック。\n * @notion-headless-cms/renderer を動的 import する。\n * adapter-cloudflare / adapter-node は renderer を明示注入するためこのパスは通らない。\n */\nasync function loadDefaultRenderer(): Promise<RendererFn> {\n\ttry {\n\t\tconst mod = await import(\"@notion-headless-cms/renderer\");\n\t\treturn mod.renderMarkdown;\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\nfunction buildListVersion<T extends BaseContentItem>(items: T[]): string {\n\treturn items.map((item) => `${item.id}:${item.updatedAt}`).join(\"|\");\n}\n\n/** 設定済みの CMS インスタンスを生成するファクトリ関数。 */\nexport function createCMS<T extends BaseContentItem = BaseContentItem>(\n\topts: CreateCMSOptions<T>,\n): CMS<T> {\n\treturn new CMS<T>(opts);\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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","names":["items"]}