@notion-headless-cms/core 0.3.20 → 0.3.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -136,4 +136,3 @@ import { memoryCache } from "@notion-headless-cms/core/cache/memory";
136
136
 
137
137
  - [クイックスタート](../../docs/quickstart.md)
138
138
  - [CMS メソッド一覧](../../docs/api/cms-methods.md)
139
- - [v2.0 移行ガイド](../../docs/migration/v2.0.md)
@@ -1,2 +1,25 @@
1
- import { i as memoryCache, n as MemoryDocumentOptions, r as MemoryImageOptions, t as MemoryCacheOptions } from "../memory-BT9rLPr1.mjs";
2
- export { MemoryCacheOptions, MemoryDocumentOptions, MemoryImageOptions, memoryCache };
1
+ import { t as CacheAdapter } from "../cache-DS81aOcC.mjs";
2
+
3
+ //#region src/cache/memory.d.ts
4
+ interface MemoryDocumentOptions {
5
+ /** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
6
+ maxItems?: number;
7
+ }
8
+ interface MemoryImageOptions {
9
+ /** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
10
+ maxItems?: number;
11
+ /** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */
12
+ maxSizeBytes?: number;
13
+ }
14
+ interface MemoryCacheOptions extends MemoryDocumentOptions, MemoryImageOptions {}
15
+ /**
16
+ * インメモリのキャッシュアダプタ。document + image 両方を担当する。
17
+ * プロセス再起動でクリアされるため、ローカル開発・SSG ビルド・テスト用途。
18
+ *
19
+ * @example
20
+ * cache: [memoryCache({ maxItems: 1000 })]
21
+ */
22
+ declare function memoryCache(options?: MemoryCacheOptions): CacheAdapter;
23
+ //#endregion
24
+ export { MemoryCacheOptions, MemoryDocumentOptions, MemoryImageOptions, memoryCache };
25
+ //# sourceMappingURL=memory.d.mts.map
@@ -133,26 +133,5 @@ interface CacheAdapter {
133
133
  img?: ImageCacheOps;
134
134
  }
135
135
  //#endregion
136
- //#region src/cache/memory.d.ts
137
- interface MemoryDocumentOptions {
138
- /** アイテム保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
139
- maxItems?: number;
140
- }
141
- interface MemoryImageOptions {
142
- /** エントリ保持上限。未指定時は上限なし。超過時は LRU で古いものから削除。 */
143
- maxItems?: number;
144
- /** 合計保持サイズ上限(バイト)。未指定時は上限なし。超過時は LRU で古いものから削除。 */
145
- maxSizeBytes?: number;
146
- }
147
- interface MemoryCacheOptions extends MemoryDocumentOptions, MemoryImageOptions {}
148
- /**
149
- * インメモリのキャッシュアダプタ。document + image 両方を担当する。
150
- * プロセス再起動でクリアされるため、ローカル開発・SSG ビルド・テスト用途。
151
- *
152
- * @example
153
- * cache: [memoryCache({ maxItems: 1000 })]
154
- */
155
- declare function memoryCache(options?: MemoryCacheOptions): CacheAdapter;
156
- //#endregion
157
- export { CacheAdapter as a, DataSource as c, PropertyDef as d, PropertyMap as f, memoryCache as i, InvalidateKind as l, MemoryDocumentOptions as n, DocumentCacheOps as o, WebhookConfig as p, MemoryImageOptions as r, ImageCacheOps as s, MemoryCacheOptions as t, InvalidateScope as u };
158
- //# sourceMappingURL=memory-BT9rLPr1.d.mts.map
136
+ export { InvalidateKind as a, PropertyMap as c, DataSource as i, WebhookConfig as l, DocumentCacheOps as n, InvalidateScope as o, ImageCacheOps as r, PropertyDef as s, CacheAdapter as t };
137
+ //# sourceMappingURL=cache-DS81aOcC.d.mts.map
@@ -0,0 +1,150 @@
1
+ import { t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
+ import { i as DataSource, t as CacheAdapter } from "./cache-DS81aOcC.mjs";
3
+ import { i as CMSHooks, r as Logger, t as CMSPlugin } from "./plugin-B795Ok3X.mjs";
4
+
5
+ //#region src/types/sources.d.ts
6
+ /**
7
+ * CMS データソースアダプターのインターフェース。
8
+ * 各アダプターパッケージ (`@notion-headless-cms/notion-source` 等) が実装し、
9
+ * `createClient({ sources: { ... } })` に渡される。
10
+ */
11
+ interface CMSAdapter<C extends CollectionsConfig = CollectionsConfig> {
12
+ readonly collections: C;
13
+ }
14
+ /**
15
+ * アダプターパッケージが宣言マージで拡張する空インターフェース。
16
+ * import するだけでキーが補完候補に現れる (Fastify プラグインと同じパターン)。
17
+ *
18
+ * @example
19
+ * declare module "@notion-headless-cms/core" {
20
+ * interface CMSSources {
21
+ * notion?: CMSAdapter;
22
+ * }
23
+ * }
24
+ */
25
+ interface CMSSources {}
26
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
27
+ /** 全ソースの collections を交差型でマージする。 */
28
+ type MergeSourceCollections<S extends CMSSources> = UnionToIntersection<{ [K in keyof S]: S[K] extends CMSAdapter<infer C> ? C : never }[keyof S]>;
29
+ //#endregion
30
+ //#region src/types/config.d.ts
31
+ /** `Logger` の出力を絞り込むログレベル。指定したレベル未満のログを抑制する。 */
32
+ type LogLevel = "debug" | "info" | "warn" | "error";
33
+ /**
34
+ * renderer プラグインの不透明型。
35
+ * core は unified / remark / rehype に依存せず、このリストをそのまま renderer に渡すだけ。
36
+ */
37
+ type RendererPluginList = unknown[];
38
+ /**
39
+ * render() オプション。core は renderer の実装を知らず、この型だけを扱う。
40
+ * @notion-headless-cms/markdown-html の renderMarkdown() はこのシグネチャと構造的に互換。
41
+ */
42
+ interface RenderOptions {
43
+ imageProxyBase?: string;
44
+ cacheImage?: (url: string) => Promise<string>;
45
+ remarkPlugins?: RendererPluginList;
46
+ rehypePlugins?: RendererPluginList;
47
+ }
48
+ /** カスタムレンダラー関数の型。デフォルトは @notion-headless-cms/markdown-html の renderMarkdown。 */
49
+ type RendererFn = (markdown: string, opts?: RenderOptions) => Promise<string>;
50
+ /** レンダリング・コンテンツ処理設定。 */
51
+ interface ContentConfig {
52
+ /** 追加する remark プラグイン。 */
53
+ remarkPlugins?: RendererPluginList;
54
+ /** 追加する rehype プラグイン。 */
55
+ rehypePlugins?: RendererPluginList;
56
+ }
57
+ /** SWR(Stale-While-Revalidate)設定。 */
58
+ interface SWRConfig {
59
+ /** SWR の有効期間 (ミリ秒)。未設定時は TTL なし(失効まで stale を返す)。 */
60
+ ttlMs?: number;
61
+ }
62
+ /** レートリミット・リトライ設定。 */
63
+ interface RateLimiterConfig {
64
+ /** 同時実行数の上限。デフォルト: 3 */
65
+ maxConcurrent?: number;
66
+ /** リトライ対象の HTTP ステータスコード。デフォルト: [429, 502, 503] */
67
+ retryOn?: number[];
68
+ /** 最大リトライ回数。デフォルト: 4 */
69
+ maxRetries?: number;
70
+ /** リトライ時の基準待機時間(ミリ秒)。デフォルト: 1000 */
71
+ baseDelayMs?: number;
72
+ }
73
+ /**
74
+ * コレクション 1 件の定義。CLI が生成する `nhc.ts` から `createClient` に渡される。
75
+ *
76
+ * `source` は notion-orm 等の DataSource 実装。
77
+ * `slugField` / `statusField` は TS フィールド名 (DataSource の `properties` キーと一致)。
78
+ */
79
+ interface CollectionDef<T extends BaseContentItem = BaseContentItem> {
80
+ /** Notion etc. のデータソース実装。 */
81
+ source: DataSource<T>;
82
+ /** slug として使う TS フィールド名 (必須)。`source.properties[slugField]` で Notion プロパティ名を解決する。 */
83
+ slugField: string;
84
+ /** ステータスとして使う TS フィールド名。 */
85
+ statusField?: string;
86
+ /** 公開扱いするステータス値。`list()` のデフォルト絞り込みに使う。 */
87
+ publishedStatuses?: readonly string[];
88
+ /** アクセス許可するステータス値。`get()` の閲覧可否判定に使う。 */
89
+ accessibleStatuses?: readonly string[];
90
+ /** コレクション固有のライフサイクルフック。グローバル hooks の後に実行される。 */
91
+ hooks?: CMSHooks<T>;
92
+ }
93
+ /**
94
+ * `createClient({ collections })` の map 型。
95
+ * キーがコレクション名、値が `CollectionDef<T>`。
96
+ */
97
+ type CollectionsConfig = Record<string, CollectionDef<BaseContentItem>>;
98
+ /** `CollectionsConfig` から各 T を抽出するユーティリティ型。 */
99
+ type InferCollectionItem<C> = C extends CollectionDef<infer T> ? T : BaseContentItem;
100
+ /**
101
+ * `createClient()` の入力。
102
+ *
103
+ * @example
104
+ * import { createClient, nodePreset } from "@notion-headless-cms/core";
105
+ * import { notionSource } from "@notion-headless-cms/notion-source";
106
+ * import { schema } from "./generated/nhc.schema";
107
+ *
108
+ * const cms = createClient({
109
+ * sources: { notion: notionSource({ schema, token: process.env.NOTION_TOKEN! }) },
110
+ * ...nodePreset(),
111
+ * });
112
+ */
113
+ interface CreateClientOptions<S extends CMSSources = CMSSources> {
114
+ /** データソースアダプター (`@notion-headless-cms/notion-source` 等) のマップ。 */
115
+ sources?: S;
116
+ /**
117
+ * キャッシュアダプタ (配列)。未指定時はキャッシュなし。
118
+ * - `memoryCache()` のように doc + image 両方を担当するもの
119
+ * - `r2Cache()` (image のみ)、`kvCache()` (doc のみ) のように片側のみ担当するもの
120
+ * - 複数 adapter を配列で組み合わせると、各 adapter の `handles` で振り分けられる
121
+ */
122
+ cache?: readonly CacheAdapter[];
123
+ /** SWR(Stale-While-Revalidate)設定。 */
124
+ swr?: SWRConfig;
125
+ /**
126
+ * Markdown→HTML レンダラー。
127
+ * 省略時は `@notion-headless-cms/markdown-html` の `renderMarkdown` を動的 import で使用する。
128
+ * カスタム実装も `RendererFn` 型を満たせば使用可能。
129
+ */
130
+ renderer?: RendererFn;
131
+ /** 画像プロキシのベース URL。デフォルト `/api/images`。 */
132
+ imageProxyBase?: string;
133
+ /** Cloudflare Workers の `waitUntil` に相当する非同期処理の登録関数。 */
134
+ waitUntil?: (p: Promise<unknown>) => void;
135
+ /** ライフサイクルフック (全コレクション共通)。 */
136
+ hooks?: CMSHooks<BaseContentItem>;
137
+ /** プラグイン配列。 */
138
+ plugins?: CMSPlugin<BaseContentItem>[];
139
+ /** ロガー。 */
140
+ logger?: Logger;
141
+ /** ログレベルの下限。指定したレベル未満のログを内部で抑制する。 */
142
+ logLevel?: LogLevel;
143
+ /** レートリミット・リトライ設定。 */
144
+ rateLimiter?: RateLimiterConfig;
145
+ /** レンダリング・コンテンツ処理設定。 */
146
+ content?: ContentConfig;
147
+ }
148
+ //#endregion
149
+ export { InferCollectionItem as a, RenderOptions as c, SWRConfig as d, CMSAdapter as f, CreateClientOptions as i, RendererFn as l, MergeSourceCollections as m, CollectionsConfig as n, LogLevel as o, CMSSources as p, ContentConfig as r, RateLimiterConfig as s, CollectionDef as t, RendererPluginList as u };
150
+ //# sourceMappingURL=config-D4JQ_pmq.d.mts.map
@@ -51,16 +51,27 @@ declare class CMSError extends Error {
51
51
  readonly code: CMSErrorCode;
52
52
  readonly cause?: unknown;
53
53
  readonly context: CMSErrorContext;
54
+ /** エラーを解消するための次のアクション(表示用)。 */
55
+ readonly nextSteps?: readonly string[];
56
+ /** 詳細ドキュメントへの URL(表示用)。 */
57
+ readonly docsUrl?: string;
54
58
  constructor(params: {
55
59
  code: CMSErrorCode;
56
60
  message: string;
57
61
  cause?: unknown;
58
62
  context: CMSErrorContext;
63
+ nextSteps?: readonly string[];
64
+ docsUrl?: string;
59
65
  });
60
66
  /** エラーコードが指定した値と一致するか判定する。 */
61
67
  is(code: CMSErrorCode): boolean;
62
68
  /** エラーコードが指定した名前空間に属するか判定する(例: `"source/"`)。 */
63
69
  inNamespace(namespace: string): boolean;
70
+ /**
71
+ * nextSteps と docsUrl を含む人間向けの詳細メッセージを返す。
72
+ * エラーダイアログ・ログ出力時に使う。
73
+ */
74
+ format(): string;
64
75
  }
65
76
  declare function isCMSError(error: unknown): error is CMSError;
66
77
  /** エラーコードが特定の名前空間に属するかを判定する(例: "source/")。 */
@@ -81,4 +92,4 @@ declare function matchCMSError<R>(error: unknown, handlers: Partial<Record<CMSEr
81
92
  }): R | undefined;
82
93
  //#endregion
83
94
  export { isCMSError as a, CMSErrorContext as i, CMSError as n, isCMSErrorInNamespace as o, CMSErrorCode as r, matchCMSError as s, BuiltInCMSErrorCode as t };
84
- //# sourceMappingURL=errors-CC_x98vG.d.mts.map
95
+ //# sourceMappingURL=errors-DTt9ii0i.d.mts.map
package/dist/errors.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-CC_x98vG.mjs";
1
+ import { a as isCMSError, i as CMSErrorContext, n as CMSError, o as isCMSErrorInNamespace, r as CMSErrorCode, s as matchCMSError, t as BuiltInCMSErrorCode } from "./errors-DTt9ii0i.mjs";
2
2
  export { BuiltInCMSErrorCode, CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace, matchCMSError };
package/dist/errors.mjs CHANGED
@@ -3,12 +3,18 @@ var CMSError = class extends Error {
3
3
  code;
4
4
  cause;
5
5
  context;
6
+ /** エラーを解消するための次のアクション(表示用)。 */
7
+ nextSteps;
8
+ /** 詳細ドキュメントへの URL(表示用)。 */
9
+ docsUrl;
6
10
  constructor(params) {
7
11
  super(params.message, { cause: params.cause });
8
12
  this.name = "CMSError";
9
13
  this.code = params.code;
10
14
  this.cause = params.cause;
11
15
  this.context = params.context;
16
+ this.nextSteps = params.nextSteps;
17
+ this.docsUrl = params.docsUrl;
12
18
  }
13
19
  /** エラーコードが指定した値と一致するか判定する。 */
14
20
  is(code) {
@@ -18,6 +24,19 @@ var CMSError = class extends Error {
18
24
  inNamespace(namespace) {
19
25
  return this.code.startsWith(namespace);
20
26
  }
27
+ /**
28
+ * nextSteps と docsUrl を含む人間向けの詳細メッセージを返す。
29
+ * エラーダイアログ・ログ出力時に使う。
30
+ */
31
+ format() {
32
+ const lines = [this.message];
33
+ if (this.nextSteps?.length) {
34
+ lines.push("\n次にやること:");
35
+ for (const step of this.nextSteps) lines.push(` - ${step}`);
36
+ }
37
+ if (this.docsUrl) lines.push(`\n詳細: ${this.docsUrl}`);
38
+ return lines.join("\n");
39
+ }
21
40
  };
22
41
  function isCMSError(error) {
23
42
  return error instanceof CMSError;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * ライブラリ組み込みの CMS エラーコード。\n *\n * | コード | 発生条件 |\n * |---|---|\n * | `core/config_invalid` | 設定不備(token 未設定など) |\n * | `core/schema_invalid` | schema/mapping の型不整合 |\n * | `core/notion_orm_missing` | `@notion-headless-cms/notion-orm` の動的ロード失敗 |\n * | `core/sort_unsupported_type` | ソートキーの値型が string / number でない |\n * | `webhook/signature_invalid` | Webhook 署名検証失敗 |\n * | `webhook/payload_invalid` | Webhook ペイロード形式不正 |\n * | `webhook/unknown_collection` | Webhook の対象コレクションが未知 |\n * | `webhook/not_implemented` | DataSource が parseWebhook を実装していない |\n * | `source/fetch_items_failed` | `DataSource.list()` 失敗 |\n * | `source/fetch_item_failed` | `DataSource.findByProp()` 失敗 |\n * | `source/load_markdown_failed` | `DataSource.loadMarkdown()` 失敗 |\n * | `source/load_blocks_failed` | `DataSource.loadBlocks()` 失敗 |\n * | `cache/io_failed` | document / image キャッシュの I/O 失敗 |\n * | `cache/image_fetch_failed` | Notion 画像の HTTP 取得失敗 |\n * | `cache/image_invalid_content_type` | 画像レスポンスの Content-Type が不正 |\n * | `renderer/failed` | Markdown → HTML 変換失敗 |\n * | `swr/item_check_failed` | SWR バックグラウンドのアイテム差分チェック失敗 |\n * | `swr/list_check_failed` | SWR バックグラウンドのリスト差分チェック失敗 |\n * | `swr/content_rebuild_failed` | SWR バックグラウンドの本文再生成失敗 |\n * | `cli/config_invalid` | `nhc.config.ts` の内容不整合 |\n * | `cli/config_load_failed` | 設定ファイルの読み込み / 評価失敗 |\n * | `cli/schema_invalid` | CLI が受け取ったスキーマ / マッピング不整合 |\n * | `cli/generate_failed` | `nhc generate` の処理失敗 |\n * | `cli/init_failed` | `nhc init` の処理失敗 |\n * | `cli/notion_api_failed` | CLI が Notion API を呼び出す際の失敗 |\n * | `cli/env_file_not_found` | `--env-file` で指定したファイルが存在しない |\n *\n * サードパーティアダプタが独自コードを追加したい場合は `CMSErrorCode` を参照。\n */\nexport type BuiltInCMSErrorCode =\n | \"core/config_invalid\"\n | \"core/schema_invalid\"\n | \"core/notion_orm_missing\"\n | \"core/sort_unsupported_type\"\n | \"webhook/signature_invalid\"\n | \"webhook/payload_invalid\"\n | \"webhook/unknown_collection\"\n | \"webhook/not_implemented\"\n | \"source/fetch_items_failed\"\n | \"source/fetch_item_failed\"\n | \"source/load_markdown_failed\"\n | \"source/load_blocks_failed\"\n | \"cache/io_failed\"\n | \"cache/image_fetch_failed\"\n | \"cache/image_invalid_content_type\"\n | \"renderer/failed\"\n | \"swr/item_check_failed\"\n | \"swr/list_check_failed\"\n | \"swr/content_rebuild_failed\"\n | \"cli/config_invalid\"\n | \"cli/config_load_failed\"\n | \"cli/schema_invalid\"\n | \"cli/generate_failed\"\n | \"cli/init_failed\"\n | \"cli/notion_api_failed\"\n | \"cli/env_file_not_found\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n operation: string;\n slug?: string;\n dataSourceId?: string;\n pageId?: string;\n [key: string]: string | number | boolean | null | undefined;\n}\n\nexport class CMSError extends Error {\n readonly code: CMSErrorCode;\n override readonly cause?: unknown;\n readonly context: CMSErrorContext;\n\n constructor(params: {\n code: CMSErrorCode;\n message: string;\n cause?: unknown;\n context: CMSErrorContext;\n }) {\n super(params.message, { cause: params.cause });\n this.name = \"CMSError\";\n this.code = params.code;\n this.cause = params.cause;\n this.context = params.context;\n }\n\n /** エラーコードが指定した値と一致するか判定する。 */\n is(code: CMSErrorCode): boolean {\n return this.code === code;\n }\n\n /** エラーコードが指定した名前空間に属するか判定する(例: `\"source/\"`)。 */\n inNamespace(namespace: string): boolean {\n return this.code.startsWith(namespace);\n }\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n return error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n error: unknown,\n namespace: string,\n): error is CMSError {\n return isCMSError(error) && error.code.startsWith(namespace);\n}\n\ntype CMSErrorHandler<R> = (err: CMSError) => R;\n\n/**\n * `CMSError` を switch 式のように分岐して処理するユーティリティ。\n * `_` キーはフォールバック(CMSError 以外 or 未マッチ時)に使われる。\n *\n * @example\n * matchCMSError(err, {\n * \"source/fetch_items_failed\": (e) => handleFetchError(e),\n * _: (e) => { throw e; },\n * });\n */\nexport function matchCMSError<R>(\n error: unknown,\n handlers: Partial<Record<CMSErrorCode, CMSErrorHandler<R>>> & {\n _?: (err: unknown) => R;\n },\n): R | undefined {\n if (!isCMSError(error)) {\n return handlers._?.(error);\n }\n const handler =\n handlers[error.code as CMSErrorCode] ??\n (handlers._ as CMSErrorHandler<R> | undefined);\n return handler?.(error);\n}\n"],"mappings":";AA6EA,IAAa,WAAb,cAA8B,MAAM;CAClC;CACA;CACA;CAEA,YAAY,QAKT;AACD,QAAM,OAAO,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC;AAC9C,OAAK,OAAO;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;CAIxB,GAAG,MAA6B;AAC9B,SAAO,KAAK,SAAS;;;CAIvB,YAAY,WAA4B;AACtC,SAAO,KAAK,KAAK,WAAW,UAAU;;;AAI1C,SAAgB,WAAW,OAAmC;AAC5D,QAAO,iBAAiB;;;AAI1B,SAAgB,sBACd,OACA,WACmB;AACnB,QAAO,WAAW,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU;;;;;;;;;;;;AAe9D,SAAgB,cACd,OACA,UAGe;AACf,KAAI,CAAC,WAAW,MAAM,CACpB,QAAO,SAAS,IAAI,MAAM;AAK5B,SAFE,SAAS,MAAM,SACd,SAAS,KACK,MAAM"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * ライブラリ組み込みの CMS エラーコード。\n *\n * | コード | 発生条件 |\n * |---|---|\n * | `core/config_invalid` | 設定不備(token 未設定など) |\n * | `core/schema_invalid` | schema/mapping の型不整合 |\n * | `core/notion_orm_missing` | `@notion-headless-cms/notion-orm` の動的ロード失敗 |\n * | `core/sort_unsupported_type` | ソートキーの値型が string / number でない |\n * | `webhook/signature_invalid` | Webhook 署名検証失敗 |\n * | `webhook/payload_invalid` | Webhook ペイロード形式不正 |\n * | `webhook/unknown_collection` | Webhook の対象コレクションが未知 |\n * | `webhook/not_implemented` | DataSource が parseWebhook を実装していない |\n * | `source/fetch_items_failed` | `DataSource.list()` 失敗 |\n * | `source/fetch_item_failed` | `DataSource.findByProp()` 失敗 |\n * | `source/load_markdown_failed` | `DataSource.loadMarkdown()` 失敗 |\n * | `source/load_blocks_failed` | `DataSource.loadBlocks()` 失敗 |\n * | `cache/io_failed` | document / image キャッシュの I/O 失敗 |\n * | `cache/image_fetch_failed` | Notion 画像の HTTP 取得失敗 |\n * | `cache/image_invalid_content_type` | 画像レスポンスの Content-Type が不正 |\n * | `renderer/failed` | Markdown → HTML 変換失敗 |\n * | `swr/item_check_failed` | SWR バックグラウンドのアイテム差分チェック失敗 |\n * | `swr/list_check_failed` | SWR バックグラウンドのリスト差分チェック失敗 |\n * | `swr/content_rebuild_failed` | SWR バックグラウンドの本文再生成失敗 |\n * | `cli/config_invalid` | `nhc.config.ts` の内容不整合 |\n * | `cli/config_load_failed` | 設定ファイルの読み込み / 評価失敗 |\n * | `cli/schema_invalid` | CLI が受け取ったスキーマ / マッピング不整合 |\n * | `cli/generate_failed` | `nhc generate` の処理失敗 |\n * | `cli/init_failed` | `nhc init` の処理失敗 |\n * | `cli/notion_api_failed` | CLI が Notion API を呼び出す際の失敗 |\n * | `cli/env_file_not_found` | `--env-file` で指定したファイルが存在しない |\n *\n * サードパーティアダプタが独自コードを追加したい場合は `CMSErrorCode` を参照。\n */\nexport type BuiltInCMSErrorCode =\n | \"core/config_invalid\"\n | \"core/schema_invalid\"\n | \"core/notion_orm_missing\"\n | \"core/sort_unsupported_type\"\n | \"webhook/signature_invalid\"\n | \"webhook/payload_invalid\"\n | \"webhook/unknown_collection\"\n | \"webhook/not_implemented\"\n | \"source/fetch_items_failed\"\n | \"source/fetch_item_failed\"\n | \"source/load_markdown_failed\"\n | \"source/load_blocks_failed\"\n | \"cache/io_failed\"\n | \"cache/image_fetch_failed\"\n | \"cache/image_invalid_content_type\"\n | \"renderer/failed\"\n | \"swr/item_check_failed\"\n | \"swr/list_check_failed\"\n | \"swr/content_rebuild_failed\"\n | \"cli/config_invalid\"\n | \"cli/config_load_failed\"\n | \"cli/schema_invalid\"\n | \"cli/generate_failed\"\n | \"cli/init_failed\"\n | \"cli/notion_api_failed\"\n | \"cli/env_file_not_found\";\n\n/**\n * CMS エラーコード。\n * `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、\n * サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。\n */\nexport type CMSErrorCode = BuiltInCMSErrorCode | (string & {});\n\nexport interface CMSErrorContext {\n operation: string;\n slug?: string;\n dataSourceId?: string;\n pageId?: string;\n [key: string]: string | number | boolean | null | undefined;\n}\n\nexport class CMSError extends Error {\n readonly code: CMSErrorCode;\n override readonly cause?: unknown;\n readonly context: CMSErrorContext;\n /** エラーを解消するための次のアクション(表示用)。 */\n readonly nextSteps?: readonly string[];\n /** 詳細ドキュメントへの URL(表示用)。 */\n readonly docsUrl?: string;\n\n constructor(params: {\n code: CMSErrorCode;\n message: string;\n cause?: unknown;\n context: CMSErrorContext;\n nextSteps?: readonly string[];\n docsUrl?: string;\n }) {\n super(params.message, { cause: params.cause });\n this.name = \"CMSError\";\n this.code = params.code;\n this.cause = params.cause;\n this.context = params.context;\n this.nextSteps = params.nextSteps;\n this.docsUrl = params.docsUrl;\n }\n\n /** エラーコードが指定した値と一致するか判定する。 */\n is(code: CMSErrorCode): boolean {\n return this.code === code;\n }\n\n /** エラーコードが指定した名前空間に属するか判定する(例: `\"source/\"`)。 */\n inNamespace(namespace: string): boolean {\n return this.code.startsWith(namespace);\n }\n\n /**\n * nextSteps と docsUrl を含む人間向けの詳細メッセージを返す。\n * エラーダイアログ・ログ出力時に使う。\n */\n format(): string {\n const lines: string[] = [this.message];\n if (this.nextSteps?.length) {\n lines.push(\"\\n次にやること:\");\n for (const step of this.nextSteps) {\n lines.push(` - ${step}`);\n }\n }\n if (this.docsUrl) {\n lines.push(`\\n詳細: ${this.docsUrl}`);\n }\n return lines.join(\"\\n\");\n }\n}\n\nexport function isCMSError(error: unknown): error is CMSError {\n return error instanceof CMSError;\n}\n\n/** エラーコードが特定の名前空間に属するかを判定する(例: \"source/\")。 */\nexport function isCMSErrorInNamespace(\n error: unknown,\n namespace: string,\n): error is CMSError {\n return isCMSError(error) && error.code.startsWith(namespace);\n}\n\ntype CMSErrorHandler<R> = (err: CMSError) => R;\n\n/**\n * `CMSError` を switch 式のように分岐して処理するユーティリティ。\n * `_` キーはフォールバック(CMSError 以外 or 未マッチ時)に使われる。\n *\n * @example\n * matchCMSError(err, {\n * \"source/fetch_items_failed\": (e) => handleFetchError(e),\n * _: (e) => { throw e; },\n * });\n */\nexport function matchCMSError<R>(\n error: unknown,\n handlers: Partial<Record<CMSErrorCode, CMSErrorHandler<R>>> & {\n _?: (err: unknown) => R;\n },\n): R | undefined {\n if (!isCMSError(error)) {\n return handlers._?.(error);\n }\n const handler =\n handlers[error.code as CMSErrorCode] ??\n (handlers._ as CMSErrorHandler<R> | undefined);\n return handler?.(error);\n}\n"],"mappings":";AA6EA,IAAa,WAAb,cAA8B,MAAM;CAClC;CACA;CACA;;CAEA;;CAEA;CAEA,YAAY,QAOT;AACD,QAAM,OAAO,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC;AAC9C,OAAK,OAAO;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;AACtB,OAAK,YAAY,OAAO;AACxB,OAAK,UAAU,OAAO;;;CAIxB,GAAG,MAA6B;AAC9B,SAAO,KAAK,SAAS;;;CAIvB,YAAY,WAA4B;AACtC,SAAO,KAAK,KAAK,WAAW,UAAU;;;;;;CAOxC,SAAiB;EACf,MAAM,QAAkB,CAAC,KAAK,QAAQ;AACtC,MAAI,KAAK,WAAW,QAAQ;AAC1B,SAAM,KAAK,YAAY;AACvB,QAAK,MAAM,QAAQ,KAAK,UACtB,OAAM,KAAK,OAAO,OAAO;;AAG7B,MAAI,KAAK,QACP,OAAM,KAAK,SAAS,KAAK,UAAU;AAErC,SAAO,MAAM,KAAK,KAAK;;;AAI3B,SAAgB,WAAW,OAAmC;AAC5D,QAAO,iBAAiB;;;AAI1B,SAAgB,sBACd,OACA,WACmB;AACnB,QAAO,WAAW,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU;;;;;;;;;;;;AAe9D,SAAgB,cACd,OACA,UAGe;AACf,KAAI,CAAC,WAAW,MAAM,CACpB,QAAO,SAAS,IAAI,MAAM;AAK5B,SAFE,SAAS,MAAM,SACd,SAAS,KACK,MAAM"}
package/dist/hooks.d.mts CHANGED
@@ -1,2 +1,17 @@
1
- import { n as mergeLoggers, t as mergeHooks } from "./hooks-C6F2PG8x.mjs";
2
- export { mergeHooks, mergeLoggers };
1
+ import { t as BaseContentItem } from "./content-DwsfWZao.mjs";
2
+ import { i as CMSHooks, r as Logger, t as CMSPlugin } from "./plugin-B795Ok3X.mjs";
3
+
4
+ //#region src/hooks.d.ts
5
+ /**
6
+ * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。
7
+ * beforeCacheMeta / beforeCacheContent / afterRender はパイプライン(前の出力が次の入力)。
8
+ * オブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
9
+ */
10
+ declare function mergeHooks<T extends BaseContentItem>(plugins: CMSPlugin<T>[], directHooks?: CMSHooks<T>, logger?: Logger): CMSHooks<T>;
11
+ /** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */
12
+ declare function mergeLoggers(plugins: Array<{
13
+ logger?: Partial<Logger>;
14
+ }>, directLogger?: Logger): Logger | undefined;
15
+ //#endregion
16
+ export { mergeHooks, mergeLoggers };
17
+ //# sourceMappingURL=hooks.d.mts.map
package/dist/html.d.mts CHANGED
@@ -2,20 +2,26 @@
2
2
  interface NotionRevalidatorScriptOptions {
3
3
  /**
4
4
  * 再検証のトリガー。既定値: "visibility"
5
- * - "visibility": タブ可視化 (`visibilitychange` で hidden→visible) の度に reload
5
+ * - "visibility": `visibilitychange` で hidden→visible の度に reload
6
6
  * - "focus": ウィンドウ focus の度に reload (visibility より発火が頻繁)
7
7
  */
8
8
  on?: "visibility" | "focus";
9
- /**
10
- * `<script nonce="...">` を出力したい場合 (CSP 対応)。
11
- * 英数字・`-` / `_` / `+` / `/` / `=` のみ許可。属性値ブレイクアウトを防ぐため
12
- * 不正な文字が含まれていれば throw する。
13
- */
9
+ /** CSP 対応の `nonce` 値。base64 / base64url 由来の文字のみ許可、不正値は throw する。 */
14
10
  nonce?: string;
15
11
  }
16
12
  /**
17
- * React を使わないページに埋め込むための `<script>...</script>` 文字列を返す。
18
- * 返り値は外部入力を埋め込まない(nonce は厳格に検証)safe な内容。
13
+ * React を使わないテンプレート (Astro / Hono / Express など) 向けに、
14
+ * SWR 再検証用の `<script>...</script>` 文字列を返す。
15
+ *
16
+ * タブ可視化や focus を検知して `location.reload()` を呼び、サーバ側で
17
+ * `waitUntil` 配線済みの SWR bg が更新したキャッシュを次回訪問時に取得する。
18
+ * クエリ無し・別 API fetch 無し。
19
+ *
20
+ * @example
21
+ * // Astro
22
+ * <Fragment set:html={notionRevalidatorScript()} />
23
+ * // Hono
24
+ * c.html(h`...${raw(notionRevalidatorScript())}`);
19
25
  */
20
26
  declare function notionRevalidatorScript(opts?: NotionRevalidatorScriptOptions): string;
21
27
  //#endregion
package/dist/html.mjs CHANGED
@@ -1,8 +1,18 @@
1
1
  //#region src/html.ts
2
2
  const NONCE_PATTERN = /^[A-Za-z0-9+/=_-]+$/;
3
3
  /**
4
- * React を使わないページに埋め込むための `<script>...<\/script>` 文字列を返す。
5
- * 返り値は外部入力を埋め込まない(nonce は厳格に検証)safe な内容。
4
+ * React を使わないテンプレート (Astro / Hono / Express など) 向けに、
5
+ * SWR 再検証用の `<script>...<\/script>` 文字列を返す。
6
+ *
7
+ * タブ可視化や focus を検知して `location.reload()` を呼び、サーバ側で
8
+ * `waitUntil` 配線済みの SWR bg が更新したキャッシュを次回訪問時に取得する。
9
+ * クエリ無し・別 API fetch 無し。
10
+ *
11
+ * @example
12
+ * // Astro
13
+ * <Fragment set:html={notionRevalidatorScript()} />
14
+ * // Hono
15
+ * c.html(h`...${raw(notionRevalidatorScript())}`);
6
16
  */
7
17
  function notionRevalidatorScript(opts = {}) {
8
18
  const body = (opts.on ?? "visibility") === "focus" ? "let l=false;addEventListener(\"focus\",()=>{if(l)location.reload();l=true});" : "document.addEventListener(\"visibilitychange\",()=>{if(document.visibilityState===\"visible\")location.reload()});";
package/dist/html.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"html.mjs","names":[],"sources":["../src/html.ts"],"sourcesContent":["// React を使わないテンプレート (Astro / Hono / Express など) で SWR 体験を\n// 成立させるための、ゼロ依存の小さな <script> 文字列ジェネレータ。\n//\n// 使い方 (Astro):\n// import { notionRevalidatorScript } from \"@notion-headless-cms/core/html\";\n// <Fragment set:html={notionRevalidatorScript()} />\n//\n// 使い方 (Hono):\n// import { raw, html as h } from \"hono/html\";\n// c.html(h`...${raw(notionRevalidatorScript())}`);\n//\n// 仕組み: タブ可視化 (visibilitychange で hidden→visible) を検知して\n// `location.reload()` で同じ URL を再リクエストする。サーバ側は\n// `cloudflarePreset` 等で `waitUntil` が配線されていれば、前回訪問時に\n// SWR bg が KV を最新化済み → 再取得で新内容が返る。クエリ無し・別 API fetch 無し。\n\nexport interface NotionRevalidatorScriptOptions {\n /**\n * 再検証のトリガー。既定値: \"visibility\"\n * - \"visibility\": タブ可視化 (`visibilitychange` で hidden→visible) の度に reload\n * - \"focus\": ウィンドウ focus の度に reload (visibility より発火が頻繁)\n */\n on?: \"visibility\" | \"focus\";\n /**\n * `<script nonce=\"...\">` を出力したい場合 (CSP 対応)。\n * 英数字・`-` / `_` / `+` / `/` / `=` のみ許可。属性値ブレイクアウトを防ぐため\n * 不正な文字が含まれていれば throw する。\n */\n nonce?: string;\n}\n\n// CSP nonce は本来 base64 / base64url 由来のランダム文字列。属性値の\n// クォート・タグブレイクアウトを未然に防ぐため、最小限の許可セットで検証する。\nconst NONCE_PATTERN = /^[A-Za-z0-9+/=_-]+$/;\n\n/**\n * React を使わないページに埋め込むための `<script>...</script>` 文字列を返す。\n * 返り値は外部入力を埋め込まない(nonce は厳格に検証)safe な内容。\n */\nexport function notionRevalidatorScript(\n opts: NotionRevalidatorScriptOptions = {},\n): string {\n const trigger = opts.on ?? \"visibility\";\n const body =\n trigger === \"focus\"\n ? // 初回ロード時の focus は無視するため、loaded フラグで初回をスキップ。\n 'let l=false;addEventListener(\"focus\",()=>{if(l)location.reload();l=true});'\n : 'document.addEventListener(\"visibilitychange\",()=>{if(document.visibilityState===\"visible\")location.reload()});';\n let nonceAttr = \"\";\n if (opts.nonce !== undefined) {\n if (!NONCE_PATTERN.test(opts.nonce)) {\n throw new Error(\n \"notionRevalidatorScript: nonce に不正な文字が含まれています。base64 / base64url 由来の英数字のみ受け付けます。\",\n );\n }\n nonceAttr = ` nonce=\"${opts.nonce}\"`;\n }\n return `<script${nonceAttr}>(()=>{${body}})();</script>`;\n}\n"],"mappings":";AAiCA,MAAM,gBAAgB;;;;;AAMtB,SAAgB,wBACd,OAAuC,EAAE,EACjC;CAER,MAAM,QADU,KAAK,MAAM,kBAEb,UAER,iFACA;CACN,IAAI,YAAY;AAChB,KAAI,KAAK,UAAU,KAAA,GAAW;AAC5B,MAAI,CAAC,cAAc,KAAK,KAAK,MAAM,CACjC,OAAM,IAAI,MACR,mFACD;AAEH,cAAY,WAAW,KAAK,MAAM;;AAEpC,QAAO,UAAU,UAAU,SAAS,KAAK"}
1
+ {"version":3,"file":"html.mjs","names":[],"sources":["../src/html.ts"],"sourcesContent":["export interface NotionRevalidatorScriptOptions {\n /**\n * 再検証のトリガー。既定値: \"visibility\"\n * - \"visibility\": `visibilitychange` で hidden→visible の度に reload\n * - \"focus\": ウィンドウ focus の度に reload (visibility より発火が頻繁)\n */\n on?: \"visibility\" | \"focus\";\n /** CSP 対応の `nonce` 値。base64 / base64url 由来の文字のみ許可、不正値は throw する。 */\n nonce?: string;\n}\n\n// 属性値ブレイクアウトを防ぐため、CSP nonce base64 / base64url 範囲に限定して検証する\nconst NONCE_PATTERN = /^[A-Za-z0-9+/=_-]+$/;\n\n/**\n * React を使わないテンプレート (Astro / Hono / Express など) 向けに、\n * SWR 再検証用の `<script>...</script>` 文字列を返す。\n *\n * タブ可視化や focus を検知して `location.reload()` を呼び、サーバ側で\n * `waitUntil` 配線済みの SWR bg が更新したキャッシュを次回訪問時に取得する。\n * クエリ無し・別 API fetch 無し。\n *\n * @example\n * // Astro\n * <Fragment set:html={notionRevalidatorScript()} />\n * // Hono\n * c.html(h`...${raw(notionRevalidatorScript())}`);\n */\nexport function notionRevalidatorScript(\n opts: NotionRevalidatorScriptOptions = {},\n): string {\n const trigger = opts.on ?? \"visibility\";\n const body =\n trigger === \"focus\"\n ? // 初回ロード直後の focus は無視するため、loaded フラグで初回をスキップ\n 'let l=false;addEventListener(\"focus\",()=>{if(l)location.reload();l=true});'\n : 'document.addEventListener(\"visibilitychange\",()=>{if(document.visibilityState===\"visible\")location.reload()});';\n let nonceAttr = \"\";\n if (opts.nonce !== undefined) {\n if (!NONCE_PATTERN.test(opts.nonce)) {\n throw new Error(\n \"notionRevalidatorScript: nonce に不正な文字が含まれています。base64 / base64url 由来の英数字のみ受け付けます。\",\n );\n }\n nonceAttr = ` nonce=\"${opts.nonce}\"`;\n }\n return `<script${nonceAttr}>(()=>{${body}})();</script>`;\n}\n"],"mappings":";AAYA,MAAM,gBAAgB;;;;;;;;;;;;;;;AAgBtB,SAAgB,wBACd,OAAuC,EAAE,EACjC;CAER,MAAM,QADU,KAAK,MAAM,kBAEb,UAER,iFACA;CACN,IAAI,YAAY;AAChB,KAAI,KAAK,UAAU,KAAA,GAAW;AAC5B,MAAI,CAAC,cAAc,KAAK,KAAK,MAAM,CACjC,OAAM,IAAI,MACR,mFACD;AAEH,cAAY,WAAW,KAAK,MAAM;;AAEpC,QAAO,UAAU,UAAU,SAAS,KAAK"}