@ryusenkei/maezae 0.10.0 → 0.12.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 CHANGED
@@ -21,6 +21,7 @@ maezae の SDK(導入先のサイト向け)
21
21
  - 鍵: 媒体の管理画面の「設定 → 連携」で発行した API の鍵
22
22
  - 1件の形: `{ id, ...項目, _meta: { created_at, updated_at, published_at } }`、published_at は公開の管理がある型だけ(0.7.0 から)
23
23
  - 一覧: 本文の種類の項目を含めない、本文は1件の取得か `/bundle` で
24
+ - 契約の版: パス `/content/v1` と、すべての応答のヘッダー `x-cms-version: 1`。非互換の変更はバージョン(版)を上げて行う(#495)
24
25
 
25
26
  ```ts
26
27
  import { createDeliveryClient } from '@ryusenkei/maezae/delivery';
@@ -54,6 +55,46 @@ const body = (await request.json()) as PublishCheckRequest;
54
55
 
55
56
  - Webhook の重複: 失敗した送信は同じ本文で再送する。本文の `id`(イベントの ID)を処理済みとして保存し、同じ `id` は処理せずに 2xx を返す
56
57
 
58
+ ## Webhook の本文と契約の版
59
+
60
+ 本文の `version` は契約の版(今は1だけ)。非互換の変更はバージョン(版)を上げて行う(#495)
61
+
62
+ ```json
63
+ { "version": 1, "id": "evt_…", "event": "published", "type": "column", "item": { "id": "…", "title": "…" }, "by": "…", "at": "2026-11-12T00:00:00.000Z" }
64
+ ```
65
+
66
+ - `event`: `submitted`(レビューの依頼)・`published`(公開)・`unpublished`(非公開)・`rejected`(差し戻し)。型は `type`(型の ID)で判断する(型の ID を変えても `event` は変わらない)
67
+ - `id`: イベントの ID(再送しても同じ値、重複の除去に使用)、`item`: 行の ID と題、`by`: 操作した人の表示名、`at`: 出来事の日時
68
+ - 拡張機能の出来事(例: お問い合わせの `contact.received`): `{ version: 1, id, event: "contact.received", data, at }`(接頭辞はモジュールの ID で型の ID ではない)
69
+ - 購読: 管理画面の「設定 → 連携」で、送る出来事と対象の型を別々に選ぶ。編集の出来事は「選んだ出来事 × 選んだ型」で届く
70
+
71
+ ```ts
72
+ import type { WebhookBody } from '@ryusenkei/maezae/integrations';
73
+
74
+ const body = (await request.json()) as WebhookBody;
75
+ if (body.type === 'part' && body.event === 'published') await revalidatePart(body.item.id);
76
+ ```
77
+
78
+ ### 受け手の移行(0.11.0 以前の形から)
79
+
80
+ 0.12.0 で旧い形(`event` が `<型の ID>.<出来事>`、0.11.0 の版1)と版2を廃止し、`event` と `type` を分けた形だけを版1とした(まだ導入先がないため移行の期間なし)
81
+
82
+ 1. SDK を 0.12.0 にする。`WebhookBodyV1`・`WebhookBodyV2` は廃止、`WebhookBody` が唯一の形(`event` は `WebhookAction`)
83
+ 2. 受け手の分岐を `body.type` と `body.event`(`published` など)に直してデプロイ。`event` の `.` を分ける処理は消す
84
+ 3. 管理画面の「設定 → 連携」の「テストの送信」と「送信の記録」で受け手の成功を確かめる。CMS の更新の前に失敗した送信の再送も、新しい形で届く(migration で本文を揃えた)
85
+
86
+ ### microCMS の Webhook との対応
87
+
88
+ キーの名前は maezae の語彙のまま(`type` を2つの意味で使わない)
89
+
90
+ | microCMS | maezae | 備考 |
91
+ | --- | --- | --- |
92
+ | `service` | (なし) | 送信先の URL と署名の鍵が媒体ごと |
93
+ | `api`(API のエンドポイント名) | `type`(型の ID) | |
94
+ | `type`(`new`・`edit`・`delete`) | `event`(`submitted`・`published`・`unpublished`・`rejected`) | データの変更の出来事(作成・更新・削除)は #494の決定(3)で追加の予定 |
95
+ | `id`(コンテンツ ID) | `item.id` | maezae の `id` はイベントの ID(重複の除去に使用) |
96
+ | `contents`(前後の全内容) | `item`(ID と題) | 中身の選択は #494の決定(3)で追加の予定、今は配信 API で取得 |
97
+
57
98
  ## 外の画面の受け手
58
99
 
59
100
  - 鍵: 媒体の管理画面の「設定 → 連携」の外の画面の登録ごとの署名の鍵
@@ -0,0 +1,29 @@
1
+ import type { LoginAuth } from './oidc.js';
2
+ /** セッションの Cookie の名前(https は __Host- の接頭辞) */
3
+ export declare const sessionCookieName: (url: URL) => string;
4
+ /** セッションの Cookie の Set-Cookie の値(maxAgeSec が0は削除) */
5
+ export declare const sessionCookie: (url: URL, value: string, maxAgeSec: number) => string;
6
+ /** リクエストの Cookie の値(なければ null) */
7
+ export declare const cookieValue: (request: Request, name: string) => string | null;
8
+ /** セッションの値(なし・署名の不一致・期限切れは null) */
9
+ export declare const openSession: <T>(request: Request, secret: string) => Promise<T | null>;
10
+ /** 引き継ぎのログインの設定 */
11
+ export type HandoffLoginOptions = Readonly<{
12
+ /** Cookie の署名の鍵 */
13
+ secret: string;
14
+ /** manage のログインの URL(例: https://example.com/manage/login) */
15
+ loginUrl: string;
16
+ /** トークンを消して使い、メールアドレスを返す(使えないトークンは null、host は CMS のホスト名) */
17
+ consume(token: string, host: string): Promise<string | null>;
18
+ /** 入り口のパスの頭(既定 /auth) */
19
+ basePath?: string;
20
+ /** セッションの秒数(既定は12時間) */
21
+ sessionTtlSec?: number;
22
+ }>;
23
+ /** manage からの引き継ぎのログイン */
24
+ export declare function handoffLogin({ secret, loginUrl, consume, basePath, sessionTtlSec }: HandoffLoginOptions): LoginAuth;
25
+ /**
26
+ * 2つのログインを順に試す(emailOf は first が null なら second、入り口とログアウトは first)
27
+ * - 用途: 引き継ぎのログインへの移行の間、Access の JWT も受け付ける
28
+ */
29
+ export declare function eitherLogin(first: LoginAuth, second: LoginAuth): LoginAuth;
@@ -1,5 +1,6 @@
1
1
  export { accessJwtOptions, type FetchCerts, type JwtClaims, type JwtOptions, verifyAccessJwt, verifyJwt } from './jwt.js';
2
- export { emailOfClaims, type LoginAuth, oidcLogin, type OidcLoginOptions, tokenAuth, type TokenAuthOptions } from './oidc.js';
2
+ export { cookieValue, eitherLogin, handoffLogin, type HandoffLoginOptions, openSession, sessionCookie, sessionCookieName } from './handoff.js';
3
+ export { emailOfClaims, type LoginAuth, oidcLogin, type OidcLoginOptions, safeNext, tokenAuth, type TokenAuthOptions } from './oidc.js';
3
4
  export { type EdgeSpan, type EdgeSpanOptions, type EdgeSpanStatus, sampleRateOf, startEdgeSpan, withEdgeSpan } from './span.js';
4
5
  export { openValue, sealValue, SIGNED_HEADERS, signedHeaders, type SigningSecrets, signParts, type SignedParts, verifyParts, verifyRequest, type VerifyRequestOptions } from './signed.js';
5
6
  export { childTraceContext, formatTraceparent, newSpanId, newTraceId, parseTraceparent, type TraceContext, TRACEPARENT_HEADER } from './traceparent.js';
@@ -1,4 +1,4 @@
1
1
  export { recordView, type Classify, type Dataset } from './record.js';
2
2
  export { syncCron } from './schedule.js';
3
- export { analyticsEngine, type DailyRow, fixedSource, type Source, projectDaily, type ProjectSource } from './source.js';
3
+ export { analyticsEngine, type DailyRow, fixedSource, type Source, projectDaily, type ProjectSource, renamedSource } from './source.js';
4
4
  export { readDaily, syncDaily } from './store.js';
@@ -30,4 +30,6 @@ export type ProjectSource = {
30
30
  export declare function projectDaily(options: Omit<AnalyticsEngineOptions, 'project'>): ProjectSource;
31
31
  /** 集計済みの行の読み込み元(定期の集計で全媒体の問い合わせ結果を媒体ごとに受け渡し、daily は since 以降の行) */
32
32
  export declare const fixedSource: (rows: readonly DailyRow[]) => Source;
33
+ /** 種類を付け替えた読み込み元(renames は旧の種類 → 今の種類、付け替えで重なった行は閲覧数を合計) */
34
+ export declare function renamedSource(source: Source, renames: Readonly<Record<string, string>>): Source;
33
35
  export {};
@@ -15,6 +15,7 @@ export { CONFIG_FORMAT, type ConfigBundle, type ConfigPreview } from './transfer
15
15
  export { IMPORT_ITEMS_MAX, IMPORT_PROBLEM_LABELS, IMPORT_PROBLEMS, type ImportIdChange, type ImportIdConflict, importIdOf, type ImportIdPlan, type ImportImage, type ImportItem, type ImportItemReport, type ImportProblem, type ImportProblemCode, type ImportReport, importReportOf, mergeImportReports, planImportIds, sourceOf } from './import.js';
16
16
  export { EMBED_CONTEXT_NEXT_PARAM, EMBED_CONTEXT_PARAM, EMBED_CONTEXT_TTL_SEC, EMBED_MESSAGES, EMBED_PLACEMENTS, type EmbedContext, type EmbedEntry, type EmbedMessage, type EmbedPlacement, type ExternalScreen } from './embeds.js';
17
17
  export { type ExternalFunction, FUNCTION_HOOK_LABELS, FUNCTION_HOOKS, functionEventLabel, functionEventOf, type FunctionHook, type FunctionOnError, type FunctionRequest, type PublishCheckRequest, type PublishCheckResponse, type SaveCheckRequest, type SaveCheckResponse } from './functions.js';
18
- export { parseWebhookEvent, type Webhook, WEBHOOK_ACTION_LABELS, type WebhookBody, WEBHOOK_ACTIONS, type WebhookAction, type WebhookDelivery, type WebhookEvent, webhookEventLabel, webhookEventOf, type WebhookKind } from './webhooks.js';
18
+ export { isWebhookAction, parseWebhookEvent, type Webhook, WEBHOOK_ACTION_LABELS, type WebhookBody, WEBHOOK_ACTIONS, type WebhookAction, type WebhookDelivery, type WebhookEvent, webhookEventLabel, webhookEventOf, type WebhookKind, type WebhookModuleBody, type WebhookSubscription, webhookSubscriptionLabel, WEBHOOK_VERSION, type WebhookVersion } from './webhooks.js';
19
+ export { CONTRACT_VERSION_HEADER, DELIVERY_VERSION } from './versions.js';
19
20
  export { type SavedView, VIEW_ACTIONS, type ViewAction, type ViewDefinition } from './views.js';
20
21
  export { canManageSettings, isSettingsTarget, managedAreasOf, SETTINGS_AREA_LABELS, SETTINGS_AREAS, type SettingsArea, settingsTarget } from './settings.js';
@@ -56,6 +56,8 @@ export type ModuleManifest<S = Readonly<Record<string, never>>> = Readonly<{
56
56
  settingFields?: readonly SettingField[];
57
57
  /** フィールドの宣言(設定値から作成、例: 種類で決まる名義) */
58
58
  fieldRules?(settings: S): readonly FieldRule[];
59
+ /** 型の id を値に持つ設定値のキー(型の id の変更で書き換え、値は id の文字列か配列) */
60
+ typeSettings?: readonly string[];
59
61
  /** 拡張機能が送る Webhook の出来事(キーは出来事の名前 <対象>.<操作>、値は表示名、設定の「連携」で選択) */
60
62
  webhookEvents?: Readonly<Record<string, string>>;
61
63
  }>;
@@ -91,6 +93,7 @@ export type ModuleBase = Readonly<{
91
93
  fieldRules: readonly FieldRule[];
92
94
  messages: TargetMessages;
93
95
  webhookEvents: Readonly<Record<string, string>>;
96
+ typeSettings: readonly string[];
94
97
  }>;
95
98
  /** 組み立てたモジュール(共通の定義と、側ごとの内容) */
96
99
  export type ModuleInstance<P> = ModuleBase & P;
@@ -29,8 +29,12 @@ export type ConfigBundle = Readonly<{
29
29
  }>;
30
30
  /** スキーマと設定の読み込みの確認(schema はスキーマの変更、ほかは追加と更新の件数) */
31
31
  export type ConfigPreview = Readonly<{
32
+ /** kind は変更の種類、type は型の id、value は型の id の変更の旧の id(型の id の変更の候補の選択に使用) */
32
33
  schema: readonly Readonly<{
33
34
  id: string;
35
+ kind: string;
36
+ type: string;
37
+ value?: string;
34
38
  level: 'safe' | 'confirm' | 'forbidden';
35
39
  message: string;
36
40
  count: number | null;
@@ -0,0 +1,4 @@
1
+ /** 配信 API の版(パス /content/v1) */
2
+ export declare const DELIVERY_VERSION = 1;
3
+ /** 配信 API の応答で契約の版を示すヘッダー */
4
+ export declare const CONTRACT_VERSION_HEADER = "x-cms-version";
@@ -2,23 +2,34 @@
2
2
  export declare const WEBHOOK_ACTIONS: readonly ["submitted", "published", "unpublished", "rejected"];
3
3
  /** 送れる操作 */
4
4
  export type WebhookAction = (typeof WEBHOOK_ACTIONS)[number];
5
- /** 送れる出来事(編集機能を持つ型の <型の id>.<操作> と、拡張機能が宣言した出来事) */
5
+ /** Webhook の契約の版(本文の version) */
6
+ export declare const WEBHOOK_VERSION = 1;
7
+ /** Webhook の契約の版 */
8
+ export type WebhookVersion = typeof WEBHOOK_VERSION;
9
+ /** サーバーの内側の出来事のキー(編集の出来事は <型の id>.<操作>、拡張機能の出来事は宣言の名前) */
6
10
  export type WebhookEvent = `${string}.${string}`;
11
+ /** 購読する出来事(操作か拡張機能の出来事の名前) */
12
+ export type WebhookSubscription = WebhookAction | WebhookEvent;
7
13
  /** 操作の表示名(画面の選択肢と、Slack の文) */
8
14
  export declare const WEBHOOK_ACTION_LABELS: Readonly<Record<WebhookAction, string>>;
9
- /** 型と操作の出来事 */
15
+ /** 送れる操作か */
16
+ export declare const isWebhookAction: (value: string) => value is WebhookAction;
17
+ /** 型と操作の出来事のキー */
10
18
  export declare const webhookEventOf: (type: string, action: WebhookAction) => WebhookEvent;
11
- /** 出来事の型と操作(形が不正なら null) */
19
+ /** 出来事のキーの型と操作(編集の出来事でなければ null) */
12
20
  export declare function parseWebhookEvent(event: string): Readonly<{
13
21
  type: string;
14
22
  action: WebhookAction;
15
23
  }> | null;
16
- /** 出来事の表示名(types は型の id と表示名で型の表示名と操作の表示名、moduleEvents は拡張機能の出来事の表示名) */
24
+ /** 出来事のキーの表示名(types は型の id と表示名で型の表示名と操作の表示名、moduleEvents は拡張機能の出来事の表示名) */
17
25
  export declare function webhookEventLabel(event: string, types: Readonly<Record<string, string>>, moduleEvents?: Readonly<Record<string, string>>): string;
26
+ /** 購読する出来事の表示名(操作か拡張機能の出来事) */
27
+ export declare const webhookSubscriptionLabel: (subscription: string, moduleEvents?: Readonly<Record<string, string>>) => string;
18
28
  /** Webhook の形式 */
19
29
  export type WebhookKind = 'json' | 'slack';
20
30
  /**
21
31
  * Webhook(url・secret は末尾だけを伏せずに返す、secret の null は署名なし)
32
+ * - events: 購読する出来事(操作と拡張機能の出来事)、types: 購読する型の id(編集の出来事は events の操作 × types)
22
33
  * - previousSecret: 鍵の移行の期間の古い鍵の末尾、期間の外は null
23
34
  * - lastStatus: 最新の送信で返った HTTP の状態、送れなかったときは0
24
35
  */
@@ -29,19 +40,23 @@ export type Webhook = Readonly<{
29
40
  url: string;
30
41
  secret: string | null;
31
42
  previousSecret: string | null;
32
- events: readonly WebhookEvent[];
43
+ events: readonly WebhookSubscription[];
44
+ types: readonly string[];
33
45
  enabled: boolean;
34
46
  lastStatus: number | null;
35
47
  lastAt: string | null;
36
48
  }>;
37
49
  /**
38
50
  * json の形式の本文(編集機能を持つ型の出来事、署名はヘッダー x-cms-signature で登録ごとの鍵)
51
+ * - version: 契約の版(1)
39
52
  * - id: イベントの ID(再送しても同じ値、受け手は処理済みの id を捨てて重複を除く)
53
+ * - event: 操作、type: 型の id
40
54
  * - item: 行の ID と題、by: 操作した人の表示名、at: 出来事の日時(ISO 8601、再送しても同じ値)
41
55
  */
42
56
  export type WebhookBody = Readonly<{
57
+ version: WebhookVersion;
43
58
  id: string;
44
- event: WebhookEvent;
59
+ event: WebhookAction;
45
60
  type: string;
46
61
  item: Readonly<{
47
62
  id: string;
@@ -50,7 +65,15 @@ export type WebhookBody = Readonly<{
50
65
  by: string;
51
66
  at: string;
52
67
  }>;
53
- /** 送信の記録(attempt は何回目の送信か、retryAt は次の再送の予定の日時、再送なしは null) */
68
+ /** json の形式の拡張機能の出来事の本文(event は宣言の名前、data はモジュールの値) */
69
+ export type WebhookModuleBody = Readonly<{
70
+ version: WebhookVersion;
71
+ id: string;
72
+ event: WebhookEvent;
73
+ data: Readonly<Record<string, unknown>>;
74
+ at: string;
75
+ }>;
76
+ /** 送信の記録(event は出来事のキー、attempt は何回目の送信か、retryAt は次の再送の予定の日時、再送なしは null) */
54
77
  export type WebhookDelivery = Readonly<{
55
78
  id: number;
56
79
  source: 'webhook' | 'function';
@@ -2,8 +2,8 @@ import { type ContentModel } from './model.js';
2
2
  /** 変更の扱い */
3
3
  export type ChangeLevel = 'safe' | 'confirm' | 'forbidden';
4
4
  /** 変更の種類 */
5
- export type ChangeKind = 'type-added' | 'type-removed' | 'type-changed' | 'format-changed' | 'publishing-changed' | 'publishing-removed' | 'fixed-changed' | 'field-added' | 'field-removed' | 'field-changed' | 'field-type-changed' | 'field-key-reused' | 'required-added' | 'constraint-tightened' | 'option-added' | 'option-removed' | 'component-added' | 'component-changed' | 'component-removed';
6
- /** スキーマの1つの変更(id は確認と置き換え先の指定で使用する識別子、path は項目の key を `.` で連結、value は削除した選択肢の値) */
5
+ export type ChangeKind = 'type-added' | 'type-removed' | 'type-renamed' | 'type-rename-invalid' | 'type-changed' | 'format-changed' | 'publishing-changed' | 'publishing-removed' | 'fixed-changed' | 'field-added' | 'field-removed' | 'field-changed' | 'field-type-changed' | 'field-key-reused' | 'required-added' | 'constraint-tightened' | 'option-added' | 'option-removed' | 'component-added' | 'component-changed' | 'component-removed';
6
+ /** スキーマの1つの変更(id は確認と置き換え先の指定で使用する識別子、path は項目の key を `.` で連結、value は削除した選択肢の値・型の旧の id) */
7
7
  export type SchemaChange = Readonly<{
8
8
  id: string;
9
9
  level: ChangeLevel;
@@ -13,9 +13,18 @@ export type SchemaChange = Readonly<{
13
13
  value?: string;
14
14
  message: string;
15
15
  }>;
16
- /** 差分の条件(history は保存済みの版より古い過去の版) */
16
+ /**
17
+ * 差分の条件
18
+ * - history: 保存済みの版より古い過去の版
19
+ * - renames: 型の id の変更(旧の id → 新しい id)
20
+ * - takenIds: 行が残る型の id(削除した型の行、id の変更先に不可)
21
+ */
17
22
  export type DiffOptions = Readonly<{
18
23
  history?: readonly ContentModel[];
24
+ renames?: Readonly<Record<string, string>>;
25
+ takenIds?: readonly string[];
19
26
  }>;
20
27
  /** 保存済みの版 prev から新しい定義 next への変更(型の順、項目の順) */
21
28
  export declare function diffSchema(prev: ContentModel, next: ContentModel, options?: DiffOptions): SchemaChange[];
29
+ /** 型の id を from から to に変えた定義(型の id と、すべての型の参照の to) */
30
+ export declare const renameType: (model: ContentModel, from: string, to: string) => ContentModel;
@@ -3,7 +3,7 @@ export { assetRefSchema, cropSchema, hotspotSchema, mediaSchema, type Asset, typ
3
3
  export { coverPosition, coverStart, cropRect, croppedHotspot, objectPosition, stillOf, type CropRect } from './image.js';
4
4
  export { IMAGE_TYPE, VIDEO_TYPE } from './media.js';
5
5
  export { IMAGE_SIZE_MAX, imageParamsOf, mediaExtOf, mediaMimeOf, mediaPathOf, parseMediaPath, withImageParams, type ImageParams, type MediaPath } from './mediaUrl.js';
6
- export { diffSchema, type ChangeKind, type ChangeLevel, type DiffOptions, type SchemaChange } from './changes.js';
6
+ export { diffSchema, renameType, type ChangeKind, type ChangeLevel, type DiffOptions, type SchemaChange } from './changes.js';
7
7
  export { dateRoleFieldsOf, primaryTypeOf, requiredPrimaryTypeOf, SOURCES_FIELD, sourceIdsOf, sourcesFieldOf, sourceTypeOf, type SourcesField } from './capabilities.js';
8
8
  export { appliesTo, checkFields, fieldsFor } from './check.js';
9
9
  export { deliveredFields, emptyValues, pickValues, referencedIds, referencesOf, valueFieldsOf } from './values.js';
@@ -3,6 +3,6 @@ import { type EmbedContext } from '../../cms-contract/src/index.js';
3
3
  export { openValue, SIGNED_HEADERS, verifyRequest } from '../../access/src/index.js';
4
4
  export type { SigningSecrets, VerifyRequestOptions } from '../../access/src/index.js';
5
5
  export { EMBED_CONTEXT_NEXT_PARAM, EMBED_CONTEXT_PARAM, EMBED_MESSAGES, FUNCTION_HOOKS } from '../../cms-contract/src/index.js';
6
- export type { EmbedContext, EmbedMessage, FunctionHook, FunctionRequest, PublishCheckRequest, PublishCheckResponse, SaveCheckRequest, SaveCheckResponse, WebhookAction, WebhookBody, WebhookEvent } from '../../cms-contract/src/index.js';
6
+ export type { EmbedContext, EmbedMessage, FunctionHook, FunctionRequest, PublishCheckRequest, PublishCheckResponse, SaveCheckRequest, SaveCheckResponse, WebhookAction, WebhookBody, WebhookEvent, WebhookModuleBody, WebhookVersion } from '../../cms-contract/src/index.js';
7
7
  /** 外の画面の URL の文脈(クエリ ctx と、鍵の移行の期間の ctx_next のどちらかが鍵で開けば文脈、なければ null) */
8
8
  export declare function openEmbedContext(secret: SigningSecrets, url: string | URL, now?: number): Promise<EmbedContext | null>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryusenkei/maezae",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "maezae の導入先のサイト向けの SDK(配信 API の取得・本文の型・プレビューの署名・手元の開発の配信 API)",
5
5
  "license": "UNLICENSED",
6
6
  "author": "RYUSENKEI",