@notion-headless-cms/core 0.1.0 → 0.1.2

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.
@@ -0,0 +1,26 @@
1
+ // src/errors.ts
2
+ var CMSError = class extends Error {
3
+ code;
4
+ cause;
5
+ context;
6
+ constructor(params) {
7
+ super(params.message, { cause: params.cause });
8
+ this.name = "CMSError";
9
+ this.code = params.code;
10
+ this.cause = params.cause;
11
+ this.context = params.context;
12
+ }
13
+ };
14
+ function isCMSError(error) {
15
+ return error instanceof CMSError;
16
+ }
17
+ function isCMSErrorInNamespace(error, namespace) {
18
+ return isCMSError(error) && error.code.startsWith(namespace);
19
+ }
20
+
21
+ export {
22
+ CMSError,
23
+ isCMSError,
24
+ isCMSErrorInNamespace
25
+ };
26
+ //# sourceMappingURL=chunk-V6ML4QE5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["type BuiltInCMSErrorCode =\n\t| \"core/config_invalid\"\n\t| \"core/schema_invalid\"\n\t| \"source/fetch_items_failed\"\n\t| \"source/fetch_item_failed\"\n\t| \"source/load_markdown_failed\"\n\t| \"cache/io_failed\"\n\t| \"cache/image_fetch_failed\"\n\t| \"renderer/failed\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n\toperation: string;\n\tslug?: string;\n\tdataSourceId?: string;\n\tpageId?: string;\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\nexport class CMSError extends Error {\n\treadonly code: CMSErrorCode;\n\toverride readonly cause?: unknown;\n\treadonly context: CMSErrorContext;\n\n\tconstructor(params: {\n\t\tcode: CMSErrorCode;\n\t\tmessage: string;\n\t\tcause?: unknown;\n\t\tcontext: CMSErrorContext;\n\t}) {\n\t\tsuper(params.message, { cause: params.cause });\n\t\tthis.name = \"CMSError\";\n\t\tthis.code = params.code;\n\t\tthis.cause = params.cause;\n\t\tthis.context = params.context;\n\t}\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n\treturn error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n\terror: unknown,\n\tnamespace: string,\n): error is CMSError {\n\treturn isCMSError(error) && error.code.startsWith(namespace);\n}\n"],"mappings":";AAyBO,IAAM,WAAN,cAAuB,MAAM;AAAA,EAC1B;AAAA,EACS;AAAA,EACT;AAAA,EAET,YAAY,QAKT;AACF,UAAM,OAAO,SAAS,EAAE,OAAO,OAAO,MAAM,CAAC;AAC7C,SAAK,OAAO;AACZ,SAAK,OAAO,OAAO;AACnB,SAAK,QAAQ,OAAO;AACpB,SAAK,UAAU,OAAO;AAAA,EACvB;AACD;AAEO,SAAS,WAAW,OAAmC;AAC7D,SAAO,iBAAiB;AACzB;AAGO,SAAS,sBACf,OACA,WACoB;AACpB,SAAO,WAAW,KAAK,KAAK,MAAM,KAAK,WAAW,SAAS;AAC5D;","names":[]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * ライブラリが動作するために必須なフィールド。
3
+ * 利用者はこのインターフェースを拡張して独自のコンテンツ型を定義する。
4
+ *
5
+ * @example
6
+ * interface Post extends BaseContentItem {
7
+ * title: string;
8
+ * author: string;
9
+ * }
10
+ * createCMS<Post>({ source: notionAdapter({ ... }) })
11
+ */
12
+ interface BaseContentItem {
13
+ /** Notion ページ ID(変更検知に必須)。 */
14
+ id: string;
15
+ /** URL キー(必須)。 */
16
+ slug: string;
17
+ /** 最終更新タイムスタンプ(変更検知に必須)。 */
18
+ updatedAt: string;
19
+ /** コンテンツのステータス。ステータスのない DB では省略可能。 */
20
+ status?: string;
21
+ /** 公開日時。日付プロパティのない DB では省略可能。 */
22
+ publishedAt?: string;
23
+ }
24
+ /** ストレージにキャッシュされたレンダリング済みコンテンツ。 */
25
+ interface CachedItem<T extends BaseContentItem = BaseContentItem> {
26
+ html: string;
27
+ item: T;
28
+ notionUpdatedAt: string;
29
+ cachedAt: number;
30
+ }
31
+ /** ストレージにキャッシュされたコンテンツ一覧。 */
32
+ interface CachedItemList<T extends BaseContentItem = BaseContentItem> {
33
+ items: T[];
34
+ cachedAt: number;
35
+ }
36
+ /** ストレージから取得したバイナリオブジェクト。 */
37
+ interface StorageBinary {
38
+ data: ArrayBuffer;
39
+ contentType?: string;
40
+ }
41
+ /** Notionのプロパティ名マッピング(すべてオプション)。 */
42
+ interface CMSSchemaProperties {
43
+ /** Notionのスラッグプロパティ名。デフォルト: 'Slug' */
44
+ slug?: string;
45
+ /** Notionのステータスプロパティ名。デフォルト: 'Status' */
46
+ status?: string;
47
+ /** Notionの公開日プロパティ名。デフォルト: 'CreatedAt' */
48
+ date?: string;
49
+ }
50
+
51
+ export type { BaseContentItem as B, CMSSchemaProperties as C, StorageBinary as S, CachedItem as a, CachedItemList as b };
@@ -0,0 +1,30 @@
1
+ type BuiltInCMSErrorCode = "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";
2
+ /**
3
+ * CMS エラーコード。
4
+ * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、
5
+ * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。
6
+ */
7
+ type CMSErrorCode = BuiltInCMSErrorCode | (string & {});
8
+ interface CMSErrorContext {
9
+ operation: string;
10
+ slug?: string;
11
+ dataSourceId?: string;
12
+ pageId?: string;
13
+ [key: string]: string | number | boolean | null | undefined;
14
+ }
15
+ declare class CMSError extends Error {
16
+ readonly code: CMSErrorCode;
17
+ readonly cause?: unknown;
18
+ readonly context: CMSErrorContext;
19
+ constructor(params: {
20
+ code: CMSErrorCode;
21
+ message: string;
22
+ cause?: unknown;
23
+ context: CMSErrorContext;
24
+ });
25
+ }
26
+ declare function isCMSError(error: unknown): error is CMSError;
27
+ /** エラーコードが特定の名前空間に属するかを判定する(例: "source/")。 */
28
+ declare function isCMSErrorInNamespace(error: unknown, namespace: string): error is CMSError;
29
+
30
+ export { CMSError, type CMSErrorCode, type CMSErrorContext, isCMSError, isCMSErrorInNamespace };
package/dist/errors.js ADDED
@@ -0,0 +1,11 @@
1
+ import {
2
+ CMSError,
3
+ isCMSError,
4
+ isCMSErrorInNamespace
5
+ } from "./chunk-V6ML4QE5.js";
6
+ export {
7
+ CMSError,
8
+ isCMSError,
9
+ isCMSErrorInNamespace
10
+ };
11
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,41 @@
1
+ import { B as BaseContentItem, a as CachedItem } from './content-Biqf0l_o.js';
2
+
3
+ type MaybePromise<T> = T | Promise<T>;
4
+ interface CMSHooks<T extends BaseContentItem = BaseContentItem> {
5
+ beforeCache?: (item: CachedItem<T>) => MaybePromise<CachedItem<T>>;
6
+ afterRender?: (html: string, item: T) => MaybePromise<string>;
7
+ onCacheHit?: (slug: string, item: CachedItem<T>) => void;
8
+ onCacheMiss?: (slug: string) => void;
9
+ onListCacheHit?: (items: T[], cachedAt: number) => void;
10
+ onListCacheMiss?: () => void;
11
+ onError?: (error: Error) => void;
12
+ onRenderStart?: (slug: string) => void;
13
+ onRenderEnd?: (slug: string, durationMs: number) => void;
14
+ }
15
+
16
+ interface Logger {
17
+ debug?: (message: string, context?: Record<string, unknown>) => void;
18
+ info?: (message: string, context?: Record<string, unknown>) => void;
19
+ warn?: (message: string, context?: Record<string, unknown>) => void;
20
+ error?: (message: string, context?: Record<string, unknown>) => void;
21
+ }
22
+
23
+ interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {
24
+ name: string;
25
+ hooks?: CMSHooks<T>;
26
+ logger?: Partial<Logger>;
27
+ }
28
+ declare function definePlugin<T extends BaseContentItem>(plugin: CMSPlugin<T>): CMSPlugin<T>;
29
+
30
+ /**
31
+ * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。
32
+ * beforeCache / afterRender はパイプライン(前の出力が次の入力)。
33
+ * onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
34
+ */
35
+ declare function mergeHooks<T extends BaseContentItem>(plugins: CMSPlugin<T>[], directHooks?: CMSHooks<T>, logger?: Logger): CMSHooks<T>;
36
+ /** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */
37
+ declare function mergeLoggers(plugins: Array<{
38
+ logger?: Partial<Logger>;
39
+ }>, directLogger?: Logger): Logger | undefined;
40
+
41
+ export { type CMSHooks as C, type Logger as L, type MaybePromise as M, type CMSPlugin as a, mergeLoggers as b, definePlugin as d, mergeHooks as m };
@@ -0,0 +1,2 @@
1
+ import './content-Biqf0l_o.js';
2
+ export { m as mergeHooks, b as mergeLoggers } from './hooks-B83RUclt.js';
package/dist/hooks.js ADDED
@@ -0,0 +1,9 @@
1
+ import {
2
+ mergeHooks,
3
+ mergeLoggers
4
+ } from "./chunk-4KGKWKKI.js";
5
+ export {
6
+ mergeHooks,
7
+ mergeLoggers
8
+ };
9
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,13 @@
1
- import { RendererFn } from '@notion-headless-cms/renderer';
2
- import { BlockHandler } from '@notion-headless-cms/transformer';
3
- import { PageObjectResponse, RichTextItemResponse } from '@notionhq/client/build/src/api-endpoints';
4
- import { PluggableList } from 'unified';
1
+ export { MemoryDocumentCacheOptions, MemoryImageCacheOptions, memoryCache, memoryDocumentCache, memoryImageCache } from './cache/memory.js';
2
+ export { noopDocumentCache, noopImageCache } from './cache/noop.js';
3
+ import { B as BaseContentItem, C as CMSSchemaProperties, a as CachedItem, S as StorageBinary } from './content-Biqf0l_o.js';
4
+ export { b as CachedItemList } from './content-Biqf0l_o.js';
5
+ import { PluggableList } from '@notion-headless-cms/renderer';
6
+ import { C as CacheConfig } from './cache-DvbyemBK.js';
7
+ export { D as DocumentCacheAdapter, I as ImageCacheAdapter } from './cache-DvbyemBK.js';
8
+ import { C as CMSHooks, a as CMSPlugin, L as Logger } from './hooks-B83RUclt.js';
9
+ export { M as MaybePromise, d as definePlugin, m as mergeHooks, b as mergeLoggers } from './hooks-B83RUclt.js';
10
+ export { CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace } from './errors.js';
5
11
 
6
12
  /** 文字列をSHA-256でハッシュ化し、16進数文字列として返す。画像キーの生成に使用。 */
7
13
  declare function sha256Hex(input: string): Promise<string>;
@@ -11,78 +17,26 @@ declare function sha256Hex(input: string): Promise<string>;
11
17
  */
12
18
  declare function isStale(cachedAt: number, ttlMs?: number): boolean;
13
19
 
14
- /**
15
- * ライブラリが動作するために必須なフィールド。
16
- * 利用者はこのインターフェースを拡張して独自のコンテンツ型を定義する。
17
- *
18
- * @example
19
- * interface Post extends BaseContentItem {
20
- * title: string;
21
- * author: string;
22
- * }
23
- * createCMS<Post>({ source: notionAdapter({ ... }) })
24
- */
25
- interface BaseContentItem {
26
- id: string;
27
- slug: string;
28
- status: string;
29
- publishedAt: string;
30
- updatedAt: string;
20
+ /** ソース側クエリのフィルタ・ソート条件。 */
21
+ interface SourceQueryOptions {
22
+ filter?: {
23
+ statuses?: string[];
24
+ tags?: string[];
25
+ [key: string]: unknown;
26
+ };
27
+ sort?: {
28
+ property: string;
29
+ direction: "asc" | "desc";
30
+ }[];
31
+ pageSize?: number;
32
+ cursor?: string;
31
33
  }
32
- /** ストレージにキャッシュされたレンダリング済みコンテンツ。 */
33
- interface CachedItem<T extends BaseContentItem = BaseContentItem> {
34
- html: string;
35
- item: T;
36
- notionUpdatedAt: string;
37
- cachedAt: number;
38
- }
39
- /** ストレージにキャッシュされたコンテンツ一覧。 */
40
- interface CachedItemList<T extends BaseContentItem = BaseContentItem> {
34
+ /** ソース側クエリの結果。 */
35
+ interface SourceQueryResult<T> {
41
36
  items: T[];
42
- cachedAt: number;
43
- }
44
- /** ストレージから取得したバイナリオブジェクト。 */
45
- interface StorageBinary {
46
- data: ArrayBuffer;
47
- contentType?: string;
48
- }
49
- /** Notionのプロパティ名マッピング(すべてオプション)。 */
50
- interface CMSSchemaProperties {
51
- /** Notionのスラッグプロパティ名。デフォルト: 'Slug' */
52
- slug?: string;
53
- /** Notionのステータスプロパティ名。デフォルト: 'Status' */
54
- status?: string;
55
- /** Notionの公開日プロパティ名。デフォルト: 'CreatedAt' */
56
- date?: string;
57
- }
58
-
59
- /** ドキュメントキャッシュを抽象化するインターフェース。 */
60
- interface DocumentCacheAdapter<T extends BaseContentItem = BaseContentItem> {
61
- readonly name: string;
62
- getList(): Promise<CachedItemList<T> | null>;
63
- setList(data: CachedItemList<T>): Promise<void>;
64
- getItem(slug: string): Promise<CachedItem<T> | null>;
65
- setItem(slug: string, data: CachedItem<T>): Promise<void>;
66
- invalidate?(scope: "all" | {
67
- slug: string;
68
- } | {
69
- tag: string;
70
- }): Promise<void>;
71
- }
72
- /** 画像キャッシュを抽象化するインターフェース。 */
73
- interface ImageCacheAdapter {
74
- readonly name: string;
75
- get(hash: string): Promise<StorageBinary | null>;
76
- set(hash: string, data: ArrayBuffer, contentType: string): Promise<void>;
77
- }
78
- /** キャッシュ設定オブジェクト。document / image それぞれ独立したアダプタを差し込める。 */
79
- interface CacheConfig<T extends BaseContentItem = BaseContentItem> {
80
- document?: DocumentCacheAdapter<T> | false;
81
- image?: ImageCacheAdapter | false;
82
- /** キャッシュの有効期間(ミリ秒)。未設定の場合はTTLなし。 */
83
- ttlMs?: number;
37
+ hasMore: boolean;
38
+ nextCursor?: string;
84
39
  }
85
-
86
40
  /**
87
41
  * コンテンツソース(Notion など)を抽象化するインターフェース。
88
42
  * core は Notion の知識を持たず、DataSourceAdapter 経由でのみデータを取得する。
@@ -96,21 +50,30 @@ interface DataSourceAdapter<T extends BaseContentItem = BaseContentItem> {
96
50
  }): Promise<T[]>;
97
51
  findBySlug(slug: string): Promise<T | null>;
98
52
  loadMarkdown(item: T): Promise<string>;
53
+ /** Notion 側でフィルタ・ソートを行うクエリ(オプション)。 */
54
+ query?(opts: SourceQueryOptions): Promise<SourceQueryResult<T>>;
99
55
  }
100
56
 
57
+ /**
58
+ * render() オプション。core は renderer の実装を知らず、この型だけを扱う。
59
+ * @notion-headless-cms/renderer の renderMarkdown() はこのシグネチャと構造的に互換。
60
+ */
61
+ interface RenderOptions {
62
+ imageProxyBase?: string;
63
+ cacheImage?: (url: string) => Promise<string>;
64
+ remarkPlugins?: PluggableList;
65
+ rehypePlugins?: PluggableList;
66
+ }
67
+ /** カスタムレンダラー関数の型。デフォルトは @notion-headless-cms/renderer の renderMarkdown。 */
68
+ type RendererFn = (markdown: string, opts?: RenderOptions) => Promise<string>;
101
69
  /** スキーマ設定。公開ステータスのフィルタやプロパティ名マッピングを制御する。 */
102
70
  interface SchemaConfig<T extends BaseContentItem = BaseContentItem> {
103
- /**
104
- * Notionページをコンテンツ型 T にマッピングするカスタム関数。
105
- * 指定した場合 properties の設定は無視される(slug プロパティ名のみ例外)。
106
- */
107
- mapItem?: (page: PageObjectResponse) => T;
108
- /** mapItem 未使用時のプロパティ名マッピング。 */
109
- properties?: CMSSchemaProperties;
110
71
  /** list() で返す「公開済み」ステータス値の配列。デフォルト: [] (全件返す) */
111
72
  publishedStatuses?: string[];
112
73
  /** findBySlug() でアクセス可能なステータス値の配列。デフォルト: [] (全件許可) */
113
74
  accessibleStatuses?: string[];
75
+ /** mapItem 未使用時のプロパティ名マッピング。source-notion 経由で渡す場合は不要。 */
76
+ properties?: CMSSchemaProperties;
114
77
  }
115
78
  /** レンダリング・コンテンツ処理設定。 */
116
79
  interface ContentConfig {
@@ -120,10 +83,17 @@ interface ContentConfig {
120
83
  remarkPlugins?: PluggableList;
121
84
  /** 追加する rehype プラグイン。 */
122
85
  rehypePlugins?: PluggableList;
123
- /** デフォルトのパイプラインを置き換えるカスタムレンダラー。 */
124
- render?: RendererFn;
125
- /** カスタムブロックハンドラーのマップ。Notionブロックタイプをキーとする。 */
126
- blocks?: Record<string, BlockHandler>;
86
+ }
87
+ /** レートリミット・リトライ設定。 */
88
+ interface RateLimiterConfig {
89
+ /** 同時実行数の上限。デフォルト: 3 */
90
+ maxConcurrent?: number;
91
+ /** リトライ対象の HTTP ステータスコード。デフォルト: [429, 502, 503] */
92
+ retryOn?: number[];
93
+ /** 最大リトライ回数。デフォルト: 4 */
94
+ maxRetries?: number;
95
+ /** リトライ時の基準待機時間(ミリ秒)。デフォルト: 1000 */
96
+ baseDelayMs?: number;
127
97
  }
128
98
  /**
129
99
  * createCMS() に渡すオプション。
@@ -132,6 +102,8 @@ interface ContentConfig {
132
102
  interface CreateCMSOptions<T extends BaseContentItem = BaseContentItem> {
133
103
  /** データソースアダプタ(Notion など)。 */
134
104
  source: DataSourceAdapter<T>;
105
+ /** レンダラー関数。未指定時は @notion-headless-cms/renderer の renderMarkdown を使用。 */
106
+ renderer?: RendererFn;
135
107
  /** キャッシュ設定。未設定時はキャッシュなし。 */
136
108
  cache?: CacheConfig<T>;
137
109
  /** スキーマ・ステータス設定。 */
@@ -140,75 +112,64 @@ interface CreateCMSOptions<T extends BaseContentItem = BaseContentItem> {
140
112
  content?: ContentConfig;
141
113
  /** Cloudflare Workers の waitUntil に相当する非同期処理の登録関数。 */
142
114
  waitUntil?: (p: Promise<unknown>) => void;
115
+ /** ライフサイクルフック。 */
116
+ hooks?: CMSHooks<T>;
117
+ /** プラグイン配列。フックとロガーを組み合わせて提供できる。 */
118
+ plugins?: CMSPlugin<T>[];
119
+ /** ロガー。 */
120
+ logger?: Logger;
121
+ /** レートリミット・リトライ設定。 */
122
+ rateLimiter?: RateLimiterConfig;
143
123
  }
144
124
 
145
- /** インメモリキャッシュ(ドキュメント用)を生成する。 */
146
- declare function memoryDocumentCache<T extends BaseContentItem = BaseContentItem>(): DocumentCacheAdapter<T>;
147
- /** インメモリキャッシュ(画像用)を生成する。 */
148
- declare function memoryImageCache(): ImageCacheAdapter;
149
- /**
150
- * ドキュメントと画像の両方にインメモリキャッシュを返す便利関数。
151
- * memoryCache() はドキュメントキャッシュを返す(後方互換)。
152
- */
153
- declare function memoryCache<T extends BaseContentItem = BaseContentItem>(): DocumentCacheAdapter<T>;
154
-
155
- /** 何もしないドキュメントキャッシュを返す(シングルトン)。 */
156
- declare function noopDocumentCache<T extends BaseContentItem = BaseContentItem>(): DocumentCacheAdapter<T>;
157
- /** 何もしない画像キャッシュを返す(シングルトン)。 */
158
- declare function noopImageCache(): ImageCacheAdapter;
159
-
160
- /**
161
- * Notion をバックエンドとして使う汎用ヘッドレス CMS クラス。
162
- *
163
- * @example
164
- * const cms = createCMS({
165
- * source: notionAdapter({ token: '...', dataSourceId: '...' }),
166
- * schema: { publishedStatuses: ['公開'] },
167
- * });
168
- * const items = await cms.list();
169
- */
170
- declare class CMS<T extends BaseContentItem = BaseContentItem> {
125
+ interface QueryResult<T> {
126
+ items: T[];
127
+ total: number;
128
+ page: number;
129
+ perPage: number;
130
+ hasNext: boolean;
131
+ hasPrev: boolean;
132
+ }
133
+ declare class QueryBuilder<T extends BaseContentItem> {
171
134
  private readonly source;
172
- private readonly docCache;
173
- private readonly imgCache;
174
- private readonly hasImageCache;
175
- private readonly ttlMs;
176
- private readonly publishedStatuses;
177
- private readonly accessibleStatuses;
178
- private readonly imageProxyBase;
179
- private readonly contentConfig;
180
- private readonly waitUntil;
181
- constructor(opts: CreateCMSOptions<T>);
182
- /** 公開済みコンテンツ一覧をソースから直接取得する。 */
183
- list(): Promise<T[]>;
184
- /** スラッグでコンテンツをソースから直接取得する。 */
185
- findBySlug(slug: string): Promise<T | null>;
186
- /** アイテムが publishedStatuses に含まれるステータスかどうかを返す。 */
187
- isPublished(item: T): boolean;
188
- /** コンテンツを Markdown → HTML にレンダリングし、CachedItem として返す。 */
189
- render(item: T): Promise<CachedItem<T>>;
190
- /** スラッグでコンテンツを取得して Markdown → HTML にレンダリングする。 */
191
- renderBySlug(slug: string): Promise<CachedItem<T> | null>;
192
- /** ステータスでフィルタリングした一覧を返す。 */
193
- listByStatus(status: string | readonly string[]): Promise<T[]>;
194
- /** 任意の条件でフィルタリングした一覧を返す。 */
195
- where(predicate: (item: T) => boolean): Promise<T[]>;
196
- /** ページネーション付き一覧を返す。 */
135
+ private readonly defaultStatuses;
136
+ private _statuses;
137
+ private _tags;
138
+ private _predicate;
139
+ private _sortField;
140
+ private _sortDir;
141
+ private _page;
142
+ private _perPage;
143
+ constructor(source: DataSourceAdapter<T>, defaultStatuses?: string[]);
144
+ status(s: string | string[]): this;
145
+ tag(t: string | string[]): this;
146
+ where(predicate: (item: T) => boolean): this;
147
+ sortBy(field: keyof T & string, dir?: "asc" | "desc"): this;
197
148
  paginate(opts: {
198
149
  page: number;
199
150
  perPage: number;
200
- }): Promise<{
201
- items: T[];
202
- total: number;
203
- page: number;
204
- perPage: number;
205
- hasNext: boolean;
206
- }>;
207
- /** 指定スラッグの前後コンテンツを返す。 */
208
- getAdjacent(slug: string): Promise<{
151
+ }): this;
152
+ execute(): Promise<QueryResult<T>>;
153
+ executeOne(): Promise<T | null>;
154
+ /** 前後アイテムを返す。sortBy() で指定したソート順を適用する。 */
155
+ adjacent(slug: string): Promise<{
209
156
  prev: T | null;
210
157
  next: T | null;
211
158
  }>;
159
+ /** 最初の 1 件を返す。`.paginate({ page: 1, perPage: 1 }).executeOne()` の短縮形。 */
160
+ first(): Promise<T | null>;
161
+ }
162
+
163
+ /** キャッシュ系の公開名前空間。SWR 読み取りとキャッシュ管理を統合する。 */
164
+ interface CacheAccessor<T extends BaseContentItem> {
165
+ /** キャッシュ優先でコンテンツ一覧を返す(SWR)。 */
166
+ getList(): Promise<{
167
+ items: T[];
168
+ isStale: boolean;
169
+ cachedAt: number;
170
+ }>;
171
+ /** キャッシュ優先で単一コンテンツを返す(SWR)。HTML 付きの CachedItem を返す。 */
172
+ get(slug: string): Promise<CachedItem<T> | null>;
212
173
  /** 全コンテンツを事前レンダリングしてキャッシュに保存する。 */
213
174
  prefetchAll(opts?: {
214
175
  concurrency?: number;
@@ -217,32 +178,25 @@ declare class CMS<T extends BaseContentItem = BaseContentItem> {
217
178
  ok: number;
218
179
  failed: number;
219
180
  }>;
220
- /** 静的生成用のスラッグ一覧を返す。 */
221
- getStaticSlugs(): Promise<string[]>;
222
181
  /** 指定スコープのキャッシュを無効化する。 */
223
182
  revalidate(scope?: "all" | {
224
183
  slug: string;
225
184
  }): Promise<void>;
226
185
  /** Webhook ペイロードを元にキャッシュを同期する。 */
227
- syncFromWebhook(payload?: {
186
+ sync(payload?: {
228
187
  slug?: string;
229
188
  }): Promise<{
230
189
  updated: string[];
231
190
  }>;
232
- /** キャッシュ優先でコンテンツ一覧を返す(SWR)。 */
233
- getList(): Promise<{
234
- items: T[];
235
- listVersion: string;
236
- }>;
237
- /** キャッシュ優先で単一コンテンツを返す(SWR)。 */
238
- getItem(slug: string): Promise<CachedItem<T> | null>;
239
- checkListUpdate(version: string): Promise<{
191
+ /** リスト全体の変更を検知する。version はリスト取得時の buildListVersion の値。 */
192
+ checkList(version: string): Promise<{
240
193
  changed: false;
241
194
  } | {
242
195
  changed: true;
243
196
  items: T[];
244
197
  }>;
245
- checkItemUpdate(slug: string, lastEdited: string): Promise<{
198
+ /** 単一アイテムの変更を検知し、変更があれば再レンダリングしてキャッシュを更新する。 */
199
+ checkItem(slug: string, lastEdited: string): Promise<{
246
200
  changed: false;
247
201
  } | {
248
202
  changed: true;
@@ -250,43 +204,75 @@ declare class CMS<T extends BaseContentItem = BaseContentItem> {
250
204
  item: T;
251
205
  notionUpdatedAt: string;
252
206
  }>;
207
+ }
208
+ /**
209
+ * Notion をバックエンドとして使う汎用ヘッドレス CMS クラス。
210
+ *
211
+ * @example
212
+ * const cms = createCMS({
213
+ * source: notionAdapter({ token: '...', dataSourceId: '...' }),
214
+ * });
215
+ * const items = await cms.list();
216
+ * const entry = await cms.cache.get('my-post');
217
+ */
218
+ declare class CMS<T extends BaseContentItem = BaseContentItem> {
219
+ private readonly source;
220
+ private readonly docCache;
221
+ private readonly imgCache;
222
+ private readonly hasImageCache;
223
+ private readonly ttlMs;
224
+ private readonly publishedStatuses;
225
+ private readonly accessibleStatuses;
226
+ private readonly imageProxyBase;
227
+ private readonly contentConfig;
228
+ private readonly rendererFn;
229
+ private readonly waitUntil;
230
+ private readonly hooks;
231
+ private readonly logger;
232
+ private readonly retryConfig;
233
+ private readonly maxConcurrent;
234
+ readonly cache: CacheAccessor<T>;
235
+ constructor(opts: CreateCMSOptions<T>);
236
+ /** 公開済みコンテンツ一覧をソースから直接取得する。 */
237
+ list(): Promise<T[]>;
238
+ /** スラッグでコンテンツをソースから直接取得する。 */
239
+ find(slug: string): Promise<T | null>;
240
+ /** 複数スラッグをまとめてソースから直接取得する。accessibleStatuses フィルタを適用する。 */
241
+ findMany(slugs: string[]): Promise<Map<string, T>>;
242
+ /** アイテムが publishedStatuses に含まれるステータスかどうかを返す。 */
243
+ isPublished(item: T): boolean;
244
+ /** コンテンツを Markdown → HTML にレンダリングし、CachedItem として返す。キャッシュには保存しない。 */
245
+ render(item: T): Promise<CachedItem<T>>;
246
+ /** QueryBuilder を返す。ステータス・タグ・ページネーションなどを連鎖で指定できる。 */
247
+ query(): QueryBuilder<T>;
248
+ /** 静的生成用のスラッグ一覧を返す。 */
249
+ getStaticSlugs(): Promise<string[]>;
253
250
  /** ハッシュキーでキャッシュ画像を取得する。 */
254
251
  getCachedImage(hash: string): Promise<StorageBinary | null>;
255
252
  /** ハッシュキーでキャッシュ画像を Response として返す。 */
256
253
  createCachedImageResponse(hash: string): Promise<Response | null>;
254
+ private cachedList;
255
+ private cachedGet;
256
+ private prefetchAll;
257
+ private revalidate;
258
+ private syncFromWebhook;
259
+ private checkListUpdate;
260
+ private checkItemUpdate;
257
261
  private buildCachedItem;
258
262
  }
259
263
  /** 設定済みの CMS インスタンスを生成するファクトリ関数。 */
260
264
  declare function createCMS<T extends BaseContentItem = BaseContentItem>(opts: CreateCMSOptions<T>): CMS<T>;
261
265
 
262
- type CMSErrorCode = "CONFIG_INVALID" | "NOTION_ITEM_SCHEMA_INVALID" | "NOTION_FETCH_ITEMS_FAILED" | "NOTION_FETCH_ITEM_BY_SLUG_FAILED" | "NOTION_GET_BLOCKS_FAILED" | "NOTION_MARKDOWN_FETCH_FAILED" | "IMAGE_CACHE_FAILED" | "RENDERER_FAILED";
263
- interface CMSErrorContext {
264
- operation: string;
265
- slug?: string;
266
- dataSourceId?: string;
267
- pageId?: string;
268
- [key: string]: string | number | boolean | null | undefined;
269
- }
270
- declare class CMSError extends Error {
271
- readonly code: CMSErrorCode;
272
- readonly cause?: unknown;
273
- readonly context: CMSErrorContext;
274
- constructor(params: {
275
- code: CMSErrorCode;
276
- message: string;
277
- cause?: unknown;
278
- context: CMSErrorContext;
279
- });
266
+ interface RetryConfig {
267
+ retryOn: number[];
268
+ maxRetries: number;
269
+ baseDelayMs: number;
270
+ /** true のとき指数バックオフにランダムジッターを加える(Thundering Herd 対策)。デフォルト: true */
271
+ jitter?: boolean;
272
+ onRetry?: (attempt: number, status: number) => void;
280
273
  }
281
- declare function isCMSError(error: unknown): error is CMSError;
282
-
283
- /** Notionリッチテキスト配列をプレーンテキストに結合する。 */
284
- declare function getPlainText(items: RichTextItemResponse[] | undefined): string;
285
- /**
286
- * NotionのPageObjectResponseをデフォルトの BaseContentItem に変換する。
287
- * 独自の拡張型(title などを含む)が必要な場合は、本関数の戻り値に
288
- * 追加フィールドを足してカスタム mapItem を実装する。
289
- */
290
- declare function mapItem(page: PageObjectResponse, props: Required<CMSSchemaProperties>): BaseContentItem;
274
+ declare const DEFAULT_RETRY_CONFIG: RetryConfig;
275
+ /** 指数バックオフ(オプションでジッター付き)でリトライする。retryOn に含まれる HTTP エラーのみ対象。 */
276
+ declare function withRetry<T>(fn: () => Promise<T>, config: RetryConfig): Promise<T>;
291
277
 
292
- export { type BaseContentItem, CMS, CMSError, type CMSErrorCode, type CMSErrorContext, type CMSSchemaProperties, type CacheConfig, type CachedItem, type CachedItemList, type ContentConfig, type CreateCMSOptions, type DataSourceAdapter, type DocumentCacheAdapter, type ImageCacheAdapter, type SchemaConfig, type StorageBinary, createCMS, getPlainText, isCMSError, isStale, mapItem, memoryCache, memoryDocumentCache, memoryImageCache, noopDocumentCache, noopImageCache, sha256Hex };
278
+ export { BaseContentItem, CMS, CMSHooks, CMSPlugin, CMSSchemaProperties, type CacheAccessor, CacheConfig, CachedItem, type ContentConfig, type CreateCMSOptions, DEFAULT_RETRY_CONFIG, type DataSourceAdapter, Logger, QueryBuilder, type QueryResult, type RateLimiterConfig, type RenderOptions, type RendererFn, type RetryConfig, type SchemaConfig, type SourceQueryOptions, type SourceQueryResult, StorageBinary, createCMS, isStale, sha256Hex, withRetry };