@notion-headless-cms/core 0.1.2 → 0.2.0

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 (45) hide show
  1. package/README.md +69 -175
  2. package/dist/cache/memory.d.mts +52 -0
  3. package/dist/cache/memory.mjs +115 -0
  4. package/dist/cache/memory.mjs.map +1 -0
  5. package/dist/cache/{noop.d.ts → noop.d.mts} +5 -3
  6. package/dist/cache/noop.mjs +44 -0
  7. package/dist/cache/noop.mjs.map +1 -0
  8. package/dist/cache-Av7HRw_s.d.mts +205 -0
  9. package/dist/content-BrwEY2_p.d.mts +53 -0
  10. package/dist/errors.d.mts +32 -0
  11. package/dist/errors.mjs +24 -0
  12. package/dist/errors.mjs.map +1 -0
  13. package/dist/hooks-DCSAQkST.d.mts +60 -0
  14. package/dist/hooks.d.mts +2 -0
  15. package/dist/hooks.mjs +75 -0
  16. package/dist/hooks.mjs.map +1 -0
  17. package/dist/index.d.mts +349 -0
  18. package/dist/index.mjs +672 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/package.json +17 -16
  21. package/dist/cache/memory.d.ts +0 -54
  22. package/dist/cache/memory.js +0 -15
  23. package/dist/cache/memory.js.map +0 -1
  24. package/dist/cache/noop.js +0 -9
  25. package/dist/cache/noop.js.map +0 -1
  26. package/dist/cache-DvbyemBK.d.ts +0 -33
  27. package/dist/chunk-4KGKWKKI.js +0 -80
  28. package/dist/chunk-4KGKWKKI.js.map +0 -1
  29. package/dist/chunk-6DG63XUF.js +0 -42
  30. package/dist/chunk-6DG63XUF.js.map +0 -1
  31. package/dist/chunk-6LHROEPI.js +0 -104
  32. package/dist/chunk-6LHROEPI.js.map +0 -1
  33. package/dist/chunk-V6ML4QE5.js +0 -26
  34. package/dist/chunk-V6ML4QE5.js.map +0 -1
  35. package/dist/content-Biqf0l_o.d.ts +0 -51
  36. package/dist/errors.d.ts +0 -30
  37. package/dist/errors.js +0 -11
  38. package/dist/errors.js.map +0 -1
  39. package/dist/hooks-B83RUclt.d.ts +0 -41
  40. package/dist/hooks.d.ts +0 -2
  41. package/dist/hooks.js +0 -9
  42. package/dist/hooks.js.map +0 -1
  43. package/dist/index.d.ts +0 -278
  44. package/dist/index.js +0 -598
  45. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -1,28 +1,78 @@
1
1
  # @notion-headless-cms/core
2
2
 
3
- CMS エンジン本体。データソース・キャッシュ・レンダラーを統合し、Stale-While-Revalidate / 更新検知 / クエリビルダー / フック / リトライを提供する。**外部ランタイム依存ゼロ**。
3
+ CMS エンジン本体。`createCMS` を提供し、SWR / 更新検知 / フック /
4
+ リトライ / Web Standard Route Handler を内蔵する。**外部ランタイム依存ゼロ**。
4
5
 
5
6
  ## インストール
6
7
 
7
8
  ```bash
8
- npm install @notion-headless-cms/core
9
+ pnpm add @notion-headless-cms/core @notion-headless-cms/notion-orm \
10
+ @notion-headless-cms/renderer @notionhq/client zod \
11
+ unified remark-parse remark-gfm remark-rehype rehype-stringify
9
12
  ```
10
13
 
11
- `core` 自体は外部ランタイム依存ゼロ。`@notion-headless-cms/renderer` `peerDependenciesMeta.optional: true` のため、renderer を `createCMS({ renderer })` で明示注入するか、動的フォールバック(`import("@notion-headless-cms/renderer")`)を使う場合のみインストールが必要。
14
+ `core` 自体は依存ゼロだが、実際に Notion を叩くには `notion-orm`、
15
+ HTML 化には `renderer` が必要。CLI 生成物 (`nhc-schema.ts`) が
16
+ notion-orm を自動参照する。
12
17
 
13
- Cloudflare Workers / Node.js / Next.js などで使う場合は、それぞれのアダプタを使うのが最短ルート:
18
+ ## 基本形
14
19
 
15
- - [`@notion-headless-cms/adapter-cloudflare`](../adapter-cloudflare)
16
- - [`@notion-headless-cms/adapter-node`](../adapter-node)
17
- - [`@notion-headless-cms/adapter-next`](../adapter-next)
20
+ ```ts
21
+ import { createCMS, nodePreset } from "@notion-headless-cms/core";
22
+ import { cmsDataSources } from "./generated/nhc-schema";
18
23
 
19
- ### サブパスエクスポート
24
+ const cms = createCMS({
25
+ ...nodePreset({ ttlMs: 5 * 60_000 }),
26
+ dataSources: cmsDataSources,
27
+ });
28
+
29
+ const posts = await cms.posts.getList();
30
+ const post = await cms.posts.getItem("my-post");
31
+ if (post) console.log(await post.content.html());
32
+ ```
20
33
 
21
- 一部の機能はサブパス経由でインポートでき、バレル経由より小さくバンドルできる。
34
+ ## 公開 API
35
+
36
+ ### `createCMS(opts)`
37
+ - `dataSources`: CLI が生成する `cmsDataSources` (必須)
38
+ - `cache?`: `CacheConfig | "disabled"`
39
+ - `renderer?`: `RendererFn` (未指定なら `@notion-headless-cms/renderer` を動的ロード)
40
+ - `content?`: `imageProxyBase` 等
41
+ - `waitUntil?`: Cloudflare Workers の `ctx.waitUntil`
42
+ - `hooks?` / `plugins?` / `logger?` / `rateLimiter?`
43
+
44
+ ### `nodePreset(opts?)`
45
+ Node.js 向けプリセット。`{ cache: { document: memoryDocumentCache, image: memoryImageCache }, renderer }` を返す。
46
+ - `cache?`: `CacheConfig | "disabled"` — デフォルトは memory cache
47
+ - `ttlMs?`: SWR TTL (ミリ秒)
48
+ - `renderer?`: カスタム renderer
49
+
50
+ ### `cms.<collection>` の主なメソッド
51
+ - `getItem(slug)` — 本文込みで単件取得 (SWR)。返り値は `T & { content: { blocks, html(), markdown() } }`
52
+ - `getList(opts?)` — 公開済み一覧 (本文なし)
53
+ - `getStaticParams()` / `getStaticPaths()` — SSG 用
54
+ - `adjacent(slug, opts?)` — 前後記事ナビゲーション
55
+ - `revalidate(scope?)` / `prefetch(opts?)` — コレクション別キャッシュ操作
56
+
57
+ ### グローバル操作
58
+ - `cms.$collections` — コレクション名一覧
59
+ - `cms.$revalidate(scope?)` — `"all" | { collection } | { collection, slug }`
60
+ - `cms.$handler(opts?)` — Web Standard `(req) => Response` を返す (画像プロキシ + webhook)
61
+ - `cms.$getCachedImage(hash)` — 画像キャッシュ直アクセス
62
+
63
+ ### cache アダプタ
64
+ - `memoryDocumentCache({ maxItems? })` — LRU 対応インメモリキャッシュ
65
+ - `memoryImageCache({ maxItems?, maxSizeBytes? })`
66
+ - `noopDocumentCache()` / `noopImageCache()`
67
+
68
+ ### エラー
69
+ - `CMSError` / `isCMSError` / `isCMSErrorInNamespace(err, "core/")`
70
+ - 名前空間: `core/*` / `source/*` / `cache/*` / `renderer/*` / `cli/*`
71
+
72
+ ## サブパスエクスポート
22
73
 
23
74
  ```ts
24
75
  import { CMSError } from "@notion-headless-cms/core/errors";
25
- import { mergeHooks } from "@notion-headless-cms/core/hooks";
26
76
  import { memoryDocumentCache } from "@notion-headless-cms/core/cache/memory";
27
77
  ```
28
78
 
@@ -31,173 +81,17 @@ import { memoryDocumentCache } from "@notion-headless-cms/core/cache/memory";
31
81
  | `@notion-headless-cms/core` | 全エクスポート |
32
82
  | `@notion-headless-cms/core/errors` | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` |
33
83
  | `@notion-headless-cms/core/hooks` | `mergeHooks` / `mergeLoggers` |
34
- | `@notion-headless-cms/core/cache/memory` | `memoryDocumentCache` / `memoryImageCache` / `*Options` |
84
+ | `@notion-headless-cms/core/cache/memory` | `memoryDocumentCache` / `memoryImageCache` |
35
85
  | `@notion-headless-cms/core/cache/noop` | `noopDocumentCache` / `noopImageCache` |
36
86
 
37
- ## 使い方(core を直接使う場合)
38
-
39
- ```typescript
40
- import { createCMS, memoryDocumentCache, memoryImageCache } from "@notion-headless-cms/core";
41
- import { notionAdapter } from "@notion-headless-cms/source-notion";
42
- import { renderMarkdown } from "@notion-headless-cms/renderer";
43
-
44
- const cms = createCMS({
45
- source: notionAdapter({
46
- token: process.env.NOTION_TOKEN!,
47
- dataSourceId: process.env.NOTION_DATA_SOURCE_ID!,
48
- }),
49
- renderer: renderMarkdown,
50
- schema: {
51
- publishedStatuses: ["公開"],
52
- properties: { slug: "Slug" },
53
- },
54
- cache: {
55
- document: memoryDocumentCache({ maxItems: 500 }),
56
- image: memoryImageCache({ maxItems: 200, maxSizeBytes: 64 * 1024 * 1024 }),
57
- ttlMs: 5 * 60 * 1000,
58
- },
59
- });
60
-
61
- // ソース直接
62
- const items = await cms.list();
63
- const item = await cms.find("my-post");
64
- const rendered = item ? await cms.render(item) : null;
65
-
66
- // SWR
67
- const { items: cachedItems } = await cms.cache.getList();
68
- const cached = await cms.cache.get("my-post");
69
- console.log(cached?.html);
70
- ```
71
-
72
- ### `CacheConfig`
73
-
74
- ```ts
75
- type CacheConfig<T> =
76
- | "disabled" // 完全無効化
77
- | {
78
- document?: DocumentCacheAdapter<T>; // 未指定なら noop
79
- image?: ImageCacheAdapter; // 未指定なら noop
80
- ttlMs?: number; // 未指定は TTL なし
81
- };
82
- ```
83
-
84
- ### カスタムコンテンツ型
85
-
86
- `BaseContentItem` を拡張することで任意のプロパティを追加できる。
87
-
88
- ```typescript
89
- import type { BaseContentItem } from "@notion-headless-cms/core";
90
- import { createCMS } from "@notion-headless-cms/core";
91
- import { notionAdapter } from "@notion-headless-cms/source-notion";
92
-
93
- interface MyPost extends BaseContentItem {
94
- title: string;
95
- category: string;
96
- }
97
-
98
- const cms = createCMS<MyPost>({
99
- source: notionAdapter<MyPost>({
100
- token: process.env.NOTION_TOKEN!,
101
- dataSourceId: process.env.NOTION_DATA_SOURCE_ID!,
102
- mapItem: (page) => ({
103
- id: page.id,
104
- slug: /* ... */ "",
105
- updatedAt: page.last_edited_time,
106
- publishedAt: page.created_time,
107
- title: /* ... */ "",
108
- category: /* ... */ "",
109
- }),
110
- }),
111
- });
112
- ```
113
-
114
- > 型安全なマッピングは [`defineSchema`](../source-notion) を推奨。
115
-
116
- ## 主要 API
117
-
118
- ### `CMS<T>` / `createCMS<T>(options)`
119
-
120
- | メソッド | 説明 |
121
- |---|---|
122
- | `list()` | ソースから一覧取得 |
123
- | `find(slug)` | ソースから単一アイテム取得 |
124
- | `findMany(slugs[])` | 複数スラッグを並列取得(`Map<string, T>` を返す) |
125
- | `render(item)` | Markdown → HTML にレンダリング |
126
- | `isPublished(item)` | `publishedStatuses` 判定 |
127
- | `cache.getList()` | SWR で一覧取得 |
128
- | `cache.get(slug)` | SWR で単一アイテム取得 |
129
- | `cache.prefetchAll(opts?)` | 全アイテムを事前レンダリング |
130
- | `cache.revalidate(scope?)` | キャッシュ無効化 |
131
- | `cache.sync(payload?)` | Webhook 由来のキャッシュ同期 |
132
- | `cache.checkList(version)` | 一覧差分検知 |
133
- | `cache.checkItem(slug, lastEdited)` | 個別差分検知 |
134
- | `query()` | QueryBuilder を返す |
135
- | `getStaticSlugs()` | 静的生成用スラッグ一覧 |
136
- | `getCachedImage(hash)` | キャッシュ画像を取得 |
137
- | `createCachedImageResponse(hash)` | キャッシュ画像の Response を生成 |
138
-
139
- 詳細は [CMS メソッド一覧](../../docs/api/cms-methods.md) を参照。
140
-
141
- ### ユーティリティ
142
-
143
- | エクスポート | 説明 |
144
- |---|---|
145
- | `memoryDocumentCache<T>({ maxItems? })` | インメモリ DocumentCacheAdapter。`maxItems` 指定時は LRU 方式で古いものから削除 |
146
- | `memoryImageCache({ maxItems?, maxSizeBytes? })` | インメモリ ImageCacheAdapter。件数とバイト数の両方で上限を設定可 |
147
- | `noopDocumentCache<T>()` / `noopImageCache()` | 何もしないアダプタ |
148
- | `isStale(cachedAt, ttlMs)` | TTL 切れ判定 |
149
- | `sha256Hex(data)` | SHA256 ハッシュ生成 |
150
- | `CMSError` / `isCMSError` / `isCMSErrorInNamespace` | エラークラスと判定ヘルパー |
151
- | `QueryBuilder` | クエリ組み立て(`cms.query()` が返す) |
152
- | `withRetry` / `DEFAULT_RETRY_CONFIG` | リトライ付き実行 |
153
- | `mergeHooks` / `mergeLoggers` | プラグイン・直接指定のフック/ロガーを統合 |
154
- | `definePlugin` | プラグイン(hooks / logger)の型付き定義 |
155
-
156
- ### ライフサイクルフック
157
-
158
- `createCMS({ hooks })` に指定する。観測フック(`onCache*` / `onRender*`)は例外を投げても他のフックに波及せず、`logger.error` に流される。
159
-
160
- | フック | 呼び出しタイミング |
161
- |---|---|
162
- | `beforeCache(item)` | キャッシュ書き込み直前(結果差し替え可) |
163
- | `afterRender(html, item)` | HTML 生成直後(差し替え可) |
164
- | `onCacheHit(slug, item)` / `onCacheMiss(slug)` | アイテム SWR のヒット・ミス |
165
- | `onListCacheHit(items, cachedAt)` / `onListCacheMiss()` | 一覧 SWR のヒット・ミス |
166
- | `onRenderStart(slug)` / `onRenderEnd(slug, durationMs)` | レンダリング所要時間の観測 |
167
- | `onError(error)` | 内部エラー通知 |
168
-
169
- ## 主要な型
170
-
171
- - `CreateCMSOptions<T>` — `createCMS()` の引数
172
- - `BaseContentItem` — デフォルト・カスタム型の基底(`status` / `publishedAt` はオプション)
173
- - `CachedItem<T>` / `CachedItemList<T>` — キャッシュ済みコンテンツ
174
- - `CacheAccessor<T>` — `cms.cache` の型(外部で型引数として使いたい場合に)
175
- - `DataSourceAdapter<T>` — データソース抽象
176
- - `DocumentCacheAdapter<T>` / `ImageCacheAdapter` — キャッシュ抽象
177
- - `RendererFn` / `RenderOptions` — レンダラー関数の型
178
- - `CMSErrorCode` / `CMSErrorContext` — エラー型(名前空間付き)
179
-
180
- ## エラー体系
181
-
182
- すべての内部エラーは `CMSError` に統一される。コードは `namespace/kind` 形式。
183
-
184
- ```ts
185
- import { isCMSErrorInNamespace } from "@notion-headless-cms/core";
186
-
187
- try {
188
- await cms.list();
189
- } catch (err) {
190
- if (isCMSErrorInNamespace(err, "source/")) {
191
- // Notion 取得系エラー
192
- }
193
- }
194
- ```
87
+ ## ランタイム別レシピ
195
88
 
196
- 組み込みコード: `core/config_invalid` / `core/schema_invalid` / `source/fetch_items_failed` / `source/fetch_item_failed` / `source/load_markdown_failed` / `cache/io_failed` / `cache/image_fetch_failed` / `renderer/failed`
89
+ - [Node.js スクリプト](../../docs/recipes/nodejs-script.md)
90
+ - [Cloudflare Workers + R2 + KV](../../docs/recipes/cloudflare-workers.md) (`cloudflarePreset` は `cache-r2` パッケージ)
91
+ - [Next.js App Router](../../docs/recipes/nextjs-app-router.md)
197
92
 
198
- ## 関連パッケージ
93
+ ## 詳細ドキュメント
199
94
 
200
- - [`@notion-headless-cms/source-notion`](../source-notion) — Notion データソース
201
- - [`@notion-headless-cms/renderer`](../renderer) — Markdown → HTML レンダラー
202
- - [`@notion-headless-cms/cache-r2`](../cache-r2) — R2 キャッシュ
203
- - [`@notion-headless-cms/cache-next`](../cache-next) — Next.js ISR キャッシュ
95
+ - [クイックスタート](../../docs/quickstart.md)
96
+ - [CMS メソッド一覧](../../docs/api/cms-methods.md)
97
+ - [v0.2 → v0.3 移行ガイド](../../docs/migration/v0.3.md)
@@ -0,0 +1,52 @@
1
+ import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "../content-BrwEY2_p.mjs";
2
+ import { l as InvalidateScope, n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-Av7HRw_s.mjs";
3
+
4
+ //#region src/cache/memory.d.ts
5
+ interface MemoryDocumentCacheOptions {
6
+ /** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
7
+ maxItems?: number;
8
+ }
9
+ interface MemoryImageCacheOptions {
10
+ /** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
11
+ maxItems?: number;
12
+ /** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */
13
+ maxSizeBytes?: number;
14
+ }
15
+ /** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
16
+ declare class MemoryDocumentCache<T extends BaseContentItem = BaseContentItem> implements DocumentCacheAdapter<T> {
17
+ readonly name = "memory-document";
18
+ private list;
19
+ private items;
20
+ private readonly maxItems;
21
+ constructor(options?: MemoryDocumentCacheOptions);
22
+ getList(): Promise<CachedItemList<T> | null>;
23
+ setList(data: CachedItemList<T>): Promise<void>;
24
+ getItem(slug: string): Promise<CachedItem<T> | null>;
25
+ setItem(slug: string, data: CachedItem<T>): Promise<void>;
26
+ invalidate(scope: InvalidateScope): Promise<void>;
27
+ private enforceLimit;
28
+ }
29
+ /** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
30
+ declare class MemoryImageCache implements ImageCacheAdapter {
31
+ readonly name = "memory-image";
32
+ private store;
33
+ private totalBytes;
34
+ private readonly maxItems;
35
+ private readonly maxSizeBytes;
36
+ constructor(options?: MemoryImageCacheOptions);
37
+ get(hash: string): Promise<StorageBinary | null>;
38
+ set(hash: string, data: ArrayBuffer, contentType: string): Promise<void>;
39
+ private enforceLimit;
40
+ }
41
+ /** インメモリキャッシュ(ドキュメント用)を生成する。 */
42
+ declare function memoryDocumentCache<T extends BaseContentItem = BaseContentItem>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T>;
43
+ /** インメモリキャッシュ(画像用)を生成する。 */
44
+ declare function memoryImageCache(options?: MemoryImageCacheOptions): ImageCacheAdapter;
45
+ /**
46
+ * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。
47
+ * memoryCache() はドキュメントキャッシュを返す(後方互換)。
48
+ */
49
+ declare function memoryCache<T extends BaseContentItem = BaseContentItem>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T>;
50
+ //#endregion
51
+ export { MemoryDocumentCache, MemoryDocumentCacheOptions, MemoryImageCache, MemoryImageCacheOptions, memoryCache, memoryDocumentCache, memoryImageCache };
52
+ //# sourceMappingURL=memory.d.mts.map
@@ -0,0 +1,115 @@
1
+ //#region src/cache/memory.ts
2
+ /**
3
+ * Map の挿入順を LRU として扱う軽量実装。
4
+ * `touch` で既存キーを末尾に移動、`enforceLimit` で古いキー(先頭)から削除する。
5
+ */
6
+ function touch(map, key) {
7
+ const v = map.get(key);
8
+ if (v === void 0) return;
9
+ map.delete(key);
10
+ map.set(key, v);
11
+ }
12
+ /** インメモリのドキュメントキャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
13
+ var MemoryDocumentCache = class {
14
+ name = "memory-document";
15
+ list = null;
16
+ items = /* @__PURE__ */ new Map();
17
+ maxItems;
18
+ constructor(options) {
19
+ this.maxItems = options?.maxItems;
20
+ }
21
+ getList() {
22
+ return Promise.resolve(this.list);
23
+ }
24
+ setList(data) {
25
+ this.list = data;
26
+ return Promise.resolve();
27
+ }
28
+ getItem(slug) {
29
+ const entry = this.items.get(slug);
30
+ if (entry) touch(this.items, slug);
31
+ return Promise.resolve(entry ?? null);
32
+ }
33
+ setItem(slug, data) {
34
+ if (this.items.has(slug)) this.items.delete(slug);
35
+ this.items.set(slug, data);
36
+ this.enforceLimit();
37
+ return Promise.resolve();
38
+ }
39
+ async invalidate(scope) {
40
+ if (scope === "all") {
41
+ this.list = null;
42
+ this.items.clear();
43
+ return;
44
+ }
45
+ this.list = null;
46
+ if ("slug" in scope) this.items.delete(scope.slug);
47
+ }
48
+ enforceLimit() {
49
+ if (this.maxItems === void 0) return;
50
+ while (this.items.size > this.maxItems) {
51
+ const firstKey = this.items.keys().next().value;
52
+ if (firstKey === void 0) break;
53
+ this.items.delete(firstKey);
54
+ }
55
+ }
56
+ };
57
+ /** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */
58
+ var MemoryImageCache = class {
59
+ name = "memory-image";
60
+ store = /* @__PURE__ */ new Map();
61
+ totalBytes = 0;
62
+ maxItems;
63
+ maxSizeBytes;
64
+ constructor(options) {
65
+ this.maxItems = options?.maxItems;
66
+ this.maxSizeBytes = options?.maxSizeBytes;
67
+ }
68
+ get(hash) {
69
+ const entry = this.store.get(hash);
70
+ if (entry) touch(this.store, hash);
71
+ return Promise.resolve(entry ?? null);
72
+ }
73
+ set(hash, data, contentType) {
74
+ const existing = this.store.get(hash);
75
+ if (existing) {
76
+ this.totalBytes -= existing.data.byteLength;
77
+ this.store.delete(hash);
78
+ }
79
+ this.store.set(hash, {
80
+ data,
81
+ contentType
82
+ });
83
+ this.totalBytes += data.byteLength;
84
+ this.enforceLimit();
85
+ return Promise.resolve();
86
+ }
87
+ enforceLimit() {
88
+ while (this.maxItems !== void 0 && this.store.size > this.maxItems || this.maxSizeBytes !== void 0 && this.totalBytes > this.maxSizeBytes) {
89
+ const firstKey = this.store.keys().next().value;
90
+ if (firstKey === void 0) break;
91
+ const victim = this.store.get(firstKey);
92
+ if (victim) this.totalBytes -= victim.data.byteLength;
93
+ this.store.delete(firstKey);
94
+ }
95
+ }
96
+ };
97
+ /** インメモリキャッシュ(ドキュメント用)を生成する。 */
98
+ function memoryDocumentCache(options) {
99
+ return new MemoryDocumentCache(options);
100
+ }
101
+ /** インメモリキャッシュ(画像用)を生成する。 */
102
+ function memoryImageCache(options) {
103
+ return new MemoryImageCache(options);
104
+ }
105
+ /**
106
+ * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。
107
+ * memoryCache() はドキュメントキャッシュを返す(後方互換)。
108
+ */
109
+ function memoryCache(options) {
110
+ return new MemoryDocumentCache(options);
111
+ }
112
+ //#endregion
113
+ export { MemoryDocumentCache, MemoryImageCache, memoryCache, memoryDocumentCache, memoryImageCache };
114
+
115
+ //# sourceMappingURL=memory.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.mjs","names":[],"sources":["../../src/cache/memory.ts"],"sourcesContent":["import type {\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\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// { collection } または { collection, slug }。単一コレクション想定のため\n\t\t// list を必ず破棄し、slug 指定があれば該当 item も削除する\n\t\tthis.list = null;\n\t\tif (\"slug\" in scope) {\n\t\t\tthis.items.delete(scope.slug);\n\t\t}\n\t}\n\n\tprivate enforceLimit(): void {\n\t\tif (this.maxItems === undefined) return;\n\t\twhile (this.items.size > this.maxItems) {\n\t\t\tconst firstKey = this.items.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tthis.items.delete(firstKey);\n\t\t}\n\t}\n}\n\n/** インメモリの画像キャッシュ実装。プロセス再起動でクリアされる。ローカル開発向け。 */\nexport class MemoryImageCache implements ImageCacheAdapter {\n\treadonly name = \"memory-image\";\n\tprivate store = new Map<string, StorageBinary>();\n\tprivate totalBytes = 0;\n\tprivate readonly maxItems: number | undefined;\n\tprivate readonly maxSizeBytes: number | undefined;\n\n\tconstructor(options?: MemoryImageCacheOptions) {\n\t\tthis.maxItems = options?.maxItems;\n\t\tthis.maxSizeBytes = options?.maxSizeBytes;\n\t}\n\n\tget(hash: string): Promise<StorageBinary | null> {\n\t\tconst entry = this.store.get(hash);\n\t\tif (entry) touch(this.store, hash);\n\t\treturn Promise.resolve(entry ?? null);\n\t}\n\n\tset(hash: string, data: ArrayBuffer, contentType: string): Promise<void> {\n\t\tconst existing = this.store.get(hash);\n\t\tif (existing) {\n\t\t\tthis.totalBytes -= existing.data.byteLength;\n\t\t\tthis.store.delete(hash);\n\t\t}\n\t\tthis.store.set(hash, { data, contentType });\n\t\tthis.totalBytes += data.byteLength;\n\t\tthis.enforceLimit();\n\t\treturn Promise.resolve();\n\t}\n\n\tprivate enforceLimit(): void {\n\t\twhile (\n\t\t\t(this.maxItems !== undefined && this.store.size > this.maxItems) ||\n\t\t\t(this.maxSizeBytes !== undefined && this.totalBytes > this.maxSizeBytes)\n\t\t) {\n\t\t\tconst firstKey = this.store.keys().next().value;\n\t\t\tif (firstKey === undefined) break;\n\t\t\tconst victim = this.store.get(firstKey);\n\t\t\tif (victim) this.totalBytes -= victim.data.byteLength;\n\t\t\tthis.store.delete(firstKey);\n\t\t}\n\t}\n}\n\n/** インメモリキャッシュ(ドキュメント用)を生成する。 */\nexport function memoryDocumentCache<\n\tT extends BaseContentItem = BaseContentItem,\n>(options?: MemoryDocumentCacheOptions): DocumentCacheAdapter<T> {\n\treturn new MemoryDocumentCache<T>(options);\n}\n\n/** インメモリキャッシュ(画像用)を生成する。 */\nexport function memoryImageCache(\n\toptions?: MemoryImageCacheOptions,\n): ImageCacheAdapter {\n\treturn new MemoryImageCache(options);\n}\n\n/**\n * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。\n * memoryCache() はドキュメントキャッシュを返す(後方互換)。\n */\nexport function memoryCache<T extends BaseContentItem = BaseContentItem>(\n\toptions?: MemoryDocumentCacheOptions,\n): DocumentCacheAdapter<T> {\n\treturn new MemoryDocumentCache<T>(options);\n}\n"],"mappings":";;;;;AA0BA,SAAS,MAAY,KAAgB,KAAc;CAClD,MAAM,IAAI,IAAI,IAAI,IAAI;AACtB,KAAI,MAAM,KAAA,EAAW;AACrB,KAAI,OAAO,IAAI;AACf,KAAI,IAAI,KAAK,EAAE;;;AAIhB,IAAa,sBAAb,MAEA;CACC,OAAgB;CAChB,OAAyC;CACzC,wBAAgB,IAAI,KAA4B;CAChD;CAEA,YAAY,SAAsC;AACjD,OAAK,WAAW,SAAS;;CAG1B,UAA6C;AAC5C,SAAO,QAAQ,QAAQ,KAAK,KAAK;;CAGlC,QAAQ,MAAwC;AAC/C,OAAK,OAAO;AACZ,SAAO,QAAQ,SAAS;;CAGzB,QAAQ,MAA6C;EACpD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;AAClC,MAAI,MAAO,OAAM,KAAK,OAAO,KAAK;AAClC,SAAO,QAAQ,QAAQ,SAAS,KAAK;;CAGtC,QAAQ,MAAc,MAAoC;AACzD,MAAI,KAAK,MAAM,IAAI,KAAK,CAAE,MAAK,MAAM,OAAO,KAAK;AACjD,OAAK,MAAM,IAAI,MAAM,KAAK;AAC1B,OAAK,cAAc;AACnB,SAAO,QAAQ,SAAS;;CAGzB,MAAM,WAAW,OAAuC;AACvD,MAAI,UAAU,OAAO;AACpB,QAAK,OAAO;AACZ,QAAK,MAAM,OAAO;AAClB;;AAID,OAAK,OAAO;AACZ,MAAI,UAAU,MACb,MAAK,MAAM,OAAO,MAAM,KAAK;;CAI/B,eAA6B;AAC5B,MAAI,KAAK,aAAa,KAAA,EAAW;AACjC,SAAO,KAAK,MAAM,OAAO,KAAK,UAAU;GACvC,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,aAAa,KAAA,EAAW;AAC5B,QAAK,MAAM,OAAO,SAAS;;;;;AAM9B,IAAa,mBAAb,MAA2D;CAC1D,OAAgB;CAChB,wBAAgB,IAAI,KAA4B;CAChD,aAAqB;CACrB;CACA;CAEA,YAAY,SAAmC;AAC9C,OAAK,WAAW,SAAS;AACzB,OAAK,eAAe,SAAS;;CAG9B,IAAI,MAA6C;EAChD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;AAClC,MAAI,MAAO,OAAM,KAAK,OAAO,KAAK;AAClC,SAAO,QAAQ,QAAQ,SAAS,KAAK;;CAGtC,IAAI,MAAc,MAAmB,aAAoC;EACxE,MAAM,WAAW,KAAK,MAAM,IAAI,KAAK;AACrC,MAAI,UAAU;AACb,QAAK,cAAc,SAAS,KAAK;AACjC,QAAK,MAAM,OAAO,KAAK;;AAExB,OAAK,MAAM,IAAI,MAAM;GAAE;GAAM;GAAa,CAAC;AAC3C,OAAK,cAAc,KAAK;AACxB,OAAK,cAAc;AACnB,SAAO,QAAQ,SAAS;;CAGzB,eAA6B;AAC5B,SACE,KAAK,aAAa,KAAA,KAAa,KAAK,MAAM,OAAO,KAAK,YACtD,KAAK,iBAAiB,KAAA,KAAa,KAAK,aAAa,KAAK,cAC1D;GACD,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC,MAAM,CAAC;AAC1C,OAAI,aAAa,KAAA,EAAW;GAC5B,MAAM,SAAS,KAAK,MAAM,IAAI,SAAS;AACvC,OAAI,OAAQ,MAAK,cAAc,OAAO,KAAK;AAC3C,QAAK,MAAM,OAAO,SAAS;;;;;AAM9B,SAAgB,oBAEd,SAA+D;AAChE,QAAO,IAAI,oBAAuB,QAAQ;;;AAI3C,SAAgB,iBACf,SACoB;AACpB,QAAO,IAAI,iBAAiB,QAAQ;;;;;;AAOrC,SAAgB,YACf,SAC0B;AAC1B,QAAO,IAAI,oBAAuB,QAAQ"}
@@ -1,9 +1,11 @@
1
- import { D as DocumentCacheAdapter, I as ImageCacheAdapter } from '../cache-DvbyemBK.js';
2
- import { B as BaseContentItem } from '../content-Biqf0l_o.js';
1
+ import { t as BaseContentItem } from "../content-BrwEY2_p.mjs";
2
+ import { n as DocumentCacheAdapter, r as ImageCacheAdapter } from "../cache-Av7HRw_s.mjs";
3
3
 
4
+ //#region src/cache/noop.d.ts
4
5
  /** 何もしないドキュメントキャッシュを返す(シングルトン)。 */
5
6
  declare function noopDocumentCache<T extends BaseContentItem = BaseContentItem>(): DocumentCacheAdapter<T>;
6
7
  /** 何もしない画像キャッシュを返す(シングルトン)。 */
7
8
  declare function noopImageCache(): ImageCacheAdapter;
8
-
9
+ //#endregion
9
10
  export { noopDocumentCache, noopImageCache };
11
+ //# sourceMappingURL=noop.d.mts.map
@@ -0,0 +1,44 @@
1
+ //#region src/cache/noop.ts
2
+ /** 何もキャッシュしないドキュメントキャッシュ実装。常に null を返す。 */
3
+ var NoopDocumentCache = class {
4
+ name = "noop-document";
5
+ getList() {
6
+ return Promise.resolve(null);
7
+ }
8
+ setList(_data) {
9
+ return Promise.resolve();
10
+ }
11
+ getItem(_slug) {
12
+ return Promise.resolve(null);
13
+ }
14
+ setItem(_slug, _data) {
15
+ return Promise.resolve();
16
+ }
17
+ invalidate(_scope) {
18
+ return Promise.resolve();
19
+ }
20
+ };
21
+ /** 何もキャッシュしない画像キャッシュ実装。常に null を返す。 */
22
+ var NoopImageCache = class {
23
+ name = "noop-image";
24
+ get(_hash) {
25
+ return Promise.resolve(null);
26
+ }
27
+ set(_hash, _data, _contentType) {
28
+ return Promise.resolve();
29
+ }
30
+ };
31
+ const _noopDocument = new NoopDocumentCache();
32
+ const _noopImage = new NoopImageCache();
33
+ /** 何もしないドキュメントキャッシュを返す(シングルトン)。 */
34
+ function noopDocumentCache() {
35
+ return _noopDocument;
36
+ }
37
+ /** 何もしない画像キャッシュを返す(シングルトン)。 */
38
+ function noopImageCache() {
39
+ return _noopImage;
40
+ }
41
+ //#endregion
42
+ export { noopDocumentCache, noopImageCache };
43
+
44
+ //# sourceMappingURL=noop.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noop.mjs","names":[],"sources":["../../src/cache/noop.ts"],"sourcesContent":["import type {\n\tBaseContentItem,\n\tCachedItem,\n\tCachedItemList,\n\tDocumentCacheAdapter,\n\tImageCacheAdapter,\n\tInvalidateScope,\n\tStorageBinary,\n} from \"../types/index\";\n\n/** 何もキャッシュしないドキュメントキャッシュ実装。常に null を返す。 */\nclass NoopDocumentCache<T extends BaseContentItem = BaseContentItem>\n\timplements DocumentCacheAdapter<T>\n{\n\treadonly name = \"noop-document\";\n\n\tgetList(): Promise<CachedItemList<T> | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tsetList(_data: CachedItemList<T>): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tgetItem(_slug: string): Promise<CachedItem<T> | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tsetItem(_slug: string, _data: CachedItem<T>): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n\n\tinvalidate(_scope: InvalidateScope): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n\n/** 何もキャッシュしない画像キャッシュ実装。常に null を返す。 */\nclass NoopImageCache implements ImageCacheAdapter {\n\treadonly name = \"noop-image\";\n\n\tget(_hash: string): Promise<StorageBinary | null> {\n\t\treturn Promise.resolve(null);\n\t}\n\n\tset(_hash: string, _data: ArrayBuffer, _contentType: string): Promise<void> {\n\t\treturn Promise.resolve();\n\t}\n}\n\nconst _noopDocument = new NoopDocumentCache();\nconst _noopImage = new NoopImageCache();\n\n/** 何もしないドキュメントキャッシュを返す(シングルトン)。 */\nexport function noopDocumentCache<\n\tT extends BaseContentItem = BaseContentItem,\n>(): DocumentCacheAdapter<T> {\n\treturn _noopDocument as DocumentCacheAdapter<T>;\n}\n\n/** 何もしない画像キャッシュを返す(シングルトン)。 */\nexport function noopImageCache(): ImageCacheAdapter {\n\treturn _noopImage;\n}\n"],"mappings":";;AAWA,IAAM,oBAAN,MAEA;CACC,OAAgB;CAEhB,UAA6C;AAC5C,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,QAAQ,OAAyC;AAChD,SAAO,QAAQ,SAAS;;CAGzB,QAAQ,OAA8C;AACrD,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,QAAQ,OAAe,OAAqC;AAC3D,SAAO,QAAQ,SAAS;;CAGzB,WAAW,QAAwC;AAClD,SAAO,QAAQ,SAAS;;;;AAK1B,IAAM,iBAAN,MAAkD;CACjD,OAAgB;CAEhB,IAAI,OAA8C;AACjD,SAAO,QAAQ,QAAQ,KAAK;;CAG7B,IAAI,OAAe,OAAoB,cAAqC;AAC3E,SAAO,QAAQ,SAAS;;;AAI1B,MAAM,gBAAgB,IAAI,mBAAmB;AAC7C,MAAM,aAAa,IAAI,gBAAgB;;AAGvC,SAAgB,oBAEa;AAC5B,QAAO;;;AAIR,SAAgB,iBAAoC;AACnD,QAAO"}