@ryusenkei/maezae 0.18.0 → 0.19.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 +33 -1
- package/dist/analytics.js +30 -14
- package/dist/body.js +27 -22
- package/dist/client-ClY3gSSd.js +137 -0
- package/dist/delivery.js +1 -136
- package/dist/dev.js +9 -2
- package/dist/integrations.js +1 -1
- package/dist/model.js +128 -217
- package/dist/preview.js +1 -1
- package/dist/schema.d.ts +1 -0
- package/dist/schema.js +56 -0
- package/dist/{src-C6YqllXw.js → src-BMOqN7Xs.js} +3 -26
- package/dist/trace.js +1 -1
- package/dist/types/access/src/jwt.d.ts +6 -13
- package/dist/types/analytics/src/index.d.ts +2 -2
- package/dist/types/analytics/src/source.d.ts +0 -16
- package/dist/types/analytics/src/view.d.ts +24 -0
- package/dist/types/body/src/inline.d.ts +20 -0
- package/dist/types/body/src/normalize.d.ts +1 -3
- package/dist/types/cms-contract/src/index.d.ts +1 -1
- package/dist/types/cms-contract/src/roles.d.ts +3 -3
- package/dist/types/model/src/asset.d.ts +16 -0
- package/dist/types/model/src/index.d.ts +4 -3
- package/dist/types/model/src/mediaUrl.d.ts +2 -0
- package/dist/types/model/src/model.d.ts +1 -1
- package/dist/types/model/src/typesSource.d.ts +7 -0
- package/dist/types/{sdk/src/defaults.d.ts → platform/src/defaultModel.d.ts} +4 -1
- package/dist/types/sdk/src/analytics.d.ts +12 -2
- package/dist/types/sdk/src/delivery/index.d.ts +2 -2
- package/dist/types/sdk/src/delivery/values.d.ts +1 -17
- package/dist/types/sdk/src/model.d.ts +2 -2
- package/dist/types/sdk/src/schema.d.ts +5 -0
- package/dist/types/sdk/src/types-cli.d.ts +2 -0
- package/dist/types-cli.d.ts +1 -0
- package/dist/types-cli.js +7 -0
- package/dist/typesSource-BKPzvl--.js +62 -0
- package/package.json +8 -1
- package/dist/types/analytics/src/record.d.ts +0 -22
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ maezae の SDK(導入先のサイト向け)
|
|
|
13
13
|
| `@ryusenkei/maezae/analytics` | サイトの閲覧の記録(CMS の閲覧数の集計と同じ形式) |
|
|
14
14
|
| `@ryusenkei/maezae/trace` | CMS のログと相関を取るトレースの区間 |
|
|
15
15
|
| `@ryusenkei/maezae/integrations` | 外の関数・Webhook が受け取る本文の型と署名の検証(ヘッダー `x-cms-signature`) |
|
|
16
|
+
| `@ryusenkei/maezae/schema` | 配信 API の値の型の定義の生成とずれの検査(CLI `maezae-types`、Node) |
|
|
16
17
|
|
|
17
18
|
## 取得(Cloudflare Workers の service binding)
|
|
18
19
|
|
|
@@ -131,6 +132,36 @@ try {
|
|
|
131
132
|
}
|
|
132
133
|
```
|
|
133
134
|
|
|
135
|
+
## スキーマを取る口(0.19.0 から)
|
|
136
|
+
|
|
137
|
+
スキーマの正本は CMS。サイトは `schema.json` の写しを置かず、次の3つに分ける
|
|
138
|
+
|
|
139
|
+
| 値 | 置き場所 | 取り方 |
|
|
140
|
+
| --- | --- | --- |
|
|
141
|
+
| コードが前提にする形(型の id、項目の key と種類、複数か、選択肢の値) | リポに置く生成のファイル | `maezae-types` で生成し、`--check` でずれを止める |
|
|
142
|
+
| 編集で変わる値(表示名、文字数の上限、URL の形) | 置かない | 実行時に `GET /content/v1/types` を読み、`optionLabels`・`limitOf`・`pagePath`(`@ryusenkei/maezae/delivery`)に渡す |
|
|
143
|
+
| 本文の設定と部品の宣言 | 置かない | CMS が保存のときに検査する。知らない部品は描画しない |
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
# 生成(API の鍵は MAEZAE_API_KEY、URL は --url か MAEZAE_DELIVERY_URL)
|
|
147
|
+
npx maezae-types --out src/cms-types.ts --url https://example.com/<媒体>
|
|
148
|
+
# ずれの検査(CI と pre-push。CMS で項目を足すと失敗し、生成し直してコミットする)
|
|
149
|
+
npx maezae-types --out src/cms-types.ts --check
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- 生成のファイル: 型ごとに `export type <PascalCase の型の id>`(`ItemOf` と同じ値の形)、全体の対応 `CmsTypes`、型の id の一覧 `TYPE_IDS`。表示名・上限・配信しない項目は含めないので、管理画面での表示名の変更ではずれにならない
|
|
153
|
+
- 整形の対象から外す(`--check` は文字の一致で比べる。改行の CRLF は同じとみなす)
|
|
154
|
+
- MCP の `get_schema` も同じ文を返す(`format: "typescript"`)。管理画面の「設定 → データ」の型定義のエクスポートも同じ文
|
|
155
|
+
|
|
156
|
+
## 公開のフォーム(お問い合わせ)
|
|
157
|
+
|
|
158
|
+
お問い合わせのモジュールを有効にした媒体は、toolkit の `@ryusenkei/web/form` の形でフォームを受け付ける
|
|
159
|
+
|
|
160
|
+
- `GET /content/v1/forms/contact`: 項目の宣言 `{ form, label, fields, labels, choices }`。`fields` は web/form の宣言のまま(`parseForm` と画面の検査にそのまま渡せる)、`labels` は項目の名前、`choices` は選択肢の表示。ご用件と上限は CMS の設定から作るので、サイトは写しを持たない
|
|
161
|
+
- `POST /content/v1/forms/contact`: 本文は項目の値の JSON。受け付けは `200 { ok: true, requestId, id }`(`id` は受付の番号、転送だけの設定は `null`)、検査の失敗は `400 { ok: false, error: "invalid", problems }`(文言は web/form の `FORM_MESSAGES_JA` で引く)
|
|
162
|
+
- ボット対策と送信元ごとの回数の制限はサイトの受け取りの処理で行う(web/form の `submissionHandler` の `botCheck`・`rateLimiter`、CMS への転送は `httpDestination` に API の鍵を付ける)
|
|
163
|
+
- 今の `POST /content/v1/contacts`(`201 { id }`)は導入先が移るまで残す
|
|
164
|
+
|
|
134
165
|
## 回数の制限
|
|
135
166
|
|
|
136
167
|
- 対象は公開のエンドポイント(`https://api.maezae.app/<媒体>/content/v1/…`)だけ。上限は媒体と API の鍵ごとに1分で600回で、データセンターごとの近似で数える
|
|
@@ -243,7 +274,8 @@ if (body.hook === 'publish_check' && body.dryRun) problems.push(...(await verify
|
|
|
243
274
|
|
|
244
275
|
- `event`: `submitted`(レビューの依頼)・`published`(公開)・`unpublished`(非公開)・`rejected`(差し戻し)。型は `type`(型の ID)で判断する(型の ID を変えても `event` は変わらない)
|
|
245
276
|
- `id`: イベントの ID(再送しても同じ値、重複の除去に使用)、`item`: 行の ID と題、`by`: 操作した人の表示名、`at`: 出来事の日時
|
|
246
|
-
-
|
|
277
|
+
- 拡張機能の出来事: `{ version: 1, id, event, data, at }`(`event` は拡張機能が宣言した名前で、接頭辞は型の ID ではない)
|
|
278
|
+
- 公開のフォームの送信(お問い合わせの `form.submitted`): toolkit の `@ryusenkei/web/form` の `webhookDestination` と同じ `{ event: "form.submitted", id, form, values, order, receivedAt }`。`id` は送信ごとの UUID、`values` は項目の値(未入力の項目は含めない)、`order` は宣言の項目の順。署名は `x-cms-signature`
|
|
247
279
|
- 購読: 管理画面の「設定 → 連携」で、送る出来事と対象の型を別々に選ぶ。編集の出来事は「選んだ出来事 × 選んだ型」で届く
|
|
248
280
|
|
|
249
281
|
```ts
|
package/dist/analytics.js
CHANGED
|
@@ -6,27 +6,43 @@ const dayIn = (timeZone, at = /* @__PURE__ */ new Date()) => new Intl.DateTimeFo
|
|
|
6
6
|
month: "2-digit",
|
|
7
7
|
day: "2-digit"
|
|
8
8
|
}).format(at);
|
|
9
|
-
// ../analytics/src/
|
|
9
|
+
// ../analytics/src/view.ts
|
|
10
|
+
/** 位置で持つ記録先の列の順 */
|
|
11
|
+
const VIEW_COLUMNS = [
|
|
12
|
+
"kind",
|
|
13
|
+
"id",
|
|
14
|
+
"path",
|
|
15
|
+
"date",
|
|
16
|
+
"event",
|
|
17
|
+
"project"
|
|
18
|
+
];
|
|
10
19
|
const BOT = /bot|crawl|spider|slurp|preview|monitor|headless/i;
|
|
11
20
|
/** ボットの UA か */
|
|
12
21
|
const isBot = (userAgent) => BOT.test(userAgent ?? "");
|
|
13
|
-
/**
|
|
14
|
-
function
|
|
15
|
-
if (
|
|
16
|
-
if (!response.headers.get("content-type")?.includes("text/html") || isBot(request.headers.get("user-agent"))) return;
|
|
22
|
+
/** 閲覧の出来事(対象: GET で応答が200の HTML・ボット以外、ほかは null) */
|
|
23
|
+
function viewOf(request, response, { classify, timeZone, project }) {
|
|
24
|
+
if (request.method !== "GET" || response.status !== 200) return null;
|
|
25
|
+
if (!response.headers.get("content-type")?.includes("text/html") || isBot(request.headers.get("user-agent"))) return null;
|
|
17
26
|
const path = new URL(request.url).pathname;
|
|
18
27
|
const { kind, id } = classify(path);
|
|
28
|
+
return {
|
|
29
|
+
kind,
|
|
30
|
+
id,
|
|
31
|
+
path,
|
|
32
|
+
date: dayIn(timeZone),
|
|
33
|
+
event: "view",
|
|
34
|
+
...project !== void 0 && { project }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// src/analytics.ts
|
|
38
|
+
/** 閲覧を1件記録(対象: GET で応答が200の HTML・ボット以外、binding なしと記録の失敗は無視) */
|
|
39
|
+
function recordView(dataset, request, response, options) {
|
|
40
|
+
const view = dataset && viewOf(request, response, options);
|
|
41
|
+
if (!view) return;
|
|
19
42
|
try {
|
|
20
43
|
dataset.writeDataPoint({
|
|
21
|
-
blobs: [
|
|
22
|
-
|
|
23
|
-
id,
|
|
24
|
-
path,
|
|
25
|
-
dayIn(timeZone),
|
|
26
|
-
"view",
|
|
27
|
-
project ?? ""
|
|
28
|
-
],
|
|
29
|
-
indexes: [project ?? kind]
|
|
44
|
+
blobs: VIEW_COLUMNS.map((key) => view[key] ?? ""),
|
|
45
|
+
indexes: [view.project ?? view.kind]
|
|
30
46
|
});
|
|
31
47
|
} catch {}
|
|
32
48
|
}
|
package/dist/body.js
CHANGED
|
@@ -72,6 +72,32 @@ const SAFE_HREF = /^(?:https?:\/\/|mailto:|#|\/(?![/\\]))/i;
|
|
|
72
72
|
const isSafeHref = (href) => SAFE_HREF.test(href);
|
|
73
73
|
/** 許可しないリンク先の問題の文言 */
|
|
74
74
|
const unsafeHrefMessage = (href) => `リンク先 ${href} は使えない(https://・http://・mailto:・/ で始まるサイト内のパス・# で始まるページ内のリンクだけ)`;
|
|
75
|
+
// ../body/src/inline.ts
|
|
76
|
+
/** 装飾の順序(外側から内側、Markdown と HTML の入れ子の順) */
|
|
77
|
+
const MARK_ORDER = [
|
|
78
|
+
"strong",
|
|
79
|
+
"em",
|
|
80
|
+
"strike",
|
|
81
|
+
"underline",
|
|
82
|
+
"mark",
|
|
83
|
+
"sup",
|
|
84
|
+
"sub",
|
|
85
|
+
"code"
|
|
86
|
+
];
|
|
87
|
+
/** 装飾を MARK_ORDER の順に並べる(重複は1つ) */
|
|
88
|
+
const sortMarks = (marks) => MARK_ORDER.filter((m) => marks?.includes(m));
|
|
89
|
+
/** 装飾の描画の HTML の要素 */
|
|
90
|
+
const MARK_HTML_TAGS = {
|
|
91
|
+
strong: "strong",
|
|
92
|
+
em: "em",
|
|
93
|
+
code: "code",
|
|
94
|
+
strike: "s",
|
|
95
|
+
mark: "mark",
|
|
96
|
+
sup: "sup",
|
|
97
|
+
sub: "sub",
|
|
98
|
+
underline: "u"
|
|
99
|
+
};
|
|
100
|
+
({ ...Object.fromEntries(Object.entries(MARK_HTML_TAGS).map(([mark, tag]) => [tag, mark])) });
|
|
75
101
|
// ../body/src/profile.ts
|
|
76
102
|
const MARK_LABELS = {
|
|
77
103
|
strong: "太字",
|
|
@@ -240,27 +266,6 @@ const ESCAPES = {
|
|
|
240
266
|
};
|
|
241
267
|
const esc = (s) => s.replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
|
242
268
|
const attrs = (pairs) => Object.entries(pairs).filter(([, v]) => v !== void 0 && v !== null && v !== false && v !== "").map(([k, v]) => ` ${k}="${esc(String(v))}"`).join("");
|
|
243
|
-
const MARK_TAGS = {
|
|
244
|
-
strong: "strong",
|
|
245
|
-
em: "em",
|
|
246
|
-
code: "code",
|
|
247
|
-
strike: "s",
|
|
248
|
-
mark: "mark",
|
|
249
|
-
sup: "sup",
|
|
250
|
-
sub: "sub",
|
|
251
|
-
underline: "u"
|
|
252
|
-
};
|
|
253
|
-
/** 外側から内側の順 */
|
|
254
|
-
const MARK_ORDER = [
|
|
255
|
-
"strong",
|
|
256
|
-
"em",
|
|
257
|
-
"strike",
|
|
258
|
-
"underline",
|
|
259
|
-
"mark",
|
|
260
|
-
"sup",
|
|
261
|
-
"sub",
|
|
262
|
-
"code"
|
|
263
|
-
];
|
|
264
269
|
const CSS_PROPERTY = {
|
|
265
270
|
color: "color",
|
|
266
271
|
background: "background-color",
|
|
@@ -294,7 +299,7 @@ function bodyToHtml(body, options) {
|
|
|
294
299
|
if (typeof i === "string") return esc(i).replaceAll("\n", "<br>");
|
|
295
300
|
if ("component" in i) return options.inlineComponent?.(i.component, i.props ?? {}, i.text ?? "") ?? esc(i.text ?? "");
|
|
296
301
|
let html = esc(i.text).replaceAll("\n", "<br>");
|
|
297
|
-
for (const m of
|
|
302
|
+
for (const m of sortMarks(i.marks).reverse()) html = `<${MARK_HTML_TAGS[m]}>${html}</${MARK_HTML_TAGS[m]}>`;
|
|
298
303
|
if (i.style) {
|
|
299
304
|
const a = styleAttr(i.style);
|
|
300
305
|
if (a) html = `<span${a}>${html}</span>`;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/delivery/query.ts
|
|
2
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3
|
+
const OPS = [
|
|
4
|
+
"eq",
|
|
5
|
+
"ne",
|
|
6
|
+
"in",
|
|
7
|
+
"gte",
|
|
8
|
+
"lte",
|
|
9
|
+
"begins",
|
|
10
|
+
"exists"
|
|
11
|
+
];
|
|
12
|
+
/** in・ids の値の検査(コンマを含む値はクエリで区切れない) */
|
|
13
|
+
const joined = (values) => {
|
|
14
|
+
const parts = values.map(String);
|
|
15
|
+
const bad = parts.find((p) => p.includes(","));
|
|
16
|
+
if (bad !== void 0) throw new Error(`in・ids の値にコンマは不可: ${bad}`);
|
|
17
|
+
return parts.join(",");
|
|
18
|
+
};
|
|
19
|
+
/** 一覧の指定をクエリの組に(find・get・subPath の params、一式の path に使用、1件の取得の fields にも使用) */
|
|
20
|
+
function listParams(query) {
|
|
21
|
+
const params = {};
|
|
22
|
+
for (const [key, value] of Object.entries(query.filter ?? {})) {
|
|
23
|
+
if (value === void 0) continue;
|
|
24
|
+
if (!isRecord(value)) {
|
|
25
|
+
params[`filter[${key}]`] = String(value);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const op of OPS) if (value[op] !== void 0) params[`filter[${key}][${op}]`] = op === "in" ? joined(value[op]) : String(value[op]);
|
|
29
|
+
}
|
|
30
|
+
if (query.ids) params.ids = joined(query.ids);
|
|
31
|
+
if (query.fields?.length) params.fields = joined(query.fields);
|
|
32
|
+
if (query.order?.length) params.order = query.order.join(",");
|
|
33
|
+
if (query.limit !== void 0) params.limit = String(query.limit);
|
|
34
|
+
if (query.cursor) params.cursor = query.cursor;
|
|
35
|
+
return params;
|
|
36
|
+
}
|
|
37
|
+
// src/delivery/client.ts
|
|
38
|
+
/** 配信 API のパスの前半 */
|
|
39
|
+
const DELIVERY_PATH = "/content/v1";
|
|
40
|
+
/** 配信 API の取得の失敗(call: 失敗した経路、status: 応答のステータス) */
|
|
41
|
+
var DeliveryError = class extends Error {
|
|
42
|
+
call;
|
|
43
|
+
status;
|
|
44
|
+
constructor(call, status, body) {
|
|
45
|
+
super(`配信 API ${call}: ${status}${body ? ` ${body}` : ""}`);
|
|
46
|
+
this.name = "DeliveryError";
|
|
47
|
+
this.call = call;
|
|
48
|
+
this.status = status;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/** 配信 API の中のパスとクエリ(一式の path と結果のキー) */
|
|
52
|
+
const subPath = (path, params = {}) => {
|
|
53
|
+
const q = new URLSearchParams(params).toString();
|
|
54
|
+
return `${path}${q ? `?${q}` : ""}`;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* 配信 API の読み取り
|
|
58
|
+
* - drafts: 下書き・予約なども含める(プレビュー用、署名は接続の側で付与)
|
|
59
|
+
* - at: 公開の基準の日時(なしは今)
|
|
60
|
+
*/
|
|
61
|
+
function createDeliveryClient(delivery, { drafts = false, at } = {}) {
|
|
62
|
+
const query = (params = {}) => new URLSearchParams({
|
|
63
|
+
...params,
|
|
64
|
+
...drafts ? { drafts: "1" } : {},
|
|
65
|
+
...at ? { at } : {}
|
|
66
|
+
});
|
|
67
|
+
const url = (path, params) => {
|
|
68
|
+
const q = query(params).toString();
|
|
69
|
+
return `${DELIVERY_PATH}${path}${q ? `?${q}` : ""}`;
|
|
70
|
+
};
|
|
71
|
+
let bundled = /* @__PURE__ */ new Map();
|
|
72
|
+
/** 取得(404は undefined、ほかの失敗は例外、一式で取得済みならその結果) */
|
|
73
|
+
const find = async (path, params = {}) => {
|
|
74
|
+
const hit = bundled.get(subPath(path, params));
|
|
75
|
+
if (hit) {
|
|
76
|
+
if (hit.status === 404) return void 0;
|
|
77
|
+
if (hit.status < 200 || hit.status >= 300) throw new DeliveryError(path, hit.status, JSON.stringify(hit.body));
|
|
78
|
+
return hit.body;
|
|
79
|
+
}
|
|
80
|
+
const res = await delivery(url(path, params));
|
|
81
|
+
if (res.status === 404) return void 0;
|
|
82
|
+
if (!res.ok) throw new DeliveryError(path, res.status, await res.text());
|
|
83
|
+
return await res.json();
|
|
84
|
+
};
|
|
85
|
+
/** 取得(404も例外) */
|
|
86
|
+
const get = async (path, params) => {
|
|
87
|
+
const v = await find(path, params);
|
|
88
|
+
if (v === void 0) throw new DeliveryError(path, 404, "");
|
|
89
|
+
return v;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 型の一覧の1ページ(path は型の一覧の経路、例: /post)
|
|
93
|
+
* - query: 絞り込み・並べ替えと limit、次のページは前の応答の next を cursor に
|
|
94
|
+
* - 一式で同じ path とクエリを取得済みならその結果(一式の path は `subPath(path, listParams(query))`)
|
|
95
|
+
*/
|
|
96
|
+
const page = (path, query) => get(path, listParams(query));
|
|
97
|
+
/**
|
|
98
|
+
* 経路の一覧を一式の1回で取得(paths は配信 API の中のパスとクエリ、`*` の区画は一覧の id ごとに展開)
|
|
99
|
+
* - ifNoneMatch: 前回の ETag(一致すれば notModified で、結果は未取得)
|
|
100
|
+
*/
|
|
101
|
+
const prefetch = async (paths, { ifNoneMatch } = {}) => {
|
|
102
|
+
const q = query();
|
|
103
|
+
for (const p of paths) q.append("path", p);
|
|
104
|
+
const res = await delivery(`${DELIVERY_PATH}/bundle?${q}`, ifNoneMatch ? { headers: { "if-none-match": ifNoneMatch } } : {});
|
|
105
|
+
if (res.status === 304) return {
|
|
106
|
+
notModified: true,
|
|
107
|
+
etag: ifNoneMatch ?? null,
|
|
108
|
+
stale: false,
|
|
109
|
+
partial: false
|
|
110
|
+
};
|
|
111
|
+
if (!res.ok) throw new DeliveryError("/bundle", res.status, await res.text());
|
|
112
|
+
const { results } = await res.json();
|
|
113
|
+
bundled = new Map(Object.entries(results));
|
|
114
|
+
return {
|
|
115
|
+
notModified: false,
|
|
116
|
+
etag: res.headers.get("etag"),
|
|
117
|
+
stale: res.headers.has("x-stale"),
|
|
118
|
+
partial: res.headers.has("x-partial")
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* 変更の一覧の1ページ(since は前回の next、null は全件の取得から)
|
|
123
|
+
* - 410(保存の期間を過ぎた cursor)は DeliveryError、since なしで取り直す
|
|
124
|
+
*/
|
|
125
|
+
const changes = (since, { limit } = {}) => get("/changes", {
|
|
126
|
+
...since ? { since } : {},
|
|
127
|
+
...limit !== void 0 ? { limit: String(limit) } : {}
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
find,
|
|
131
|
+
get,
|
|
132
|
+
page,
|
|
133
|
+
prefetch,
|
|
134
|
+
changes
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export { listParams as a, subPath as i, DeliveryError as n, createDeliveryClient as r, DELIVERY_PATH as t };
|
package/dist/delivery.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as listParams, i as subPath, n as DeliveryError, r as createDeliveryClient, t as DELIVERY_PATH } from "./client-ClY3gSSd.js";
|
|
1
2
|
import { a as optionsOf, l as cropRect, o as typeOf, p as createClock, t as pagePathOf, u as croppedHotspot } from "./page-BnwHL-LC.js";
|
|
2
3
|
// src/delivery/cache.ts
|
|
3
4
|
/** 保持した日時(ミリ秒)を置く内部のヘッダー */
|
|
@@ -106,142 +107,6 @@ async function readChanges(client, since, apply, { limit } = {}) {
|
|
|
106
107
|
if (!page.more) return cursor;
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
|
-
// src/delivery/query.ts
|
|
110
|
-
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
111
|
-
const OPS = [
|
|
112
|
-
"eq",
|
|
113
|
-
"ne",
|
|
114
|
-
"in",
|
|
115
|
-
"gte",
|
|
116
|
-
"lte",
|
|
117
|
-
"begins",
|
|
118
|
-
"exists"
|
|
119
|
-
];
|
|
120
|
-
/** in・ids の値の検査(コンマを含む値はクエリで区切れない) */
|
|
121
|
-
const joined = (values) => {
|
|
122
|
-
const parts = values.map(String);
|
|
123
|
-
const bad = parts.find((p) => p.includes(","));
|
|
124
|
-
if (bad !== void 0) throw new Error(`in・ids の値にコンマは不可: ${bad}`);
|
|
125
|
-
return parts.join(",");
|
|
126
|
-
};
|
|
127
|
-
/** 一覧の指定をクエリの組に(find・get・subPath の params、一式の path に使用、1件の取得の fields にも使用) */
|
|
128
|
-
function listParams(query) {
|
|
129
|
-
const params = {};
|
|
130
|
-
for (const [key, value] of Object.entries(query.filter ?? {})) {
|
|
131
|
-
if (value === void 0) continue;
|
|
132
|
-
if (!isRecord(value)) {
|
|
133
|
-
params[`filter[${key}]`] = String(value);
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
for (const op of OPS) if (value[op] !== void 0) params[`filter[${key}][${op}]`] = op === "in" ? joined(value[op]) : String(value[op]);
|
|
137
|
-
}
|
|
138
|
-
if (query.ids) params.ids = joined(query.ids);
|
|
139
|
-
if (query.fields?.length) params.fields = joined(query.fields);
|
|
140
|
-
if (query.order?.length) params.order = query.order.join(",");
|
|
141
|
-
if (query.limit !== void 0) params.limit = String(query.limit);
|
|
142
|
-
if (query.cursor) params.cursor = query.cursor;
|
|
143
|
-
return params;
|
|
144
|
-
}
|
|
145
|
-
// src/delivery/client.ts
|
|
146
|
-
/** 配信 API のパスの前半 */
|
|
147
|
-
const DELIVERY_PATH = "/content/v1";
|
|
148
|
-
/** 配信 API の取得の失敗(call: 失敗した経路、status: 応答のステータス) */
|
|
149
|
-
var DeliveryError = class extends Error {
|
|
150
|
-
call;
|
|
151
|
-
status;
|
|
152
|
-
constructor(call, status, body) {
|
|
153
|
-
super(`配信 API ${call}: ${status}${body ? ` ${body}` : ""}`);
|
|
154
|
-
this.name = "DeliveryError";
|
|
155
|
-
this.call = call;
|
|
156
|
-
this.status = status;
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
/** 配信 API の中のパスとクエリ(一式の path と結果のキー) */
|
|
160
|
-
const subPath = (path, params = {}) => {
|
|
161
|
-
const q = new URLSearchParams(params).toString();
|
|
162
|
-
return `${path}${q ? `?${q}` : ""}`;
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* 配信 API の読み取り
|
|
166
|
-
* - drafts: 下書き・予約なども含める(プレビュー用、署名は接続の側で付与)
|
|
167
|
-
* - at: 公開の基準の日時(なしは今)
|
|
168
|
-
*/
|
|
169
|
-
function createDeliveryClient(delivery, { drafts = false, at } = {}) {
|
|
170
|
-
const query = (params = {}) => new URLSearchParams({
|
|
171
|
-
...params,
|
|
172
|
-
...drafts ? { drafts: "1" } : {},
|
|
173
|
-
...at ? { at } : {}
|
|
174
|
-
});
|
|
175
|
-
const url = (path, params) => {
|
|
176
|
-
const q = query(params).toString();
|
|
177
|
-
return `${DELIVERY_PATH}${path}${q ? `?${q}` : ""}`;
|
|
178
|
-
};
|
|
179
|
-
let bundled = /* @__PURE__ */ new Map();
|
|
180
|
-
/** 取得(404は undefined、ほかの失敗は例外、一式で取得済みならその結果) */
|
|
181
|
-
const find = async (path, params = {}) => {
|
|
182
|
-
const hit = bundled.get(subPath(path, params));
|
|
183
|
-
if (hit) {
|
|
184
|
-
if (hit.status === 404) return void 0;
|
|
185
|
-
if (hit.status < 200 || hit.status >= 300) throw new DeliveryError(path, hit.status, JSON.stringify(hit.body));
|
|
186
|
-
return hit.body;
|
|
187
|
-
}
|
|
188
|
-
const res = await delivery(url(path, params));
|
|
189
|
-
if (res.status === 404) return void 0;
|
|
190
|
-
if (!res.ok) throw new DeliveryError(path, res.status, await res.text());
|
|
191
|
-
return await res.json();
|
|
192
|
-
};
|
|
193
|
-
/** 取得(404も例外) */
|
|
194
|
-
const get = async (path, params) => {
|
|
195
|
-
const v = await find(path, params);
|
|
196
|
-
if (v === void 0) throw new DeliveryError(path, 404, "");
|
|
197
|
-
return v;
|
|
198
|
-
};
|
|
199
|
-
/**
|
|
200
|
-
* 型の一覧の1ページ(path は型の一覧の経路、例: /post)
|
|
201
|
-
* - query: 絞り込み・並べ替えと limit、次のページは前の応答の next を cursor に
|
|
202
|
-
* - 一式で同じ path とクエリを取得済みならその結果(一式の path は `subPath(path, listParams(query))`)
|
|
203
|
-
*/
|
|
204
|
-
const page = (path, query) => get(path, listParams(query));
|
|
205
|
-
/**
|
|
206
|
-
* 経路の一覧を一式の1回で取得(paths は配信 API の中のパスとクエリ、`*` の区画は一覧の id ごとに展開)
|
|
207
|
-
* - ifNoneMatch: 前回の ETag(一致すれば notModified で、結果は未取得)
|
|
208
|
-
*/
|
|
209
|
-
const prefetch = async (paths, { ifNoneMatch } = {}) => {
|
|
210
|
-
const q = query();
|
|
211
|
-
for (const p of paths) q.append("path", p);
|
|
212
|
-
const res = await delivery(`${DELIVERY_PATH}/bundle?${q}`, ifNoneMatch ? { headers: { "if-none-match": ifNoneMatch } } : {});
|
|
213
|
-
if (res.status === 304) return {
|
|
214
|
-
notModified: true,
|
|
215
|
-
etag: ifNoneMatch ?? null,
|
|
216
|
-
stale: false,
|
|
217
|
-
partial: false
|
|
218
|
-
};
|
|
219
|
-
if (!res.ok) throw new DeliveryError("/bundle", res.status, await res.text());
|
|
220
|
-
const { results } = await res.json();
|
|
221
|
-
bundled = new Map(Object.entries(results));
|
|
222
|
-
return {
|
|
223
|
-
notModified: false,
|
|
224
|
-
etag: res.headers.get("etag"),
|
|
225
|
-
stale: res.headers.has("x-stale"),
|
|
226
|
-
partial: res.headers.has("x-partial")
|
|
227
|
-
};
|
|
228
|
-
};
|
|
229
|
-
/**
|
|
230
|
-
* 変更の一覧の1ページ(since は前回の next、null は全件の取得から)
|
|
231
|
-
* - 410(保存の期間を過ぎた cursor)は DeliveryError、since なしで取り直す
|
|
232
|
-
*/
|
|
233
|
-
const changes = (since, { limit } = {}) => get("/changes", {
|
|
234
|
-
...since ? { since } : {},
|
|
235
|
-
...limit !== void 0 ? { limit: String(limit) } : {}
|
|
236
|
-
});
|
|
237
|
-
return {
|
|
238
|
-
find,
|
|
239
|
-
get,
|
|
240
|
-
page,
|
|
241
|
-
prefetch,
|
|
242
|
-
changes
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
110
|
// ../model/src/mediaUrl.ts
|
|
246
111
|
/** 配信する形式と拡張子 */
|
|
247
112
|
const EXTENSIONS = {
|
package/dist/dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as SIGNED_HEADERS, s as signedHeaders } from "./src-
|
|
1
|
+
import { i as SIGNED_HEADERS, s as signedHeaders } from "./src-BMOqN7Xs.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { createServer } from "node:http";
|
|
@@ -49,6 +49,11 @@ function relayDelivery({ origin, apiKey, previewSecret }) {
|
|
|
49
49
|
});
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
const DECODED_BODY_HEADERS = /* @__PURE__ */ new Set([
|
|
53
|
+
"content-encoding",
|
|
54
|
+
"content-length",
|
|
55
|
+
"transfer-encoding"
|
|
56
|
+
]);
|
|
52
57
|
/** 手元の配信 API のサーバー(/content/v1/… だけ、失敗は502、戻り値は origin と停止の関数) */
|
|
53
58
|
async function serveDelivery(delivery, options = {}) {
|
|
54
59
|
const server = createServer(async (req, res) => {
|
|
@@ -67,7 +72,9 @@ async function serveDelivery(delivery, options = {}) {
|
|
|
67
72
|
...chunks.length ? { body: Buffer.concat(chunks).toString("utf8") } : {}
|
|
68
73
|
});
|
|
69
74
|
const out = {};
|
|
70
|
-
response.headers.forEach((v, k) =>
|
|
75
|
+
response.headers.forEach((v, k) => {
|
|
76
|
+
if (!DECODED_BODY_HEADERS.has(k)) out[k] = v;
|
|
77
|
+
});
|
|
71
78
|
res.writeHead(response.status, out);
|
|
72
79
|
res.end(Buffer.from(await response.arrayBuffer()));
|
|
73
80
|
} catch (e) {
|
package/dist/integrations.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as openValue, i as SIGNED_HEADERS, l as verifyRequest } from "./src-
|
|
1
|
+
import { a as openValue, i as SIGNED_HEADERS, l as verifyRequest } from "./src-BMOqN7Xs.js";
|
|
2
2
|
import "./src-BrLwIWaT.js";
|
|
3
3
|
// ../cms-contract/src/embeds.ts
|
|
4
4
|
/** URL のクエリの名前(署名つきの文脈) */
|