@notion-headless-cms/core 0.0.1 → 0.0.5
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 +20 -6
- package/dist/index.d.ts +16 -11
- package/dist/index.js +34 -24
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ Cloudflare Workers で使う場合は [`@notion-headless-cms/adapter-cloudflare`
|
|
|
16
16
|
import { CMS } from "@notion-headless-cms/core";
|
|
17
17
|
|
|
18
18
|
const cms = new CMS({
|
|
19
|
+
env: {
|
|
20
|
+
NOTION_TOKEN: process.env.NOTION_TOKEN!,
|
|
21
|
+
NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID!,
|
|
22
|
+
},
|
|
19
23
|
schema: {
|
|
20
24
|
publishedStatuses: ["公開"],
|
|
21
25
|
properties: { slug: "Slug" },
|
|
@@ -23,11 +27,11 @@ const cms = new CMS({
|
|
|
23
27
|
cache: { ttlMs: 5 * 60 * 1000 },
|
|
24
28
|
});
|
|
25
29
|
|
|
26
|
-
//
|
|
27
|
-
const { items } = await cms.getItems(
|
|
30
|
+
// コンテンツ一覧を取得
|
|
31
|
+
const { items } = await cms.getItems();
|
|
28
32
|
|
|
29
33
|
// スラッグで個別コンテンツを取得(HTML 付き)
|
|
30
|
-
const cached = await cms.getItemBySlug("my-post"
|
|
34
|
+
const cached = await cms.getItemBySlug("my-post");
|
|
31
35
|
console.log(cached?.html);
|
|
32
36
|
```
|
|
33
37
|
|
|
@@ -45,6 +49,10 @@ interface MyPost extends BaseContentItem {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
const config: CMSConfig<MyPost> = {
|
|
52
|
+
env: {
|
|
53
|
+
NOTION_TOKEN: process.env.NOTION_TOKEN!,
|
|
54
|
+
NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID!,
|
|
55
|
+
},
|
|
48
56
|
schema: {
|
|
49
57
|
mapItem: (page: PageObjectResponse): MyPost => ({
|
|
50
58
|
id: page.id,
|
|
@@ -65,8 +73,14 @@ const config: CMSConfig<MyPost> = {
|
|
|
65
73
|
|
|
66
74
|
| メソッド | 説明 |
|
|
67
75
|
|---|---|
|
|
68
|
-
| `getItems(
|
|
69
|
-
| `getItemBySlug(slug
|
|
76
|
+
| `getItems()` | コンテンツ一覧を返す |
|
|
77
|
+
| `getItemBySlug(slug)` | スラッグで個別コンテンツを返す |
|
|
78
|
+
| `renderItem(item)` | アイテムをレンダリングして `CachedItem` を返す |
|
|
79
|
+
| `renderItemBySlug(slug)` | スラッグで取得してレンダリングする |
|
|
80
|
+
| `getItemsCachedFirst(options?)` | キャッシュ優先でコンテンツ一覧を返す(SWR) |
|
|
81
|
+
| `getItemCachedFirst(slug, options?)` | キャッシュ優先で個別コンテンツを返す(SWR) |
|
|
82
|
+
| `checkItemsUpdate(clientVersion)` | 一覧の更新有無を確認する |
|
|
83
|
+
| `checkItemUpdate(slug, lastEdited)` | 個別コンテンツの更新有無を確認する |
|
|
70
84
|
|
|
71
85
|
### ユーティリティ
|
|
72
86
|
|
|
@@ -79,7 +93,7 @@ const config: CMSConfig<MyPost> = {
|
|
|
79
93
|
|
|
80
94
|
## 主要な型
|
|
81
95
|
|
|
82
|
-
- `CMSConfig<T>` — CMS
|
|
96
|
+
- `CMSConfig<T>` — CMS 設定オブジェクト(`env` フィールドで認証情報を渡す)
|
|
83
97
|
- `BaseContentItem` — デフォルト・カスタム型の基底インターフェース
|
|
84
98
|
- `CachedItem<T>` — キャッシュ済みコンテンツ(HTML + メタデータ)
|
|
85
99
|
- `StorageAdapter` — ストレージ抽象インターフェース
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { RendererFn } from '@notion-headless-cms/renderer';
|
|
|
2
2
|
import { BlockHandler } from '@notion-headless-cms/transformer';
|
|
3
3
|
import { PageObjectResponse, RichTextItemResponse } from '@notionhq/client/build/src/api-endpoints';
|
|
4
4
|
import { PluggableList } from 'unified';
|
|
5
|
-
import { Client } from '@notionhq/client';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* ライブラリが動作するために必須なフィールド。
|
|
@@ -67,6 +66,8 @@ interface CMSSchemaProperties {
|
|
|
67
66
|
* ジェネリクス型 T にカスタムコンテンツ型を指定できる(デフォルト: BaseContentItem)。
|
|
68
67
|
*/
|
|
69
68
|
interface CMSConfig<T extends BaseContentItem = BaseContentItem> {
|
|
69
|
+
/** Notion API 認証情報。コンストラクタでクライアントを事前生成するために使用。 */
|
|
70
|
+
env?: CMSEnv;
|
|
70
71
|
/** キャッシュ/画像保存用ストレージ。未設定時はキャッシュ機能を無効化。 */
|
|
71
72
|
storage?: StorageAdapter;
|
|
72
73
|
schema?: {
|
|
@@ -135,8 +136,10 @@ declare class CacheStore<T extends BaseContentItem = BaseContentItem> {
|
|
|
135
136
|
*
|
|
136
137
|
* @example
|
|
137
138
|
* const cms = createCMS({
|
|
138
|
-
*
|
|
139
|
+
* env: { NOTION_TOKEN: '...', NOTION_DATA_SOURCE_ID: '...' },
|
|
140
|
+
* schema: { publishedStatuses: ['Published'] }
|
|
139
141
|
* });
|
|
142
|
+
* const items = await cms.getItems();
|
|
140
143
|
*/
|
|
141
144
|
declare class CMS<T extends BaseContentItem = BaseContentItem> {
|
|
142
145
|
private readonly itemMapper;
|
|
@@ -149,40 +152,42 @@ declare class CMS<T extends BaseContentItem = BaseContentItem> {
|
|
|
149
152
|
private readonly store;
|
|
150
153
|
private readonly hasStorage;
|
|
151
154
|
private readonly ttlMs;
|
|
155
|
+
private readonly client;
|
|
156
|
+
private readonly dataSourceId;
|
|
152
157
|
constructor(config?: CMSConfig<T>);
|
|
153
|
-
|
|
158
|
+
private requireClient;
|
|
154
159
|
/** 公開済みコンテンツ一覧を Notion から直接取得する。 */
|
|
155
|
-
getItems(
|
|
160
|
+
getItems(): Promise<T[]>;
|
|
156
161
|
/** スラッグでコンテンツを Notion から直接取得する。 */
|
|
157
|
-
getItemBySlug(
|
|
162
|
+
getItemBySlug(slug: string): Promise<T | null>;
|
|
158
163
|
/** アイテムが publishedStatuses に含まれるステータスかどうかを返す。 */
|
|
159
164
|
isPublished(item: T): boolean;
|
|
160
165
|
/** コンテンツをMarkdown→HTMLにレンダリングし、CachedItemとして返す。 */
|
|
161
|
-
renderItem(
|
|
166
|
+
renderItem(item: T): Promise<CachedItem<T>>;
|
|
162
167
|
/** スラッグでコンテンツを取得してMarkdown→HTMLにレンダリングする。 */
|
|
163
|
-
renderItemBySlug(
|
|
168
|
+
renderItemBySlug(slug: string): Promise<CachedItem<T> | null>;
|
|
164
169
|
getCachedItemList(): Promise<CachedItemList<T> | null>;
|
|
165
170
|
setCachedItemList(items: T[]): Promise<void>;
|
|
166
171
|
getCachedItem(slug: string): Promise<CachedItem<T> | null>;
|
|
167
172
|
setCachedItem(slug: string, data: CachedItem<T>): Promise<void>;
|
|
168
173
|
getCachedImage(hash: string): Promise<StorageBinary | null>;
|
|
169
174
|
createCachedImageResponse(hash: string): Promise<Response | null>;
|
|
170
|
-
getItemsCachedFirst(
|
|
175
|
+
getItemsCachedFirst(options?: {
|
|
171
176
|
waitUntil?: (promise: Promise<void>) => void;
|
|
172
177
|
}): Promise<{
|
|
173
178
|
items: T[];
|
|
174
179
|
listVersion: string;
|
|
175
180
|
}>;
|
|
176
|
-
getItemCachedFirst(
|
|
181
|
+
getItemCachedFirst(slug: string, options?: {
|
|
177
182
|
waitUntil?: (promise: Promise<void>) => void;
|
|
178
183
|
}): Promise<CachedItem<T> | null>;
|
|
179
|
-
checkItemsUpdate(
|
|
184
|
+
checkItemsUpdate(clientVersion: string): Promise<{
|
|
180
185
|
changed: false;
|
|
181
186
|
} | {
|
|
182
187
|
changed: true;
|
|
183
188
|
items: T[];
|
|
184
189
|
}>;
|
|
185
|
-
checkItemUpdate(
|
|
190
|
+
checkItemUpdate(slug: string, lastEdited: string): Promise<{
|
|
186
191
|
changed: false;
|
|
187
192
|
} | {
|
|
188
193
|
changed: true;
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,8 @@ var CMS = class {
|
|
|
194
194
|
store;
|
|
195
195
|
hasStorage;
|
|
196
196
|
ttlMs;
|
|
197
|
+
client;
|
|
198
|
+
dataSourceId;
|
|
197
199
|
constructor(config) {
|
|
198
200
|
const props = {
|
|
199
201
|
...DEFAULT_PROPERTIES,
|
|
@@ -218,16 +220,27 @@ var CMS = class {
|
|
|
218
220
|
config?.cache?.itemPrefix ?? DEFAULT_ITEM_PREFIX,
|
|
219
221
|
config?.cache?.imagePrefix ?? DEFAULT_IMAGE_PREFIX
|
|
220
222
|
);
|
|
223
|
+
if (config?.env) {
|
|
224
|
+
const { dataSourceId } = validateEnv(config.env);
|
|
225
|
+
this.client = createClient(config.env);
|
|
226
|
+
this.dataSourceId = dataSourceId;
|
|
227
|
+
}
|
|
221
228
|
}
|
|
222
|
-
// ──
|
|
223
|
-
|
|
224
|
-
|
|
229
|
+
// ── プライベートヘルパー(認証) ──────────────────────────────────────
|
|
230
|
+
requireClient() {
|
|
231
|
+
if (!this.client || !this.dataSourceId) {
|
|
232
|
+
throw new CMSError({
|
|
233
|
+
code: "CONFIG_INVALID",
|
|
234
|
+
message: "NOTION_TOKEN \u3068 NOTION_DATA_SOURCE_ID \u306F CMS \u306E\u8A2D\u5B9A\u306B\u5FC5\u8981\u3067\u3059\u3002",
|
|
235
|
+
context: { operation: "requireClient" }
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return { client: this.client, dataSourceId: this.dataSourceId };
|
|
225
239
|
}
|
|
226
240
|
// ── コンテンツ取得 ────────────────────────────────────────────────────
|
|
227
241
|
/** 公開済みコンテンツ一覧を Notion から直接取得する。 */
|
|
228
|
-
async getItems(
|
|
229
|
-
const { dataSourceId } =
|
|
230
|
-
const client = createClient(env);
|
|
242
|
+
async getItems() {
|
|
243
|
+
const { client, dataSourceId } = this.requireClient();
|
|
231
244
|
try {
|
|
232
245
|
const pages = await queryAllPages(client, dataSourceId);
|
|
233
246
|
const items = pages.map(this.itemMapper);
|
|
@@ -246,9 +259,8 @@ var CMS = class {
|
|
|
246
259
|
}
|
|
247
260
|
}
|
|
248
261
|
/** スラッグでコンテンツを Notion から直接取得する。 */
|
|
249
|
-
async getItemBySlug(
|
|
250
|
-
const { dataSourceId } =
|
|
251
|
-
const client = createClient(env);
|
|
262
|
+
async getItemBySlug(slug) {
|
|
263
|
+
const { client, dataSourceId } = this.requireClient();
|
|
252
264
|
try {
|
|
253
265
|
const page = await queryPageBySlug(
|
|
254
266
|
client,
|
|
@@ -278,15 +290,13 @@ var CMS = class {
|
|
|
278
290
|
return this.publishedStatuses.includes(item.status);
|
|
279
291
|
}
|
|
280
292
|
/** コンテンツをMarkdown→HTMLにレンダリングし、CachedItemとして返す。 */
|
|
281
|
-
async renderItem(
|
|
282
|
-
|
|
283
|
-
const client = createClient(env);
|
|
293
|
+
async renderItem(item) {
|
|
294
|
+
const { client } = this.requireClient();
|
|
284
295
|
return this.buildCachedItem(client, item);
|
|
285
296
|
}
|
|
286
297
|
/** スラッグでコンテンツを取得してMarkdown→HTMLにレンダリングする。 */
|
|
287
|
-
async renderItemBySlug(
|
|
288
|
-
const { dataSourceId } =
|
|
289
|
-
const client = createClient(env);
|
|
298
|
+
async renderItemBySlug(slug) {
|
|
299
|
+
const { client, dataSourceId } = this.requireClient();
|
|
290
300
|
try {
|
|
291
301
|
const page = await queryPageBySlug(
|
|
292
302
|
client,
|
|
@@ -335,7 +345,7 @@ var CMS = class {
|
|
|
335
345
|
return new Response(object.data, { headers });
|
|
336
346
|
}
|
|
337
347
|
// ── キャッシュ優先取得(Stale-While-Revalidate) ─────────────────────
|
|
338
|
-
async getItemsCachedFirst(
|
|
348
|
+
async getItemsCachedFirst(options) {
|
|
339
349
|
const cached = await this.store.getItemList();
|
|
340
350
|
if (cached && !isStale(cached.cachedAt, this.ttlMs)) {
|
|
341
351
|
return {
|
|
@@ -343,7 +353,7 @@ var CMS = class {
|
|
|
343
353
|
listVersion: buildListVersion(cached.items)
|
|
344
354
|
};
|
|
345
355
|
}
|
|
346
|
-
const items = await this.getItems(
|
|
356
|
+
const items = await this.getItems();
|
|
347
357
|
const save = this.store.setItemList(items);
|
|
348
358
|
if (options?.waitUntil) {
|
|
349
359
|
options.waitUntil(save);
|
|
@@ -352,10 +362,10 @@ var CMS = class {
|
|
|
352
362
|
}
|
|
353
363
|
return { items, listVersion: buildListVersion(items) };
|
|
354
364
|
}
|
|
355
|
-
async getItemCachedFirst(
|
|
365
|
+
async getItemCachedFirst(slug, options) {
|
|
356
366
|
const cached = await this.store.getItem(slug);
|
|
357
367
|
if (cached && !isStale(cached.cachedAt, this.ttlMs)) return cached;
|
|
358
|
-
const entry = await this.renderItemBySlug(
|
|
368
|
+
const entry = await this.renderItemBySlug(slug);
|
|
359
369
|
if (!entry) return null;
|
|
360
370
|
const save = this.store.setItem(slug, entry);
|
|
361
371
|
if (options?.waitUntil) {
|
|
@@ -365,19 +375,19 @@ var CMS = class {
|
|
|
365
375
|
}
|
|
366
376
|
return entry;
|
|
367
377
|
}
|
|
368
|
-
async checkItemsUpdate(
|
|
369
|
-
const items = await this.getItems(
|
|
378
|
+
async checkItemsUpdate(clientVersion) {
|
|
379
|
+
const items = await this.getItems();
|
|
370
380
|
const serverVersion = buildListVersion(items);
|
|
371
381
|
if (serverVersion === clientVersion) return { changed: false };
|
|
372
382
|
await this.store.setItemList(items);
|
|
373
383
|
return { changed: true, items };
|
|
374
384
|
}
|
|
375
|
-
async checkItemUpdate(
|
|
376
|
-
const item = await this.getItemBySlug(
|
|
385
|
+
async checkItemUpdate(slug, lastEdited) {
|
|
386
|
+
const item = await this.getItemBySlug(slug);
|
|
377
387
|
if (!item) return { changed: false };
|
|
378
388
|
if (!this.isPublished(item)) return { changed: false };
|
|
379
389
|
if (item.updatedAt === lastEdited) return { changed: false };
|
|
380
|
-
const entry = await this.renderItemBySlug(
|
|
390
|
+
const entry = await this.renderItemBySlug(slug);
|
|
381
391
|
if (!entry) return { changed: false };
|
|
382
392
|
await this.store.setItem(slug, entry);
|
|
383
393
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notion-headless-cms/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Core CMS engine for notion-headless-cms — fetch, transform, cache with stale-while-revalidate strategy",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"notion",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"@notionhq/client": "^5.18.0",
|
|
43
43
|
"unified": "^11.0.5",
|
|
44
44
|
"zod": "^4.1.12",
|
|
45
|
-
"@notion-headless-cms/fetcher": "0.0.
|
|
46
|
-
"@notion-headless-cms/transformer": "0.0.
|
|
47
|
-
"@notion-headless-cms/renderer": "0.0.
|
|
45
|
+
"@notion-headless-cms/fetcher": "0.0.3",
|
|
46
|
+
"@notion-headless-cms/transformer": "0.0.3",
|
|
47
|
+
"@notion-headless-cms/renderer": "0.0.3"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsup src/index.ts --format esm --dts --out-dir dist",
|