@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.
- package/README.md +69 -175
- package/dist/cache/memory.d.mts +52 -0
- package/dist/cache/memory.mjs +115 -0
- package/dist/cache/memory.mjs.map +1 -0
- package/dist/cache/{noop.d.ts → noop.d.mts} +5 -3
- package/dist/cache/noop.mjs +44 -0
- package/dist/cache/noop.mjs.map +1 -0
- package/dist/cache-Av7HRw_s.d.mts +205 -0
- package/dist/content-BrwEY2_p.d.mts +53 -0
- package/dist/errors.d.mts +32 -0
- package/dist/errors.mjs +24 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/hooks-DCSAQkST.d.mts +60 -0
- package/dist/hooks.d.mts +2 -0
- package/dist/hooks.mjs +75 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/index.d.mts +349 -0
- package/dist/index.mjs +672 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +17 -16
- package/dist/cache/memory.d.ts +0 -54
- package/dist/cache/memory.js +0 -15
- package/dist/cache/memory.js.map +0 -1
- package/dist/cache/noop.js +0 -9
- package/dist/cache/noop.js.map +0 -1
- package/dist/cache-DvbyemBK.d.ts +0 -33
- package/dist/chunk-4KGKWKKI.js +0 -80
- package/dist/chunk-4KGKWKKI.js.map +0 -1
- package/dist/chunk-6DG63XUF.js +0 -42
- package/dist/chunk-6DG63XUF.js.map +0 -1
- package/dist/chunk-6LHROEPI.js +0 -104
- package/dist/chunk-6LHROEPI.js.map +0 -1
- package/dist/chunk-V6ML4QE5.js +0 -26
- package/dist/chunk-V6ML4QE5.js.map +0 -1
- package/dist/content-Biqf0l_o.d.ts +0 -51
- package/dist/errors.d.ts +0 -30
- package/dist/errors.js +0 -11
- package/dist/errors.js.map +0 -1
- package/dist/hooks-B83RUclt.d.ts +0 -41
- package/dist/hooks.d.ts +0 -2
- package/dist/hooks.js +0 -9
- package/dist/hooks.js.map +0 -1
- package/dist/index.d.ts +0 -278
- package/dist/index.js +0 -598
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { a as StorageBinary, i as CachedItemList, r as CachedItem, t as BaseContentItem } from "./content-BrwEY2_p.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/content/blocks.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* CMS 本文の中間表現 (AST)。
|
|
6
|
+
*
|
|
7
|
+
* DataSource が本文をこの配列にノーマライズして返す。
|
|
8
|
+
* レンダラー (`blocksToHtml`) やカスタム React コンポーネントは
|
|
9
|
+
* この型だけを扱えばよい。
|
|
10
|
+
*
|
|
11
|
+
* 対応が難しいブロック (Notion の column / synced block など) は
|
|
12
|
+
* `{ type: "raw", html }` にフォールバックする。
|
|
13
|
+
*/
|
|
14
|
+
type ContentBlock = {
|
|
15
|
+
type: "paragraph";
|
|
16
|
+
children: InlineNode[];
|
|
17
|
+
} | {
|
|
18
|
+
type: "heading";
|
|
19
|
+
level: 1 | 2 | 3;
|
|
20
|
+
children: InlineNode[];
|
|
21
|
+
} | {
|
|
22
|
+
type: "image";
|
|
23
|
+
src: string;
|
|
24
|
+
alt?: string;
|
|
25
|
+
cachedHash?: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "code";
|
|
28
|
+
lang?: string;
|
|
29
|
+
value: string;
|
|
30
|
+
} | {
|
|
31
|
+
type: "list";
|
|
32
|
+
ordered: boolean;
|
|
33
|
+
items: ContentBlock[][];
|
|
34
|
+
} | {
|
|
35
|
+
type: "quote";
|
|
36
|
+
children: ContentBlock[];
|
|
37
|
+
} | {
|
|
38
|
+
type: "divider";
|
|
39
|
+
} | {
|
|
40
|
+
type: "raw";
|
|
41
|
+
html: string;
|
|
42
|
+
};
|
|
43
|
+
/** paragraph / heading 等の子に並ぶインラインノード。 */
|
|
44
|
+
type InlineNode = {
|
|
45
|
+
type: "text";
|
|
46
|
+
value: string;
|
|
47
|
+
bold?: boolean;
|
|
48
|
+
italic?: boolean;
|
|
49
|
+
code?: boolean;
|
|
50
|
+
} | {
|
|
51
|
+
type: "link";
|
|
52
|
+
url: string;
|
|
53
|
+
children: InlineNode[];
|
|
54
|
+
} | {
|
|
55
|
+
type: "break";
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* `getItem({ include: { content: true } })` で返される本文。
|
|
59
|
+
* blocks は常に同梱。html / markdown は遅延生成。
|
|
60
|
+
*/
|
|
61
|
+
interface ContentResult {
|
|
62
|
+
/** 本文の AST (第一級)。 */
|
|
63
|
+
blocks: ContentBlock[];
|
|
64
|
+
/** 遅延 HTML。renderer が必要な場合のみ呼ぶ。 */
|
|
65
|
+
html(): Promise<string>;
|
|
66
|
+
/** 遅延 Markdown。 */
|
|
67
|
+
markdown(): Promise<string>;
|
|
68
|
+
}
|
|
69
|
+
/** 画像参照 (DataSource.resolveImageUrl に渡す)。 */
|
|
70
|
+
interface ImageRef {
|
|
71
|
+
/** 元の Notion 画像 URL (期限切れの可能性あり)。 */
|
|
72
|
+
originalUrl: string;
|
|
73
|
+
/** 関連するアイテム ID。 */
|
|
74
|
+
itemId?: string;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/types/data-source.d.ts
|
|
78
|
+
/**
|
|
79
|
+
* キャッシュ無効化のスコープ (DataSource 層で参照する形)。
|
|
80
|
+
*/
|
|
81
|
+
type InvalidateScope = "all" | {
|
|
82
|
+
collection: string;
|
|
83
|
+
} | {
|
|
84
|
+
collection: string;
|
|
85
|
+
slug: string;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Webhook 受信時の検証設定。
|
|
89
|
+
*/
|
|
90
|
+
interface WebhookConfig {
|
|
91
|
+
/** 署名検証用シークレット。 */
|
|
92
|
+
secret?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* コンテンツソースを抽象化する v1 インターフェース。
|
|
96
|
+
*
|
|
97
|
+
* ユーザーは直接実装しない。`notion-orm` 等の ORM パッケージが実装する。
|
|
98
|
+
* core は Notion 固有の知識を持たず、このインターフェース経由でのみデータを扱う。
|
|
99
|
+
* 将来 `googledocs-orm` 等の別ソースもこの I/F を満たせば差し替え可能。
|
|
100
|
+
*/
|
|
101
|
+
interface DataSource<T extends BaseContentItem = BaseContentItem> {
|
|
102
|
+
/** ソース識別子 (ロギング・デバッグ用)。 */
|
|
103
|
+
readonly name: string;
|
|
104
|
+
/** 公開扱いするステータス値 (ORM 側デフォルト)。 */
|
|
105
|
+
readonly publishedStatuses?: readonly string[];
|
|
106
|
+
/** アクセス許可するステータス値 (ORM 側デフォルト)。 */
|
|
107
|
+
readonly accessibleStatuses?: readonly string[];
|
|
108
|
+
/** 公開済みアイテム一覧を取得する。 */
|
|
109
|
+
list(opts?: {
|
|
110
|
+
publishedStatuses?: readonly string[];
|
|
111
|
+
}): Promise<T[]>;
|
|
112
|
+
/** スラッグで単件取得。見つからなければ null。 */
|
|
113
|
+
findBySlug(slug: string): Promise<T | null>;
|
|
114
|
+
/** アイテム本文を ContentBlock 配列で返す。 */
|
|
115
|
+
loadBlocks(item: T): Promise<ContentBlock[]>;
|
|
116
|
+
/** アイテム本文を Markdown 文字列で返す (html() 生成の元ソース)。 */
|
|
117
|
+
loadMarkdown(item: T): Promise<string>;
|
|
118
|
+
/** SWR 鮮度判定用。item の最終更新タイムスタンプ。 */
|
|
119
|
+
getLastModified(item: T): string;
|
|
120
|
+
/** リスト全体のバージョン文字列 (例: 最新 last_edited_time)。 */
|
|
121
|
+
getListVersion(items: T[]): string;
|
|
122
|
+
/** 期限切れ画像 URL の再取得 (Notion の署名 URL 対応)。 */
|
|
123
|
+
resolveImageUrl?(ref: ImageRef): Promise<string>;
|
|
124
|
+
/**
|
|
125
|
+
* Webhook リクエストをパースして無効化スコープを返す。
|
|
126
|
+
* 実装していない場合は `$handler` が body の `{ slug }` にフォールバック。
|
|
127
|
+
*/
|
|
128
|
+
parseWebhook?(req: Request, config: WebhookConfig): Promise<InvalidateScope>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* `nhcSchema` の各コレクション設定エントリ。
|
|
132
|
+
* ユーザーは CLI 生成の `nhcSchema` を渡すだけで、
|
|
133
|
+
* この型は `createCMS` 内部で DataSource のファクトリに渡される。
|
|
134
|
+
*/
|
|
135
|
+
interface CollectionConfig<T extends BaseContentItem = BaseContentItem> {
|
|
136
|
+
/** Notion データソース (database) ID。 */
|
|
137
|
+
databaseId: string;
|
|
138
|
+
/** スキーマ情報 (ORM が解釈する不透明データ)。 */
|
|
139
|
+
schema?: any;
|
|
140
|
+
/** 公開扱いするステータス値。 */
|
|
141
|
+
publishedStatuses?: string[];
|
|
142
|
+
/** アクセス許可するステータス値。 */
|
|
143
|
+
accessibleStatuses?: string[];
|
|
144
|
+
/** `T` を型レベルで持ち回るためのマーカー (ランタイム値なし)。 */
|
|
145
|
+
__itemType?: T;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `nhc generate` が生成する `nhcSchema` の型。
|
|
149
|
+
* コレクション名をキーとして、各コレクションの設定を保持する。
|
|
150
|
+
*/
|
|
151
|
+
type CMSSchema = Record<string, CollectionConfig<any>>;
|
|
152
|
+
/** `CollectionConfig<T>` から `T` を抽出するユーティリティ型。 */
|
|
153
|
+
type InferCollectionItem<C> = C extends CollectionConfig<infer T> ? T : BaseContentItem;
|
|
154
|
+
/**
|
|
155
|
+
* DataSource を生成するファクトリ関数の型。
|
|
156
|
+
* `createCMS` はコレクション名 → この関数 → DataSource の経路で組み立てる。
|
|
157
|
+
*
|
|
158
|
+
* ユーザーコードは直接呼ばない。`@notion-headless-cms/notion-orm` 等の
|
|
159
|
+
* ORM パッケージが provide する。ORM 固有のオプション (Notion なら
|
|
160
|
+
* `{ token }`、Google Docs なら `{ credentials }` 等) は `TOptions` の
|
|
161
|
+
* generic で受け取る。
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* // notion-orm が DataSourceFactory<{ token: string }> として実装する
|
|
165
|
+
* const factory: DataSourceFactory<{ token: string }> = ({ collection, config, options }) =>
|
|
166
|
+
* createNotionCollection({
|
|
167
|
+
* token: options.token,
|
|
168
|
+
* dataSourceId: config.databaseId,
|
|
169
|
+
* schema: config.schema,
|
|
170
|
+
* });
|
|
171
|
+
*/
|
|
172
|
+
type DataSourceFactory<TOptions = unknown> = <T extends BaseContentItem>(args: {
|
|
173
|
+
collection: string;
|
|
174
|
+
config: CollectionConfig<T>;
|
|
175
|
+
options: TOptions;
|
|
176
|
+
}) => DataSource<T>;
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/types/cache.d.ts
|
|
179
|
+
/** ドキュメントキャッシュを抽象化するインターフェース。 */
|
|
180
|
+
interface DocumentCacheAdapter<T extends BaseContentItem = BaseContentItem> {
|
|
181
|
+
readonly name: string;
|
|
182
|
+
getList(): Promise<CachedItemList<T> | null>;
|
|
183
|
+
setList(data: CachedItemList<T>): Promise<void>;
|
|
184
|
+
getItem(slug: string): Promise<CachedItem<T> | null>;
|
|
185
|
+
setItem(slug: string, data: CachedItem<T>): Promise<void>;
|
|
186
|
+
invalidate?(scope: InvalidateScope): Promise<void>;
|
|
187
|
+
}
|
|
188
|
+
/** 画像キャッシュを抽象化するインターフェース。 */
|
|
189
|
+
interface ImageCacheAdapter {
|
|
190
|
+
readonly name: string;
|
|
191
|
+
get(hash: string): Promise<StorageBinary | null>;
|
|
192
|
+
set(hash: string, data: ArrayBuffer, contentType: string): Promise<void>;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* キャッシュ設定。`"disabled"` を渡すと完全にキャッシュを無効化する。
|
|
196
|
+
* オブジェクトの場合、document / image それぞれ独立したアダプタを差し込める。
|
|
197
|
+
*/
|
|
198
|
+
type CacheConfig<T extends BaseContentItem = BaseContentItem> = "disabled" | {
|
|
199
|
+
document?: DocumentCacheAdapter<T>;
|
|
200
|
+
image?: ImageCacheAdapter; /** キャッシュの有効期間(ミリ秒)。未設定の場合はTTLなし。 */
|
|
201
|
+
ttlMs?: number;
|
|
202
|
+
};
|
|
203
|
+
//#endregion
|
|
204
|
+
export { CollectionConfig as a, InferCollectionItem as c, ContentBlock as d, ContentResult as f, CMSSchema as i, InvalidateScope as l, InlineNode as m, DocumentCacheAdapter as n, DataSource as o, ImageRef as p, ImageCacheAdapter as r, DataSourceFactory as s, CacheConfig as t, WebhookConfig as u };
|
|
205
|
+
//# sourceMappingURL=cache-Av7HRw_s.d.mts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/types/content.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* ライブラリが動作するために必須なフィールド。
|
|
4
|
+
* 利用者はこのインターフェースを拡張して独自のコンテンツ型を定義する。
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* interface Post extends BaseContentItem {
|
|
8
|
+
* title: string;
|
|
9
|
+
* author: string;
|
|
10
|
+
* }
|
|
11
|
+
* createCMS<Post>({ source: notionAdapter({ ... }) })
|
|
12
|
+
*/
|
|
13
|
+
interface BaseContentItem {
|
|
14
|
+
/** Notion ページ ID(変更検知に必須)。 */
|
|
15
|
+
id: string;
|
|
16
|
+
/** URL キー(必須)。 */
|
|
17
|
+
slug: string;
|
|
18
|
+
/** 最終更新タイムスタンプ(変更検知に必須)。 */
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
/** コンテンツのステータス。ステータスのない DB では省略可能。 */
|
|
21
|
+
status?: string;
|
|
22
|
+
/** 公開日時。日付プロパティのない DB では省略可能。 */
|
|
23
|
+
publishedAt?: string;
|
|
24
|
+
}
|
|
25
|
+
/** ストレージにキャッシュされたレンダリング済みコンテンツ。 */
|
|
26
|
+
interface CachedItem<T extends BaseContentItem = BaseContentItem> {
|
|
27
|
+
html: string;
|
|
28
|
+
item: T;
|
|
29
|
+
notionUpdatedAt: string;
|
|
30
|
+
cachedAt: number;
|
|
31
|
+
}
|
|
32
|
+
/** ストレージにキャッシュされたコンテンツ一覧。 */
|
|
33
|
+
interface CachedItemList<T extends BaseContentItem = BaseContentItem> {
|
|
34
|
+
items: T[];
|
|
35
|
+
cachedAt: number;
|
|
36
|
+
}
|
|
37
|
+
/** ストレージから取得したバイナリオブジェクト。 */
|
|
38
|
+
interface StorageBinary {
|
|
39
|
+
data: ArrayBuffer;
|
|
40
|
+
contentType?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Notionのプロパティ名マッピング(すべてオプション)。 */
|
|
43
|
+
interface CMSSchemaProperties {
|
|
44
|
+
/** Notionのスラッグプロパティ名。デフォルト: 'Slug' */
|
|
45
|
+
slug?: string;
|
|
46
|
+
/** Notionのステータスプロパティ名。デフォルト: 'Status' */
|
|
47
|
+
status?: string;
|
|
48
|
+
/** Notionの公開日プロパティ名。デフォルト: 'CreatedAt' */
|
|
49
|
+
date?: string;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { StorageBinary as a, CachedItemList as i, CMSSchemaProperties as n, CachedItem as r, BaseContentItem as t };
|
|
53
|
+
//# sourceMappingURL=content-BrwEY2_p.d.mts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/errors.d.ts
|
|
2
|
+
type BuiltInCMSErrorCode = "core/config_invalid" | "core/schema_invalid" | "core/notion_orm_missing" | "source/fetch_items_failed" | "source/fetch_item_failed" | "source/load_markdown_failed" | "cache/io_failed" | "cache/image_fetch_failed" | "renderer/failed" | "cli/config_invalid" | "cli/config_load_failed" | "cli/schema_invalid" | "cli/generate_failed" | "cli/init_failed" | "cli/notion_api_failed" | "cli/env_file_not_found";
|
|
3
|
+
/**
|
|
4
|
+
* CMS エラーコード。
|
|
5
|
+
* `BuiltInCMSErrorCode` のリテラル補完を維持しつつ、
|
|
6
|
+
* サードパーティアダプタが独自コードを定義できるよう `string & {}` で拡張可能にする。
|
|
7
|
+
*/
|
|
8
|
+
type CMSErrorCode = BuiltInCMSErrorCode | (string & {});
|
|
9
|
+
interface CMSErrorContext {
|
|
10
|
+
operation: string;
|
|
11
|
+
slug?: string;
|
|
12
|
+
dataSourceId?: string;
|
|
13
|
+
pageId?: string;
|
|
14
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
15
|
+
}
|
|
16
|
+
declare class CMSError extends Error {
|
|
17
|
+
readonly code: CMSErrorCode;
|
|
18
|
+
readonly cause?: unknown;
|
|
19
|
+
readonly context: CMSErrorContext;
|
|
20
|
+
constructor(params: {
|
|
21
|
+
code: CMSErrorCode;
|
|
22
|
+
message: string;
|
|
23
|
+
cause?: unknown;
|
|
24
|
+
context: CMSErrorContext;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
declare function isCMSError(error: unknown): error is CMSError;
|
|
28
|
+
/** エラーコードが特定の名前空間に属するかを判定する(例: "source/")。 */
|
|
29
|
+
declare function isCMSErrorInNamespace(error: unknown, namespace: string): error is CMSError;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { CMSError, CMSErrorCode, CMSErrorContext, isCMSError, isCMSErrorInNamespace };
|
|
32
|
+
//# sourceMappingURL=errors.d.mts.map
|
package/dist/errors.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region 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
|
+
/** エラーコードが特定の名前空間に属するかを判定する(例: "source/")。 */
|
|
18
|
+
function isCMSErrorInNamespace(error, namespace) {
|
|
19
|
+
return isCMSError(error) && error.code.startsWith(namespace);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { CMSError, isCMSError, isCMSErrorInNamespace };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["type BuiltInCMSErrorCode =\n\t| \"core/config_invalid\"\n\t| \"core/schema_invalid\"\n\t| \"core/notion_orm_missing\"\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\t| \"cli/config_invalid\"\n\t| \"cli/config_load_failed\"\n\t| \"cli/schema_invalid\"\n\t| \"cli/generate_failed\"\n\t| \"cli/init_failed\"\n\t| \"cli/notion_api_failed\"\n\t| \"cli/env_file_not_found\";\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":";AAiCA,IAAa,WAAb,cAA8B,MAAM;CACnC;CACA;CACA;CAEA,YAAY,QAKT;AACF,QAAM,OAAO,SAAS,EAAE,OAAO,OAAO,OAAO,CAAC;AAC9C,OAAK,OAAO;AACZ,OAAK,OAAO,OAAO;AACnB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;;;AAIxB,SAAgB,WAAW,OAAmC;AAC7D,QAAO,iBAAiB;;;AAIzB,SAAgB,sBACf,OACA,WACoB;AACpB,QAAO,WAAW,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { r as CachedItem, t as BaseContentItem } from "./content-BrwEY2_p.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/types/hooks.d.ts
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
interface CMSHooks<T extends BaseContentItem = BaseContentItem> {
|
|
6
|
+
beforeCache?: (item: CachedItem<T>) => MaybePromise<CachedItem<T>>;
|
|
7
|
+
afterRender?: (html: string, item: T) => MaybePromise<string>;
|
|
8
|
+
onCacheHit?: (slug: string, item: CachedItem<T>) => void;
|
|
9
|
+
onCacheMiss?: (slug: string) => void;
|
|
10
|
+
onListCacheHit?: (items: T[], cachedAt: number) => void;
|
|
11
|
+
onListCacheMiss?: () => void;
|
|
12
|
+
onError?: (error: Error) => void;
|
|
13
|
+
onRenderStart?: (slug: string) => void;
|
|
14
|
+
onRenderEnd?: (slug: string, durationMs: number) => void;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/types/logger.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* ログの付加情報。よく使うフィールドを型安全に渡せるようにしつつ、
|
|
20
|
+
* 任意の拡張フィールドは `[key: string]: unknown` で許容する。
|
|
21
|
+
*/
|
|
22
|
+
interface LogContext {
|
|
23
|
+
operation?: string;
|
|
24
|
+
slug?: string;
|
|
25
|
+
pageId?: string;
|
|
26
|
+
durationMs?: number;
|
|
27
|
+
attempt?: number;
|
|
28
|
+
status?: number;
|
|
29
|
+
error?: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
interface Logger {
|
|
33
|
+
debug?: (message: string, context?: LogContext) => void;
|
|
34
|
+
info?: (message: string, context?: LogContext) => void;
|
|
35
|
+
warn?: (message: string, context?: LogContext) => void;
|
|
36
|
+
error?: (message: string, context?: LogContext) => void;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/types/plugin.d.ts
|
|
40
|
+
interface CMSPlugin<T extends BaseContentItem = BaseContentItem> {
|
|
41
|
+
name: string;
|
|
42
|
+
hooks?: CMSHooks<T>;
|
|
43
|
+
logger?: Partial<Logger>;
|
|
44
|
+
}
|
|
45
|
+
declare function definePlugin<T extends BaseContentItem>(plugin: CMSPlugin<T>): CMSPlugin<T>;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/hooks.d.ts
|
|
48
|
+
/**
|
|
49
|
+
* プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。
|
|
50
|
+
* beforeCache / afterRender はパイプライン(前の出力が次の入力)。
|
|
51
|
+
* onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
|
|
52
|
+
*/
|
|
53
|
+
declare function mergeHooks<T extends BaseContentItem>(plugins: CMSPlugin<T>[], directHooks?: CMSHooks<T>, logger?: Logger): CMSHooks<T>;
|
|
54
|
+
/** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */
|
|
55
|
+
declare function mergeLoggers(plugins: Array<{
|
|
56
|
+
logger?: Partial<Logger>;
|
|
57
|
+
}>, directLogger?: Logger): Logger | undefined;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { Logger as a, definePlugin as i, mergeLoggers as n, CMSHooks as o, CMSPlugin as r, MaybePromise as s, mergeHooks as t };
|
|
60
|
+
//# sourceMappingURL=hooks-DCSAQkST.d.mts.map
|
package/dist/hooks.d.mts
ADDED
package/dist/hooks.mjs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/hooks.ts
|
|
2
|
+
/**
|
|
3
|
+
* プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。
|
|
4
|
+
* beforeCache / afterRender はパイプライン(前の出力が次の入力)。
|
|
5
|
+
* onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。
|
|
6
|
+
*/
|
|
7
|
+
function mergeHooks(plugins, directHooks, logger) {
|
|
8
|
+
const allHooks = [...plugins.map((p) => p.hooks ?? {}), ...directHooks ? [directHooks] : []];
|
|
9
|
+
if (allHooks.length === 0) return {};
|
|
10
|
+
return {
|
|
11
|
+
beforeCache: buildPipeline(allHooks, "beforeCache"),
|
|
12
|
+
afterRender: buildRenderPipeline(allHooks),
|
|
13
|
+
onCacheHit: buildObserver(allHooks, "onCacheHit", logger),
|
|
14
|
+
onCacheMiss: buildObserver(allHooks, "onCacheMiss", logger),
|
|
15
|
+
onListCacheHit: buildObserver(allHooks, "onListCacheHit", logger),
|
|
16
|
+
onListCacheMiss: buildObserver(allHooks, "onListCacheMiss", logger),
|
|
17
|
+
onError: buildObserver(allHooks, "onError", logger),
|
|
18
|
+
onRenderStart: buildObserver(allHooks, "onRenderStart", logger),
|
|
19
|
+
onRenderEnd: buildObserver(allHooks, "onRenderEnd", logger)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function buildPipeline(hooks, key) {
|
|
23
|
+
const fns = hooks.map((h) => h[key]).filter(Boolean);
|
|
24
|
+
if (fns.length === 0) return void 0;
|
|
25
|
+
return async (item) => {
|
|
26
|
+
let current = item;
|
|
27
|
+
for (const fn of fns) current = await fn(current);
|
|
28
|
+
return current;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function buildRenderPipeline(hooks) {
|
|
32
|
+
const fns = hooks.map((h) => h.afterRender).filter(Boolean);
|
|
33
|
+
if (fns.length === 0) return void 0;
|
|
34
|
+
return async (html, item) => {
|
|
35
|
+
let current = html;
|
|
36
|
+
for (const fn of fns) current = await fn(current, item);
|
|
37
|
+
return current;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function buildObserver(hooks, key, logger) {
|
|
41
|
+
const fns = hooks.map((h) => h[key]).filter(Boolean);
|
|
42
|
+
if (fns.length === 0) return void 0;
|
|
43
|
+
return ((...args) => {
|
|
44
|
+
for (const fn of fns) try {
|
|
45
|
+
fn(...args);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
logger?.error?.("観測フックで例外が発生", {
|
|
48
|
+
hook: String(key),
|
|
49
|
+
error: err instanceof Error ? err.message : String(err)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */
|
|
55
|
+
function mergeLoggers(plugins, directLogger) {
|
|
56
|
+
const loggers = [...plugins.map((p) => p.logger ?? {}), ...directLogger ? [directLogger] : []];
|
|
57
|
+
if (loggers.length === 0) return void 0;
|
|
58
|
+
const merged = {};
|
|
59
|
+
for (const level of [
|
|
60
|
+
"debug",
|
|
61
|
+
"info",
|
|
62
|
+
"warn",
|
|
63
|
+
"error"
|
|
64
|
+
]) {
|
|
65
|
+
const fns = loggers.map((l) => l[level]).filter(Boolean);
|
|
66
|
+
if (fns.length > 0) merged[level] = (message, context) => {
|
|
67
|
+
for (const fn of fns) fn(message, context);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return merged;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { mergeHooks, mergeLoggers };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=hooks.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.mjs","names":[],"sources":["../src/hooks.ts"],"sourcesContent":["import type { BaseContentItem } from \"./types/content\";\nimport type { CMSHooks, MaybePromise } from \"./types/hooks\";\nimport type { Logger } from \"./types/logger\";\nimport type { CMSPlugin } from \"./types/plugin\";\n\n/**\n * プラグイン配列とダイレクトフックを合成して単一の CMSHooks を返す。\n * beforeCache / afterRender はパイプライン(前の出力が次の入力)。\n * onCacheHit などオブザーバー系は全員に同じ値を渡し、例外は logger に流して握りつぶす。\n */\nexport function mergeHooks<T extends BaseContentItem>(\n\tplugins: CMSPlugin<T>[],\n\tdirectHooks?: CMSHooks<T>,\n\tlogger?: Logger,\n): CMSHooks<T> {\n\tconst allHooks: CMSHooks<T>[] = [\n\t\t...plugins.map((p) => p.hooks ?? {}),\n\t\t...(directHooks ? [directHooks] : []),\n\t];\n\n\tif (allHooks.length === 0) return {};\n\n\treturn {\n\t\tbeforeCache: buildPipeline(allHooks, \"beforeCache\"),\n\t\tafterRender: buildRenderPipeline(allHooks),\n\t\tonCacheHit: buildObserver(allHooks, \"onCacheHit\", logger),\n\t\tonCacheMiss: buildObserver(allHooks, \"onCacheMiss\", logger),\n\t\tonListCacheHit: buildObserver(allHooks, \"onListCacheHit\", logger),\n\t\tonListCacheMiss: buildObserver(allHooks, \"onListCacheMiss\", logger),\n\t\tonError: buildObserver(allHooks, \"onError\", logger),\n\t\tonRenderStart: buildObserver(allHooks, \"onRenderStart\", logger),\n\t\tonRenderEnd: buildObserver(allHooks, \"onRenderEnd\", logger),\n\t};\n}\n\nfunction buildPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n\tkey: \"beforeCache\",\n): CMSHooks<T>[\"beforeCache\"] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"beforeCache\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (item) => {\n\t\tlet current = item;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current) as MaybePromise<typeof item>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildRenderPipeline<T extends BaseContentItem>(\n\thooks: CMSHooks<T>[],\n): CMSHooks<T>[\"afterRender\"] {\n\tconst fns = hooks.map((h) => h.afterRender).filter(Boolean) as NonNullable<\n\t\tCMSHooks<T>[\"afterRender\"]\n\t>[];\n\tif (fns.length === 0) return undefined;\n\treturn async (html, item) => {\n\t\tlet current = html;\n\t\tfor (const fn of fns) {\n\t\t\tcurrent = await (fn(current, item) as MaybePromise<string>);\n\t\t}\n\t\treturn current;\n\t};\n}\n\nfunction buildObserver<T extends BaseContentItem, K extends keyof CMSHooks<T>>(\n\thooks: CMSHooks<T>[],\n\tkey: K,\n\tlogger?: Logger,\n): CMSHooks<T>[K] {\n\tconst fns = hooks.map((h) => h[key]).filter(Boolean);\n\tif (fns.length === 0) return undefined;\n\treturn ((...args: unknown[]) => {\n\t\tfor (const fn of fns) {\n\t\t\ttry {\n\t\t\t\t(fn as (...a: unknown[]) => void)(...args);\n\t\t\t} catch (err) {\n\t\t\t\tlogger?.error?.(\"観測フックで例外が発生\", {\n\t\t\t\t\thook: String(key),\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}) as CMSHooks<T>[K];\n}\n\n/** プラグイン配列とダイレクトロガーを合成して単一の Logger を返す。 */\nexport function mergeLoggers(\n\tplugins: Array<{ logger?: Partial<Logger> }>,\n\tdirectLogger?: Logger,\n): Logger | undefined {\n\tconst loggers: Partial<Logger>[] = [\n\t\t...plugins.map((p) => p.logger ?? {}),\n\t\t...(directLogger ? [directLogger] : []),\n\t];\n\tif (loggers.length === 0) return undefined;\n\n\tconst merged: Logger = {};\n\tfor (const level of [\"debug\", \"info\", \"warn\", \"error\"] as const) {\n\t\tconst fns = loggers.map((l) => l[level]).filter(Boolean) as NonNullable<\n\t\t\tLogger[typeof level]\n\t\t>[];\n\t\tif (fns.length > 0) {\n\t\t\tmerged[level] = (message, context) => {\n\t\t\t\tfor (const fn of fns) fn(message, context);\n\t\t\t};\n\t\t}\n\t}\n\treturn merged;\n}\n"],"mappings":";;;;;;AAUA,SAAgB,WACf,SACA,aACA,QACc;CACd,MAAM,WAA0B,CAC/B,GAAG,QAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,CAAC,EACpC,GAAI,cAAc,CAAC,YAAY,GAAG,EAAE,CACpC;AAED,KAAI,SAAS,WAAW,EAAG,QAAO,EAAE;AAEpC,QAAO;EACN,aAAa,cAAc,UAAU,cAAc;EACnD,aAAa,oBAAoB,SAAS;EAC1C,YAAY,cAAc,UAAU,cAAc,OAAO;EACzD,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D,gBAAgB,cAAc,UAAU,kBAAkB,OAAO;EACjE,iBAAiB,cAAc,UAAU,mBAAmB,OAAO;EACnE,SAAS,cAAc,UAAU,WAAW,OAAO;EACnD,eAAe,cAAc,UAAU,iBAAiB,OAAO;EAC/D,aAAa,cAAc,UAAU,eAAe,OAAO;EAC3D;;AAGF,SAAS,cACR,OACA,KAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AAGpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,SAAS;EACtB,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,QAAQ;AAE7B,SAAO;;;AAIT,SAAS,oBACR,OAC6B;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,YAAY,CAAC,OAAO,QAAQ;AAG3D,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,QAAO,OAAO,MAAM,SAAS;EAC5B,IAAI,UAAU;AACd,OAAK,MAAM,MAAM,IAChB,WAAU,MAAO,GAAG,SAAS,KAAK;AAEnC,SAAO;;;AAIT,SAAS,cACR,OACA,KACA,QACiB;CACjB,MAAM,MAAM,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,QAAQ;AACpD,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,UAAS,GAAG,SAAoB;AAC/B,OAAK,MAAM,MAAM,IAChB,KAAI;AACF,MAAiC,GAAG,KAAK;WAClC,KAAK;AACb,WAAQ,QAAQ,eAAe;IAC9B,MAAM,OAAO,IAAI;IACjB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,CAAC;;;;;AAON,SAAgB,aACf,SACA,cACqB;CACrB,MAAM,UAA6B,CAClC,GAAG,QAAQ,KAAK,MAAM,EAAE,UAAU,EAAE,CAAC,EACrC,GAAI,eAAe,CAAC,aAAa,GAAG,EAAE,CACtC;AACD,KAAI,QAAQ,WAAW,EAAG,QAAO,KAAA;CAEjC,MAAM,SAAiB,EAAE;AACzB,MAAK,MAAM,SAAS;EAAC;EAAS;EAAQ;EAAQ;EAAQ,EAAW;EAChE,MAAM,MAAM,QAAQ,KAAK,MAAM,EAAE,OAAO,CAAC,OAAO,QAAQ;AAGxD,MAAI,IAAI,SAAS,EAChB,QAAO,UAAU,SAAS,YAAY;AACrC,QAAK,MAAM,MAAM,IAAK,IAAG,SAAS,QAAQ;;;AAI7C,QAAO"}
|