@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 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
- // コンテンツ一覧を取得(R2 から返すか Notion API を呼ぶかはキャッシュ状態に依存)
27
- const { items } = await cms.getItems(env);
30
+ // コンテンツ一覧を取得
31
+ const { items } = await cms.getItems();
28
32
 
29
33
  // スラッグで個別コンテンツを取得(HTML 付き)
30
- const cached = await cms.getItemBySlug("my-post", env);
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(env)` | コンテンツ一覧を返す(キャッシュ優先) |
69
- | `getItemBySlug(slug, env)` | スラッグで個別コンテンツを返す(HTML 付き) |
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
- * schema: { properties: { slug: 'Slug' }, publishedStatuses: ['Published'] }
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
- createClient(env: Pick<CMSEnv, "NOTION_TOKEN">): Client;
158
+ private requireClient;
154
159
  /** 公開済みコンテンツ一覧を Notion から直接取得する。 */
155
- getItems(env: CMSEnv): Promise<T[]>;
160
+ getItems(): Promise<T[]>;
156
161
  /** スラッグでコンテンツを Notion から直接取得する。 */
157
- getItemBySlug(env: CMSEnv, slug: string): Promise<T | null>;
162
+ getItemBySlug(slug: string): Promise<T | null>;
158
163
  /** アイテムが publishedStatuses に含まれるステータスかどうかを返す。 */
159
164
  isPublished(item: T): boolean;
160
165
  /** コンテンツをMarkdown→HTMLにレンダリングし、CachedItemとして返す。 */
161
- renderItem(env: CMSEnv, item: T): Promise<CachedItem<T>>;
166
+ renderItem(item: T): Promise<CachedItem<T>>;
162
167
  /** スラッグでコンテンツを取得してMarkdown→HTMLにレンダリングする。 */
163
- renderItemBySlug(env: CMSEnv, slug: string): Promise<CachedItem<T> | null>;
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(env: CMSEnv, options?: {
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(env: CMSEnv, slug: string, options?: {
181
+ getItemCachedFirst(slug: string, options?: {
177
182
  waitUntil?: (promise: Promise<void>) => void;
178
183
  }): Promise<CachedItem<T> | null>;
179
- checkItemsUpdate(env: CMSEnv, clientVersion: string): Promise<{
184
+ checkItemsUpdate(clientVersion: string): Promise<{
180
185
  changed: false;
181
186
  } | {
182
187
  changed: true;
183
188
  items: T[];
184
189
  }>;
185
- checkItemUpdate(env: CMSEnv, slug: string, lastEdited: string): Promise<{
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
- // ── Notionクライアント生成 ───────────────────────────────────────────────
223
- createClient(env) {
224
- return createClient(env);
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(env) {
229
- const { dataSourceId } = validateEnv(env);
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(env, slug) {
250
- const { dataSourceId } = validateEnv(env);
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(env, item) {
282
- validateEnv(env);
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(env, slug) {
288
- const { dataSourceId } = validateEnv(env);
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(env, options) {
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(env);
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(env, slug, options) {
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(env, slug);
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(env, clientVersion) {
369
- const items = await this.getItems(env);
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(env, slug, lastEdited) {
376
- const item = await this.getItemBySlug(env, slug);
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(env, slug);
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.1",
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.1",
46
- "@notion-headless-cms/transformer": "0.0.1",
47
- "@notion-headless-cms/renderer": "0.0.1"
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",