@ryusenkei/maezae 0.5.0 → 0.7.1
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 +2 -0
- package/dist/delivery.js +3 -4
- package/dist/dev.js +38 -53
- package/dist/model.js +173 -70
- package/dist/{page-UEKb7_gu.js → page-TFUlKv3R.js} +2 -2
- package/dist/types/delivery-client/src/client.d.ts +0 -2
- package/dist/types/delivery-client/src/index.d.ts +1 -1
- package/dist/types/delivery-client/src/query.d.ts +4 -3
- package/dist/types/delivery-client/src/schema.d.ts +1 -1
- package/dist/types/delivery-client/src/values.d.ts +11 -5
- package/dist/types/model/src/capabilities.d.ts +22 -19
- package/dist/types/model/src/changes.d.ts +2 -4
- package/dist/types/model/src/index.d.ts +5 -4
- package/dist/types/model/src/legacy.d.ts +2 -0
- package/dist/types/model/src/model.d.ts +38 -22
- package/dist/types/model/src/page.d.ts +7 -2
- package/dist/types/model/src/refs.d.ts +10 -0
- package/dist/types/sdk/src/delivery.d.ts +1 -1
- package/dist/types/sdk/src/dev.d.ts +17 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ maezae の SDK(導入先のサイト向け)
|
|
|
19
19
|
- バインディング: `services: [{ "binding": "CMS", "service": "<CMS のサーバーの Worker>", "entrypoint": "Delivery" }]`
|
|
20
20
|
- 媒体: URL のホスト名(媒体の管理画面のホスト名)
|
|
21
21
|
- 鍵: 媒体の管理画面の「設定 → 連携」で発行した API の鍵
|
|
22
|
+
- 1件の形: `{ id, ...項目, _meta: { created_at, updated_at, published_at } }`、published_at は公開の管理がある型だけ(0.7.0 から)
|
|
23
|
+
- 一覧: 本文の種類の項目を含めない、本文は1件の取得か `/bundle` で
|
|
22
24
|
|
|
23
25
|
```ts
|
|
24
26
|
import { createDeliveryClient } from '@ryusenkei/maezae/delivery';
|
package/dist/delivery.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as croppedHotspot, d as createClock, i as typeOf, r as optionsOf, s as cropRect, t as pagePathOf } from "./page-
|
|
1
|
+
import { c as croppedHotspot, d as createClock, i as typeOf, r as optionsOf, s as cropRect, t as pagePathOf } from "./page-TFUlKv3R.js";
|
|
2
2
|
// ../delivery-client/src/query.ts
|
|
3
3
|
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4
4
|
/** in の値の検査(コンマを含む値はクエリで区切れない) */
|
|
@@ -277,10 +277,9 @@ function mediaOf(base, imageUrl) {
|
|
|
277
277
|
// ../delivery-client/src/schema.ts
|
|
278
278
|
/** 選択肢の項目から表示名の表(値 → 表示名、key はまとまりの中なら `links.kind` の形、選択肢の項目でなければ空) */
|
|
279
279
|
const optionLabels = (schema, type, key) => Object.fromEntries(optionsOf(typeOf(schema, type), key).map((o) => [o.value, o.label]));
|
|
280
|
-
/**
|
|
280
|
+
/** 型の項目(なければ例外: 導入先の設定の誤り) */
|
|
281
281
|
function fieldOf(schema, type, key) {
|
|
282
|
-
const
|
|
283
|
-
const field = [...t.core ?? [], ...t.fields].find((f) => f.key === key);
|
|
282
|
+
const field = typeOf(schema, type).fields.find((f) => f.key === key);
|
|
284
283
|
if (!field) throw new Error(`型 ${type} に項目 ${key} がない`);
|
|
285
284
|
return field;
|
|
286
285
|
}
|
package/dist/dev.js
CHANGED
|
@@ -1,71 +1,56 @@
|
|
|
1
1
|
import { i as SIGNED_HEADERS, s as signedHeaders } from "./src-Baj-EC2G.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { existsSync,
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
// src/dev.ts
|
|
7
|
-
/**
|
|
8
|
-
const
|
|
9
|
-
"content-type",
|
|
10
|
-
"etag",
|
|
11
|
-
"x-stale"
|
|
12
|
-
];
|
|
7
|
+
/** 呼び出しの記録のファイル名(メソッド・パス・If-None-Match・本文のハッシュ) */
|
|
8
|
+
const fixtureName = (method, path, ifNoneMatch = "", body = "") => `${createHash("sha256").update(`${method.toUpperCase()} ${path}\n${ifNoneMatch}\n${body}`).digest("hex").slice(0, 16)}.json`;
|
|
13
9
|
/** 記録のディレクトリの URL(末尾はスラッシュ) */
|
|
14
10
|
const dirUrl = (dir) => {
|
|
15
11
|
const url = dir instanceof URL ? dir : pathToFileURL(dir);
|
|
16
12
|
return url.href.endsWith("/") ? url : new URL(`${url.href}/`);
|
|
17
13
|
};
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
/** upstream へ中継(API の鍵を付与、下書きの署名は upstream の URL で改めて付与) */
|
|
21
|
-
async function relay({ origin, apiKey, previewSecret }, path, init) {
|
|
22
|
-
const url = `${origin.replace(/\/$/, "")}${path}`;
|
|
23
|
-
const body = typeof init.body === "string" ? init.body : null;
|
|
24
|
-
const headers = new Headers(init.headers);
|
|
25
|
-
if (previewSecret && headers.has(SIGNED_HEADERS.preview)) headers.set(SIGNED_HEADERS.preview, (await signedHeaders(previewSecret, {
|
|
26
|
-
method: init.method ?? "GET",
|
|
27
|
-
url,
|
|
28
|
-
body
|
|
29
|
-
}, { header: SIGNED_HEADERS.preview })).get(SIGNED_HEADERS.preview));
|
|
30
|
-
headers.set("authorization", `Bearer ${apiKey}`);
|
|
31
|
-
return fetch(url, {
|
|
32
|
-
...init,
|
|
33
|
-
headers
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
/** 記録の配信 API(port Delivery、upstream があれば中継して保存、並行の書き込みは一時のファイルから置き換え) */
|
|
37
|
-
function fixtureDelivery({ dir, upstream }) {
|
|
14
|
+
/** 記録の再生(port Delivery、dir は記録のディレクトリ) */
|
|
15
|
+
function fixtureDelivery(dir) {
|
|
38
16
|
const base = dirUrl(dir);
|
|
39
17
|
return async (path, init = {}) => {
|
|
40
18
|
const method = (init.method ?? "GET").toUpperCase();
|
|
41
|
-
const file =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
19
|
+
const file = new URL(fixtureName(method, path, new Headers(init.headers).get("if-none-match") ?? "", typeof init.body === "string" ? init.body : ""), base);
|
|
20
|
+
if (!existsSync(file)) throw new Error(`配信 API の記録なし: ${method} ${path}(${file.pathname.split("/").at(-1)})`);
|
|
21
|
+
const { status, headers, body } = JSON.parse(readFileSync(file, "utf8"));
|
|
22
|
+
return new Response(status === 304 ? null : body, {
|
|
23
|
+
status,
|
|
24
|
+
headers
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
29
|
+
/** 公開の配信 API への中継(port Delivery、読み取りだけで書き込みの操作は405) */
|
|
30
|
+
function relayDelivery({ origin, apiKey, previewSecret }) {
|
|
31
|
+
return async (path, init = {}) => {
|
|
32
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
33
|
+
if (!READ_METHODS.has(method)) return new Response("手元の中継は読み取りだけ", {
|
|
34
|
+
status: 405,
|
|
35
|
+
headers: { allow: "GET, HEAD" }
|
|
36
|
+
});
|
|
37
|
+
const url = `${origin.replace(/\/$/, "")}${path}`;
|
|
38
|
+
const headers = new Headers(init.headers);
|
|
39
|
+
if (previewSecret && headers.has(SIGNED_HEADERS.preview)) headers.set(SIGNED_HEADERS.preview, (await signedHeaders(previewSecret, {
|
|
40
|
+
method,
|
|
41
|
+
url,
|
|
42
|
+
body: null
|
|
43
|
+
}, { header: SIGNED_HEADERS.preview })).get(SIGNED_HEADERS.preview));
|
|
44
|
+
headers.set("authorization", `Bearer ${apiKey}`);
|
|
45
|
+
return fetch(url, {
|
|
46
|
+
...init,
|
|
47
|
+
method,
|
|
48
|
+
headers
|
|
63
49
|
});
|
|
64
50
|
};
|
|
65
51
|
}
|
|
66
|
-
/** 手元の配信 API のサーバー(/content/v1/…
|
|
67
|
-
async function serveDelivery(options) {
|
|
68
|
-
const delivery = fixtureDelivery(options);
|
|
52
|
+
/** 手元の配信 API のサーバー(/content/v1/… だけ、失敗は502、戻り値は origin と停止の関数) */
|
|
53
|
+
async function serveDelivery(delivery, options = {}) {
|
|
69
54
|
const server = createServer(async (req, res) => {
|
|
70
55
|
const chunks = [];
|
|
71
56
|
for await (const chunk of req) chunks.push(chunk);
|
|
@@ -97,4 +82,4 @@ async function serveDelivery(options) {
|
|
|
97
82
|
close: () => new Promise((resolve) => server.close(() => resolve()))
|
|
98
83
|
};
|
|
99
84
|
}
|
|
100
|
-
export { fixtureDelivery, serveDelivery };
|
|
85
|
+
export { fixtureDelivery, fixtureName, relayDelivery, serveDelivery };
|
package/dist/model.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as coverPosition, c as croppedHotspot, d as createClock, f as isDateOnly, i as typeOf, l as objectPosition, n as publicPathOf, o as coverStart, s as cropRect, t as pagePathOf, u as stillOf } from "./page-
|
|
1
|
+
import { a as coverPosition, c as croppedHotspot, d as createClock, f as isDateOnly, i as typeOf, l as objectPosition, n as publicPathOf, o as coverStart, s as cropRect, t as pagePathOf, u as stillOf } from "./page-TFUlKv3R.js";
|
|
2
2
|
import { i as CORE_COMPONENTS, t as declaredComponentsSchema } from "./declared-9_jI3JXH.js";
|
|
3
3
|
import "./src-BrLwIWaT.js";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -72,14 +72,13 @@ const IMAGE_TYPE = {
|
|
|
72
72
|
id: "image",
|
|
73
73
|
label: "画像",
|
|
74
74
|
description: "見せたい点(hotspot)と切り抜き(crop)を画像ごとに1つ保持、切り抜く場所すべてで共通",
|
|
75
|
-
|
|
76
|
-
fields: []
|
|
75
|
+
fields: file
|
|
77
76
|
};
|
|
78
77
|
/** 動画の型(配信と再生は最初に使用する場所が決まったときに実装) */
|
|
79
78
|
const VIDEO_TYPE = {
|
|
80
79
|
id: "video",
|
|
81
80
|
label: "動画",
|
|
82
|
-
|
|
81
|
+
fields: [
|
|
83
82
|
...file,
|
|
84
83
|
{
|
|
85
84
|
key: "duration",
|
|
@@ -94,30 +93,101 @@ const VIDEO_TYPE = {
|
|
|
94
93
|
accept: ["image"],
|
|
95
94
|
description: "読み込み前・動きを抑える設定・OG で代わりに表示"
|
|
96
95
|
}
|
|
97
|
-
]
|
|
98
|
-
fields: []
|
|
96
|
+
]
|
|
99
97
|
};
|
|
100
98
|
// ../model/src/capabilities.ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
/** 出典の項目と、内側の項目の key */
|
|
100
|
+
const SOURCES_FIELD = {
|
|
101
|
+
key: "sources",
|
|
102
|
+
source: "source",
|
|
103
|
+
note: "note"
|
|
106
104
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
// ../model/src/legacy.ts
|
|
106
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
107
|
+
/** 記事の表の型だった目印(古いバージョンの table の値) */
|
|
108
|
+
const ARTICLES = "articles";
|
|
109
|
+
const PUBLISHED_AT = "published_at";
|
|
110
|
+
/** 本文から作成する値の計算の項目(from は本文の項目の key) */
|
|
111
|
+
const computedFields = (from) => [
|
|
112
|
+
{
|
|
113
|
+
key: "excerpt",
|
|
114
|
+
label: "抜粋",
|
|
115
|
+
type: "textarea",
|
|
116
|
+
computed: {
|
|
117
|
+
kind: "excerpt",
|
|
118
|
+
from
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
key: "read_min",
|
|
123
|
+
label: "読了の分",
|
|
124
|
+
type: "number",
|
|
125
|
+
computed: {
|
|
126
|
+
kind: "read_min",
|
|
127
|
+
from
|
|
128
|
+
}
|
|
113
129
|
},
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
130
|
+
{
|
|
131
|
+
key: "links",
|
|
132
|
+
label: "リンク先",
|
|
133
|
+
type: "text",
|
|
134
|
+
multiple: true,
|
|
135
|
+
computed: {
|
|
136
|
+
kind: "links",
|
|
137
|
+
from
|
|
138
|
+
}
|
|
117
139
|
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
const
|
|
140
|
+
];
|
|
141
|
+
/** 出典の参照の項目を複数のまとまりに */
|
|
142
|
+
const sourcesGroup = (f) => ({
|
|
143
|
+
key: f.key,
|
|
144
|
+
label: f.label,
|
|
145
|
+
...f.private !== void 0 && { private: f.private },
|
|
146
|
+
type: "group",
|
|
147
|
+
multiple: true,
|
|
148
|
+
fields: [{
|
|
149
|
+
key: SOURCES_FIELD.source,
|
|
150
|
+
label: f.label,
|
|
151
|
+
type: "reference",
|
|
152
|
+
to: f.to
|
|
153
|
+
}, {
|
|
154
|
+
key: SOURCES_FIELD.note,
|
|
155
|
+
label: "注記",
|
|
156
|
+
type: "textarea"
|
|
157
|
+
}]
|
|
158
|
+
});
|
|
159
|
+
/** 古い形の型を今の形に(core・table のない型はそのまま) */
|
|
160
|
+
function normalizeType(t) {
|
|
161
|
+
if (!("core" in t) && !("table" in t)) return t;
|
|
162
|
+
const { core, table, ...rest } = t;
|
|
163
|
+
const articles = table === ARTICLES;
|
|
164
|
+
const coreFields = (Array.isArray(core) ? core : []).filter(isRecord).flatMap((f) => {
|
|
165
|
+
if (articles && f.key === PUBLISHED_AT) return [];
|
|
166
|
+
if (articles && f.key === SOURCES_FIELD.key && f.type === "reference") return [sourcesGroup(f)];
|
|
167
|
+
return [f];
|
|
168
|
+
});
|
|
169
|
+
const keys = new Set(coreFields.map((f) => f.key));
|
|
170
|
+
const fields = [...coreFields, ...(Array.isArray(t.fields) ? t.fields : []).filter((f) => !isRecord(f) || !keys.has(f.key))];
|
|
171
|
+
if (!articles) return {
|
|
172
|
+
...rest,
|
|
173
|
+
fields
|
|
174
|
+
};
|
|
175
|
+
const body = fields.find((f) => isRecord(f) && f.type === "body");
|
|
176
|
+
const present = new Set(fields.filter(isRecord).map((f) => f.key));
|
|
177
|
+
const computed = body ? computedFields(String(body.key)).filter((f) => !present.has(f.key)) : [];
|
|
178
|
+
return {
|
|
179
|
+
...rest,
|
|
180
|
+
fields: [...fields, ...computed]
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** 古い形のスキーマの JSON を今の形に(形の分からない値はそのまま、検査は parseModel) */
|
|
184
|
+
function normalizeLegacyModel(json) {
|
|
185
|
+
if (!isRecord(json) || !Array.isArray(json.types)) return json;
|
|
186
|
+
return {
|
|
187
|
+
...json,
|
|
188
|
+
types: json.types.map((t) => isRecord(t) ? normalizeType(t) : t)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
121
191
|
// ../model/src/schema.ts
|
|
122
192
|
const key = z.string().regex(/^[a-z][a-z0-9_]*$/, "id は英小文字で始まり、英小文字・数字・アンダーラインだけ");
|
|
123
193
|
const fieldKey = z.string().regex(/^[a-z][a-zA-Z0-9_-]*$/, "key は英小文字で始まり、英字・数字・アンダーライン・ハイフンだけ");
|
|
@@ -125,6 +195,14 @@ const condition = z.object({
|
|
|
125
195
|
key: fieldKey,
|
|
126
196
|
in: z.array(z.string().min(1)).min(1)
|
|
127
197
|
});
|
|
198
|
+
const computed = z.object({
|
|
199
|
+
kind: z.enum([
|
|
200
|
+
"excerpt",
|
|
201
|
+
"read_min",
|
|
202
|
+
"links"
|
|
203
|
+
]),
|
|
204
|
+
from: fieldKey
|
|
205
|
+
});
|
|
128
206
|
const base = {
|
|
129
207
|
key: fieldKey,
|
|
130
208
|
label: z.string().min(1),
|
|
@@ -138,7 +216,8 @@ const base = {
|
|
|
138
216
|
when: condition.optional(),
|
|
139
217
|
private: z.boolean().optional(),
|
|
140
218
|
adminOnly: z.boolean().optional(),
|
|
141
|
-
pendingUntil: fieldKey.optional()
|
|
219
|
+
pendingUntil: fieldKey.optional(),
|
|
220
|
+
computed: computed.optional()
|
|
142
221
|
};
|
|
143
222
|
const frame = z.object({
|
|
144
223
|
when: condition.optional(),
|
|
@@ -296,8 +375,7 @@ const nestedRules = (fields) => fields.flatMap((f) => (f.fields ?? []).flatMap((
|
|
|
296
375
|
/**
|
|
297
376
|
* システムの値と同じ名前で、項目の key に使用できない名前(API・配信が項目の値を上書きするため)
|
|
298
377
|
* - すべての型: 行の ID・作成と更新の日時・作成者、管理画面の一覧で参照する行の数 uses
|
|
299
|
-
* -
|
|
300
|
-
* - 本文から作成する列を持つ表の型: excerpt・read_min・links
|
|
378
|
+
* - 公開の管理がある型: 公開の機能の値
|
|
301
379
|
* - 対象外: API・配信の値に出ない列 type、導入先の設定で決まるアカウントの email・account_role
|
|
302
380
|
*/
|
|
303
381
|
const RESERVED = [
|
|
@@ -315,22 +393,25 @@ const RESERVED_EDITORIAL = [
|
|
|
315
393
|
"review_note",
|
|
316
394
|
"assignee"
|
|
317
395
|
];
|
|
318
|
-
|
|
319
|
-
"excerpt",
|
|
320
|
-
"read_min",
|
|
321
|
-
"links"
|
|
322
|
-
];
|
|
323
|
-
/** 予約の名前と同じ最上位の key(コアの項目は表の列そのものなので、編集の機能の列は対象外) */
|
|
396
|
+
/** 予約の名前と同じ最上位の key */
|
|
324
397
|
function reservedKeysOf(t) {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
398
|
+
const reserved = [...RESERVED, ...(t.publishing ?? "none") !== "none" ? RESERVED_EDITORIAL : []];
|
|
399
|
+
return [...t.fields.map((f) => f.key), ...(t.counts ?? []).map((c) => c.key)].filter((k) => reserved.includes(k));
|
|
400
|
+
}
|
|
401
|
+
/** 計算の種類ごとに受け付ける項目の種類 */
|
|
402
|
+
const COMPUTED_TYPES = {
|
|
403
|
+
excerpt: (f) => (f.type === "text" || f.type === "textarea") && !f.multiple,
|
|
404
|
+
read_min: (f) => f.type === "number",
|
|
405
|
+
links: (f) => f.type === "text" && Boolean(f.multiple)
|
|
406
|
+
};
|
|
407
|
+
/** 計算の項目の誤り(最上位の項目と、まとまりの中にある計算の項目) */
|
|
408
|
+
function computedProblems(fields) {
|
|
409
|
+
const nested = (fs, path) => fs.flatMap((f) => [...f.computed ? [`${path}${f.key}: まとまりの中の項目は計算の項目なし`] : [], ...nested(f.fields ?? [], `${path}${f.key}.`)]);
|
|
410
|
+
return fields.flatMap((f) => {
|
|
411
|
+
const inner = nested(f.fields ?? [], `${f.key}.`);
|
|
412
|
+
if (!f.computed) return inner;
|
|
413
|
+
return [...[...fields.find((g) => g.key === f.computed.from)?.type === "body" ? [] : [`${f.key}: 計算の元 ${f.computed.from} が同じ型の本文の項目でない`], ...COMPUTED_TYPES[f.computed.kind]?.(f) ? [] : [`${f.key}: 計算の種類 ${f.computed.kind} に合わない項目の種類 ${f.type}`]], ...inner];
|
|
414
|
+
});
|
|
334
415
|
}
|
|
335
416
|
/** 本文の項目が使用する部品の名前(まとまりの中も、key は項目の key) */
|
|
336
417
|
const bodyComponents = (fields) => fields.flatMap((f) => f.type === "body" ? f.profile.components.map((name) => ({
|
|
@@ -349,8 +430,6 @@ const contentType = z.object({
|
|
|
349
430
|
"draft",
|
|
350
431
|
"review"
|
|
351
432
|
]).optional(),
|
|
352
|
-
table: key.optional(),
|
|
353
|
-
core: z.array(field).optional(),
|
|
354
433
|
fields: z.array(field),
|
|
355
434
|
pages: z.array(page).optional(),
|
|
356
435
|
counts: z.array(z.object({
|
|
@@ -360,29 +439,33 @@ const contentType = z.object({
|
|
|
360
439
|
})).optional(),
|
|
361
440
|
og: z.string().startsWith("/", "OG 画像の雛形は / で始まる").optional(),
|
|
362
441
|
idInput: z.boolean().optional()
|
|
363
|
-
}).refine((t) =>
|
|
442
|
+
}).refine((t) => new Set(t.fields.map((f) => f.key)).size === t.fields.length, { message: "項目の key が重複" }).refine((t) => conditionsValid(t.fields), { message: "項目の条件が同じ階層の選択肢の項目と値を指していない" }).refine((t) => (t.pages ?? []).every((p) => conditionValid(p.when, t.fields)), { message: "ページの条件が型の選択肢の項目と値を指していない" }).superRefine((t, ctx) => {
|
|
364
443
|
for (const k of reservedKeysOf(t)) ctx.addIssue({
|
|
365
444
|
code: "custom",
|
|
366
445
|
message: `型 ${t.id} の key ${k} はシステムの値と同じ名前(別の key に)`
|
|
367
446
|
});
|
|
368
|
-
for (const k of nestedRules(
|
|
447
|
+
for (const k of nestedRules(t.fields)) ctx.addIssue({
|
|
369
448
|
code: "custom",
|
|
370
449
|
message: `型 ${t.id} の項目 ${k}: まとまりの中の項目は決まる値 derive・参照先で固定 fixedBy なし`
|
|
371
450
|
});
|
|
451
|
+
for (const m of computedProblems(t.fields)) ctx.addIssue({
|
|
452
|
+
code: "custom",
|
|
453
|
+
message: `型 ${t.id} の項目 ${m}`
|
|
454
|
+
});
|
|
372
455
|
});
|
|
373
456
|
const modelSchema = z.object({
|
|
374
457
|
components: declaredComponentsSchema.optional(),
|
|
375
458
|
types: z.array(contentType).refine((ts) => new Set(ts.map((t) => t.id)).size === ts.length, { message: "型の id が重複" }).superRefine((ts, ctx) => {
|
|
376
459
|
const ids = new Set(ts.map((t) => t.id));
|
|
377
|
-
for (const t of ts) for (const to of referenceTargets(
|
|
460
|
+
for (const t of ts) for (const to of referenceTargets(t.fields)) if (!ids.has(to)) ctx.addIssue({
|
|
378
461
|
code: "custom",
|
|
379
462
|
message: `型 ${t.id} の参照先 ${to} がスキーマにない`
|
|
380
463
|
});
|
|
381
464
|
for (const t of ts) {
|
|
382
|
-
const keys = new Set(
|
|
465
|
+
const keys = new Set(t.fields.map((f) => f.key));
|
|
383
466
|
for (const c of t.counts ?? []) {
|
|
384
467
|
const from = ts.find((x) => x.id === c.from);
|
|
385
|
-
const ref = from &&
|
|
468
|
+
const ref = from && from.fields.find((f) => f.key === c.field);
|
|
386
469
|
if (ref?.type !== "reference" || ref.to !== t.id) ctx.addIssue({
|
|
387
470
|
code: "custom",
|
|
388
471
|
message: `型 ${t.id} の被参照の件数 ${c.key}: 型 ${c.from} の参照の項目 ${c.field} がこの型を指していない`
|
|
@@ -396,13 +479,13 @@ const modelSchema = z.object({
|
|
|
396
479
|
})
|
|
397
480
|
}).superRefine((m, ctx) => {
|
|
398
481
|
const known = /* @__PURE__ */ new Set([...CORE_COMPONENTS.map((c) => c.name), ...(m.components ?? []).map((c) => c.name)]);
|
|
399
|
-
for (const t of m.types) for (const { key, name } of bodyComponents(
|
|
482
|
+
for (const t of m.types) for (const { key, name } of bodyComponents(t.fields)) if (!known.has(name)) ctx.addIssue({
|
|
400
483
|
code: "custom",
|
|
401
484
|
message: `型 ${t.id} の本文の項目 ${key} の部品 ${name} がコアの部品にも宣言 components にもない`
|
|
402
485
|
});
|
|
403
486
|
});
|
|
404
487
|
/** コンテンツスキーマの JSON を検査して返却(形が合わなければ例外、理由は zod の issues) */
|
|
405
|
-
const parseModel = (json) => modelSchema.parse(json);
|
|
488
|
+
const parseModel = (json) => modelSchema.parse(normalizeLegacyModel(json));
|
|
406
489
|
// ../model/src/screens.ts
|
|
407
490
|
/** メニューにある全項目の id(グループの中も、上から順) */
|
|
408
491
|
const menuItemsOf = (menu) => menu.flatMap((e) => typeof e === "string" ? [e] : e.items);
|
|
@@ -443,9 +526,8 @@ const DEFAULT_SCHEMA = { "types": [
|
|
|
443
526
|
{
|
|
444
527
|
"id": "post",
|
|
445
528
|
"label": "投稿",
|
|
446
|
-
"table": "articles",
|
|
447
529
|
"publishing": "review",
|
|
448
|
-
"
|
|
530
|
+
"fields": [
|
|
449
531
|
{
|
|
450
532
|
"key": "title",
|
|
451
533
|
"label": "タイトル",
|
|
@@ -498,24 +580,47 @@ const DEFAULT_SCHEMA = { "types": [
|
|
|
498
580
|
"components": ["gallery", "link-card"]
|
|
499
581
|
}
|
|
500
582
|
},
|
|
501
|
-
{
|
|
502
|
-
"key": "published_at",
|
|
503
|
-
"label": "公開日時",
|
|
504
|
-
"type": "datetime"
|
|
505
|
-
},
|
|
506
583
|
{
|
|
507
584
|
"key": "revised_at",
|
|
508
585
|
"label": "更新日時",
|
|
509
586
|
"type": "datetime"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
"key": "tags",
|
|
590
|
+
"label": "タグ",
|
|
591
|
+
"type": "reference",
|
|
592
|
+
"to": "tag",
|
|
593
|
+
"multiple": true
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
"key": "excerpt",
|
|
597
|
+
"label": "抜粋",
|
|
598
|
+
"type": "textarea",
|
|
599
|
+
"computed": {
|
|
600
|
+
"kind": "excerpt",
|
|
601
|
+
"from": "body"
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"key": "read_min",
|
|
606
|
+
"label": "読了の分",
|
|
607
|
+
"type": "number",
|
|
608
|
+
"computed": {
|
|
609
|
+
"kind": "read_min",
|
|
610
|
+
"from": "body"
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
"key": "links",
|
|
615
|
+
"label": "リンク先",
|
|
616
|
+
"type": "text",
|
|
617
|
+
"multiple": true,
|
|
618
|
+
"computed": {
|
|
619
|
+
"kind": "links",
|
|
620
|
+
"from": "body"
|
|
621
|
+
}
|
|
510
622
|
}
|
|
511
623
|
],
|
|
512
|
-
"fields": [{
|
|
513
|
-
"key": "tags",
|
|
514
|
-
"label": "タグ",
|
|
515
|
-
"type": "reference",
|
|
516
|
-
"to": "tag",
|
|
517
|
-
"multiple": true
|
|
518
|
-
}],
|
|
519
624
|
"pages": [{
|
|
520
625
|
"path": "/posts/{id}",
|
|
521
626
|
"public": [{
|
|
@@ -565,7 +670,7 @@ const DEFAULT_SCHEMA = { "types": [
|
|
|
565
670
|
"id": "image",
|
|
566
671
|
"label": "画像",
|
|
567
672
|
"description": "見せたい点(hotspot)と切り抜き(crop)を画像ごとに1つ保持、切り抜く場所すべてで共通",
|
|
568
|
-
"
|
|
673
|
+
"fields": [
|
|
569
674
|
{
|
|
570
675
|
"key": "mime_type",
|
|
571
676
|
"label": "形式",
|
|
@@ -600,13 +705,12 @@ const DEFAULT_SCHEMA = { "types": [
|
|
|
600
705
|
"type": "text",
|
|
601
706
|
"description": "使用する側の既定の値、使用する場所ごとに上書き可"
|
|
602
707
|
}
|
|
603
|
-
]
|
|
604
|
-
"fields": []
|
|
708
|
+
]
|
|
605
709
|
},
|
|
606
710
|
{
|
|
607
711
|
"id": "video",
|
|
608
712
|
"label": "動画",
|
|
609
|
-
"
|
|
713
|
+
"fields": [
|
|
610
714
|
{
|
|
611
715
|
"key": "mime_type",
|
|
612
716
|
"label": "形式",
|
|
@@ -654,8 +758,7 @@ const DEFAULT_SCHEMA = { "types": [
|
|
|
654
758
|
"accept": ["image"],
|
|
655
759
|
"description": "読み込み前・動きを抑える設定・OG で代わりに表示"
|
|
656
760
|
}
|
|
657
|
-
]
|
|
658
|
-
"fields": []
|
|
761
|
+
]
|
|
659
762
|
}
|
|
660
763
|
] };
|
|
661
764
|
/** 既定の画面設定(新しい媒体の作成で適用する中立の値、移入する screens の土台) */
|
|
@@ -107,10 +107,10 @@ function typeOf(model, id) {
|
|
|
107
107
|
if (!type) throw new Error(`コンテンツスキーマに型 ${id} がない`);
|
|
108
108
|
return type;
|
|
109
109
|
}
|
|
110
|
-
/**
|
|
110
|
+
/** 選択肢の項目の選択肢(まとまりの中の項目は `links.kind` の形、選択肢の項目でなければ空) */
|
|
111
111
|
function optionsOf(type, key) {
|
|
112
112
|
const [head, ...rest] = key.split(".");
|
|
113
|
-
let field =
|
|
113
|
+
let field = type.fields.find((f) => f.key === head);
|
|
114
114
|
for (const k of rest) field = field?.type === "group" ? field.fields.find((f) => f.key === k) : void 0;
|
|
115
115
|
return field?.type === "select" ? field.options : [];
|
|
116
116
|
}
|
|
@@ -45,8 +45,6 @@ export declare function createDeliveryClient(delivery: Delivery, { drafts, at }?
|
|
|
45
45
|
description?: string;
|
|
46
46
|
format?: import("../../model/src/index.js").ContentFormat;
|
|
47
47
|
publishing?: import("../../model/src/index.js").Publishing;
|
|
48
|
-
table?: string;
|
|
49
|
-
core?: readonly import("../../model/src/index.js").FieldDef[];
|
|
50
48
|
fields: readonly import("../../model/src/index.js").FieldDef[];
|
|
51
49
|
pages?: readonly import("../../model/src/index.js").PageDef[];
|
|
52
50
|
counts?: readonly import("../../model/src/index.js").CountDef[];
|
|
@@ -3,4 +3,4 @@ export { deliveredImageUrl, mediaOf, type ImageUrl, type MediaOf } from './media
|
|
|
3
3
|
export { imageParamsOf, parseMediaPath, withImageParams, type ImageParams, type MediaPath } from '../../model/src/index.js';
|
|
4
4
|
export { listParams, type ListFilter, type ListQuery, type Page } from './query.js';
|
|
5
5
|
export { createDeliveryClock, fieldOf, limitOf, optionLabels, pagePath, type DeliveryClock } from './schema.js';
|
|
6
|
-
export type { DeliveredAsset, FieldValue, Item, ItemOf } from './values.js';
|
|
6
|
+
export type { DeliveredAsset, FieldValue, Item, ItemMeta, ItemOf } from './values.js';
|
|
@@ -29,13 +29,14 @@ type SortableKey<T extends ContentType> = FieldsOf<T> extends infer F ? (F exten
|
|
|
29
29
|
type: 'text' | 'number' | 'date' | 'datetime' | 'select' | 'checkbox';
|
|
30
30
|
key: infer K extends string;
|
|
31
31
|
} ? K : never) : never;
|
|
32
|
-
/** 並べ替えの key
|
|
33
|
-
type OrderKey<T extends ContentType> = SortableKey<T> | 'published_at' | 'updated_at' | 'id';
|
|
34
|
-
/** 絞り込みの指定(項目の key
|
|
32
|
+
/** 並べ替えの key(項目・公開日時・作成日時・更新日時・id) */
|
|
33
|
+
type OrderKey<T extends ContentType> = SortableKey<T> | 'published_at' | 'created_at' | 'updated_at' | 'id';
|
|
34
|
+
/** 絞り込みの指定(項目の key ごと、公開日時・作成日時・更新日時は範囲) */
|
|
35
35
|
export type ListFilter<T extends ContentType = ContentType> = Readonly<{
|
|
36
36
|
[F in FieldsOf<T> as [FilterValue<F>] extends [never] ? never : F['key']]?: FilterValue<F>;
|
|
37
37
|
} & {
|
|
38
38
|
published_at?: Range<string>;
|
|
39
|
+
created_at?: Range<string>;
|
|
39
40
|
updated_at?: Range<string>;
|
|
40
41
|
}>;
|
|
41
42
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Clock, type ContentModel, type DateString, type FieldDef, type PageEntry } from '../../model/src/index.js';
|
|
2
2
|
/** 選択肢の項目から表示名の表(値 → 表示名、key はまとまりの中なら `links.kind` の形、選択肢の項目でなければ空) */
|
|
3
3
|
export declare const optionLabels: <K extends string = string>(schema: ContentModel, type: string, key: string) => Readonly<Record<K, string>>;
|
|
4
|
-
/**
|
|
4
|
+
/** 型の項目(なければ例外: 導入先の設定の誤り) */
|
|
5
5
|
export declare function fieldOf(schema: ContentModel, type: string, key: string): FieldDef;
|
|
6
6
|
/** 項目の宣言の上限(宣言がなければ例外) */
|
|
7
7
|
export declare function limitOf(field: FieldDef, name: 'maxItems' | 'maxLength'): number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Body } from '../../body/src/index.js';
|
|
2
2
|
import type { ContentType, Crop, DateString, FieldDef, Hotspot } from '../../model/src/index.js';
|
|
3
|
-
/** 配信 API の1
|
|
3
|
+
/** 配信 API の1件の値(id・項目の key・メタ情報 _meta) */
|
|
4
4
|
export type Item = Readonly<Record<string, unknown>>;
|
|
5
5
|
/** 配信 API が解決した実体(url は配信 URL で配信のオリジンがある CMS だけ、hotspot・crop は元の画像に対する割合) */
|
|
6
6
|
export type DeliveredAsset = Readonly<{
|
|
@@ -52,12 +52,18 @@ type GroupValue<G extends FieldDef> = {
|
|
|
52
52
|
export type FieldValue<F extends FieldDef> = (F extends {
|
|
53
53
|
multiple: true;
|
|
54
54
|
} ? readonly SingleValue<F>[] : SingleValue<F>) | null;
|
|
55
|
-
/**
|
|
56
|
-
export type FieldsOf<T extends ContentType> =
|
|
57
|
-
/**
|
|
55
|
+
/** 型の項目 */
|
|
56
|
+
export type FieldsOf<T extends ContentType> = T['fields'][number];
|
|
57
|
+
/** 1件のメタ情報(作成・更新の日時、公開日時は公開の管理がある型だけで未公開は null、日時は `ISO 8601` の UTC) */
|
|
58
|
+
export type ItemMeta = Readonly<{
|
|
59
|
+
created_at: string;
|
|
60
|
+
updated_at: string;
|
|
61
|
+
published_at?: string | null;
|
|
62
|
+
}>;
|
|
63
|
+
/** 型の宣言から導く1件の値(id・項目の key ごとの値・メタ情報 _meta) */
|
|
58
64
|
export type ItemOf<T extends ContentType> = Readonly<{
|
|
59
65
|
id: string;
|
|
60
|
-
|
|
66
|
+
_meta: ItemMeta;
|
|
61
67
|
} & {
|
|
62
68
|
[F in FieldsOf<T> as F['key']]: FieldValue<F>;
|
|
63
69
|
}>;
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import type
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
shared: boolean;
|
|
1
|
+
import { type ContentModel, type ContentType, type FieldDef } from './model.js';
|
|
2
|
+
/** 出典の項目と、内側の項目の key */
|
|
3
|
+
export declare const SOURCES_FIELD: {
|
|
4
|
+
readonly key: "sources";
|
|
5
|
+
readonly source: "source";
|
|
6
|
+
readonly note: "note";
|
|
7
|
+
};
|
|
8
|
+
/** 出典の項目の定義(参照の項目 source の参照先 to) */
|
|
9
|
+
export type SourcesField = Extract<FieldDef, {
|
|
10
|
+
type: 'group';
|
|
11
|
+
}> & Readonly<{
|
|
12
|
+
to: string;
|
|
14
13
|
}>;
|
|
15
|
-
/**
|
|
16
|
-
export declare const
|
|
17
|
-
/**
|
|
18
|
-
export declare
|
|
19
|
-
/**
|
|
20
|
-
export declare function
|
|
14
|
+
/** 主の型(スキーマの順で先頭、なければ null) */
|
|
15
|
+
export declare const primaryTypeOf: (model: ContentModel) => ContentType | null;
|
|
16
|
+
/** 主の型(なければ例外: その型を前提にする画面・処理だけが使用) */
|
|
17
|
+
export declare function requiredPrimaryTypeOf(model: ContentModel): ContentType;
|
|
18
|
+
/** 型の出典の項目(なければ null) */
|
|
19
|
+
export declare function sourcesFieldOf(type: ContentType): SourcesField | null;
|
|
20
|
+
/** 出典の型(出典の項目の参照先でスキーマの順で先頭、なければ null) */
|
|
21
|
+
export declare function sourceTypeOf(model: ContentModel): ContentType | null;
|
|
22
|
+
/** 出典の値にある参照の ID(注の順、空と形の誤りは除外) */
|
|
23
|
+
export declare const sourceIdsOf: (value: unknown) => string[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ContentModel } from './model.js';
|
|
2
2
|
/** 変更の扱い */
|
|
3
3
|
export type ChangeLevel = 'safe' | 'confirm' | 'forbidden';
|
|
4
4
|
/** 変更の種類 */
|
|
@@ -13,11 +13,9 @@ export type SchemaChange = Readonly<{
|
|
|
13
13
|
value?: string;
|
|
14
14
|
message: string;
|
|
15
15
|
}>;
|
|
16
|
-
/** 差分の条件(
|
|
16
|
+
/** 差分の条件(history は保存済みの版より古い過去の版) */
|
|
17
17
|
export type DiffOptions = Readonly<{
|
|
18
|
-
fixed?: ContentModel;
|
|
19
18
|
history?: readonly ContentModel[];
|
|
20
19
|
}>;
|
|
21
20
|
/** 保存済みの版 prev から新しい定義 next への変更(型の順、項目の順) */
|
|
22
21
|
export declare function diffSchema(prev: ContentModel, next: ContentModel, options?: DiffOptions): SchemaChange[];
|
|
23
|
-
export declare function withFixed(fixed: ContentModel, stored: ContentModel): ContentModel;
|
|
@@ -3,12 +3,13 @@ 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,
|
|
7
|
-
export {
|
|
6
|
+
export { diffSchema, type ChangeKind, type ChangeLevel, type DiffOptions, type SchemaChange } from './changes.js';
|
|
7
|
+
export { 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';
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
10
|
+
export { entryRefsOf, type EntryRef } from './refs.js';
|
|
11
|
+
export { bodyFieldOf, bodyFieldsOf, computedFieldsOf, idInputOf, imageFieldOf, initialValueOf, isEditorial, isInputField, isListType, isMediaType, isSingleton, isStoredType, MEDIA_TYPE_IDS, optionLabelOf, optionsOf, publishingOf, requestReferencesOf, titleFieldOf, toneOf, typeOf, type ComputedKind, type FieldComputed, type ContentFormat, type ContentModel, type ContentType, type FieldCondition, type FieldDef, type FieldDerive, type FieldFixedBy, type CountDef, type FieldProblem, type MediaKind, type OptionTone, type Publishing, type ReferenceRules, type SelectOption } from './model.js';
|
|
12
|
+
export { hasPublicPage, imageFrameOf, pagePathOf, publicPathOf, withPagesFrom, type ImageFrame, type ImageFrameDef, type PageDef, type PageEntry, type PageRule } from './page.js';
|
|
12
13
|
export { schemaRulesOf, type SchemaRule } from './rules.js';
|
|
13
14
|
export { parseModel } from './schema.js';
|
|
14
15
|
export { menuItemsOf, parseScreens, placedItemsOf, resolveLayout, resolveMenu, typeItem, typeOfItem, viewItem, viewOfItem, type EditorLayout, type EditorSection, type MenuEntry, type MenuGroup, type ResolvedMenuEntry, type ScreenSettings } from './screens.js';
|
|
@@ -15,6 +15,15 @@ type FieldBase = Readonly<{
|
|
|
15
15
|
adminOnly?: boolean;
|
|
16
16
|
/** 未定の null にできる期限: 型の最上位の項目 key に値が入ると未定なし(方針のページの制定日など) */
|
|
17
17
|
pendingUntil?: string;
|
|
18
|
+
/** 計算の項目(保存のときに本文の項目から作成、入力の対象外) */
|
|
19
|
+
computed?: FieldComputed;
|
|
20
|
+
}>;
|
|
21
|
+
/** 計算の項目の種類(excerpt は導入の段落・read_min は読了の分・links はサイト内へのリンク先) */
|
|
22
|
+
export type ComputedKind = 'excerpt' | 'read_min' | 'links';
|
|
23
|
+
/** 計算の項目の宣言(from は同じ型の最上位にある本文の項目の key) */
|
|
24
|
+
export type FieldComputed = Readonly<{
|
|
25
|
+
kind: ComputedKind;
|
|
26
|
+
from: string;
|
|
18
27
|
}>;
|
|
19
28
|
/** 項目の対象の条件(同じ階層にある選択肢の項目 key の値が in のいずれか) */
|
|
20
29
|
export type FieldCondition = Readonly<{
|
|
@@ -67,7 +76,7 @@ export type ReferenceRules = Readonly<{
|
|
|
67
76
|
published?: boolean;
|
|
68
77
|
linkedBy?: string;
|
|
69
78
|
}>;
|
|
70
|
-
/**
|
|
79
|
+
/** メディアの種類(メディアの型 image・video の id と同じ) */
|
|
71
80
|
export type MediaKind = 'image' | 'video';
|
|
72
81
|
/** 項目の定義 */
|
|
73
82
|
export type FieldDef = FieldBase & Readonly<{
|
|
@@ -95,7 +104,7 @@ export type FieldDef = FieldBase & Readonly<{
|
|
|
95
104
|
} & Counts) | ({
|
|
96
105
|
type: 'reference';
|
|
97
106
|
to: string;
|
|
98
|
-
multiple?: boolean; /**
|
|
107
|
+
multiple?: boolean; /** 候補の絞り込み(参照先の項目の key と値、例: 企画の種類が特集) */
|
|
99
108
|
where?: Readonly<Record<string, string>>; /** 候補にない名前を追加の依頼で付けられる(参照先は一覧で管理する汎用の型、承認で行を作成) */
|
|
100
109
|
requests?: boolean; /** 値が空のときの表示名(持ち主のいない行の名義など) */
|
|
101
110
|
emptyLabel?: string;
|
|
@@ -117,19 +126,14 @@ export type ContentFormat = 'list' | 'object';
|
|
|
117
126
|
/** 公開の管理(なし・下書きと公開・レビューあり) */
|
|
118
127
|
export type Publishing = 'none' | 'draft' | 'review';
|
|
119
128
|
/**
|
|
120
|
-
*
|
|
121
|
-
* - id:
|
|
122
|
-
* -
|
|
123
|
-
* - format: 形式(なしはリスト形式)、オブジェクト形式はコアの項目なし
|
|
129
|
+
* コンテンツタイプ
|
|
130
|
+
* - id: 型の ID(メディアの型は image・video)
|
|
131
|
+
* - format: 形式(なしはリスト形式)
|
|
124
132
|
* - publishing: 公開の管理(なしは管理なし)
|
|
125
|
-
* - table: コアの項目を持つ型の表
|
|
126
133
|
* - pages: サイトのページの URL と公開中の条件(なしはページのない型、page.ts)
|
|
127
|
-
*/
|
|
128
|
-
/**
|
|
129
|
-
* コンテンツタイプ
|
|
130
134
|
* - counts: 被参照の件数(ページの公開中の条件・ページの公開で必須の判定に使用)
|
|
131
135
|
* - og: サイトの OG 画像のパスの雛形({key} は行の値、管理画面の OG 画像のプレビュー)
|
|
132
|
-
* - idInput: 作成のときに管理画面で ID
|
|
136
|
+
* - idInput: 作成のときに管理画面で ID を入力できる(ページのある型は常に入力可、空欄は自動)
|
|
133
137
|
*/
|
|
134
138
|
export type ContentType = Readonly<{
|
|
135
139
|
id: string;
|
|
@@ -137,8 +141,6 @@ export type ContentType = Readonly<{
|
|
|
137
141
|
description?: string;
|
|
138
142
|
format?: ContentFormat;
|
|
139
143
|
publishing?: Publishing;
|
|
140
|
-
table?: string;
|
|
141
|
-
core?: readonly FieldDef[];
|
|
142
144
|
fields: readonly FieldDef[];
|
|
143
145
|
pages?: readonly PageDef[];
|
|
144
146
|
counts?: readonly CountDef[];
|
|
@@ -155,15 +157,17 @@ export type FieldProblem = Readonly<{
|
|
|
155
157
|
path: string;
|
|
156
158
|
message: string;
|
|
157
159
|
}>;
|
|
158
|
-
/**
|
|
159
|
-
export declare const
|
|
160
|
-
/**
|
|
160
|
+
/** メディアの型の id(項目の種類 media の accept と同じ、行は表 assets) */
|
|
161
|
+
export declare const MEDIA_TYPE_IDS: readonly MediaKind[];
|
|
162
|
+
/** メディアの型か(画像・動画、行は表 assets で一覧・作成・配信の対象外) */
|
|
163
|
+
export declare const isMediaType: (type: ContentType) => boolean;
|
|
164
|
+
/** 作成のときに ID を入力できる型か(メディアの型を除き、宣言 idInput かページを持つ型) */
|
|
161
165
|
export declare const idInputOf: (type: ContentType) => boolean;
|
|
162
166
|
/** オブジェクト形式の型か(行は表 entries の id が型の id の1行、一覧・作成の対象外) */
|
|
163
167
|
export declare const isSingleton: (type: ContentType) => boolean;
|
|
164
168
|
/** 公開の管理(なしは none) */
|
|
165
169
|
export declare const publishingOf: (type: ContentType) => Publishing;
|
|
166
|
-
/**
|
|
170
|
+
/** 一覧で行を追加・管理する型か(オブジェクト形式の型・メディアの型を除く) */
|
|
167
171
|
export declare const isListType: (type: ContentType) => boolean;
|
|
168
172
|
/**
|
|
169
173
|
* 編集の機能を持つ型か(公開の管理がある一覧の型)
|
|
@@ -171,11 +175,11 @@ export declare const isListType: (type: ContentType) => boolean;
|
|
|
171
175
|
* - 提出と差し戻し: レビューありの型だけ
|
|
172
176
|
*/
|
|
173
177
|
export declare const isEditorial: (type: ContentType) => boolean;
|
|
174
|
-
/**
|
|
175
|
-
export declare const
|
|
178
|
+
/** 型の行を表 entries に保存するか(メディアの型を除くすべて) */
|
|
179
|
+
export declare const isStoredType: (type: ContentType) => boolean;
|
|
176
180
|
/** コンテンツスキーマの型(id から、なければ例外: 導入先の設定の誤り) */
|
|
177
181
|
export declare function typeOf(model: ContentModel, id: string): ContentType;
|
|
178
|
-
/**
|
|
182
|
+
/** 選択肢の項目の選択肢(まとまりの中の項目は `links.kind` の形、選択肢の項目でなければ空) */
|
|
179
183
|
export declare function optionsOf(type: ContentType, key: string): readonly SelectOption[];
|
|
180
184
|
/** 新しい行の初期値(セレクトは項目の initial、なければ先頭の選択肢、セレクトでなければ空文字) */
|
|
181
185
|
export declare const initialValueOf: (type: ContentType, key: string) => string;
|
|
@@ -183,11 +187,23 @@ export declare const initialValueOf: (type: ContentType, key: string) => string;
|
|
|
183
187
|
export declare const toneOf: (type: ContentType, key: string, value: string) => OptionTone;
|
|
184
188
|
/** 選択肢の表示名(なければ値のまま) */
|
|
185
189
|
export declare const optionLabelOf: (type: ContentType, key: string, value: string) => string;
|
|
190
|
+
/** 入力する項目か(計算の項目を除く) */
|
|
191
|
+
export declare const isInputField: (f: FieldDef) => boolean;
|
|
186
192
|
/** 型の表示名の項目(複数でない文字の項目で先頭、なければ null: 行の表示名に使用) */
|
|
187
193
|
export declare const titleFieldOf: (type: ContentType) => FieldDef | null;
|
|
188
|
-
/** 型のサムネの項目(複数でない画像の項目で先頭、なければ null:
|
|
194
|
+
/** 型のサムネの項目(複数でない画像の項目で先頭、なければ null: 一覧・参照の候補・アイキャッチに使用) */
|
|
189
195
|
export declare const imageFieldOf: (type: ContentType) => FieldDef | null;
|
|
190
|
-
/**
|
|
196
|
+
/** 型の本文の項目(最上位で先頭、なければ null: 本文の Markdown・点検・制作の案内に使用) */
|
|
197
|
+
export declare const bodyFieldOf: (type: ContentType) => Extract<FieldDef, {
|
|
198
|
+
type: "body";
|
|
199
|
+
}> | null;
|
|
200
|
+
/** 最上位で本文の種類の項目(表 entry_bodies に保存、一覧では取得しない) */
|
|
201
|
+
export declare const bodyFieldsOf: (type: ContentType) => Extract<FieldDef, {
|
|
202
|
+
type: "body";
|
|
203
|
+
}>[];
|
|
204
|
+
/** 型の計算の項目(最上位) */
|
|
205
|
+
export declare const computedFieldsOf: (type: ContentType) => FieldDef[];
|
|
206
|
+
/** 追加の依頼ができる参照の項目(参照先が一覧で管理する型で requests の指定があるもの、タグ・分類など) */
|
|
191
207
|
export declare function requestReferencesOf(model: ContentModel, type: ContentType): Extract<FieldDef, {
|
|
192
208
|
type: 'reference';
|
|
193
209
|
}>[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentType, FieldCondition } from './model.js';
|
|
1
|
+
import type { ContentModel, ContentType, FieldCondition } from './model.js';
|
|
2
2
|
/** ページの行の値(項目の key と値、表の列・集計の値も含む) */
|
|
3
3
|
export type PageEntry = Readonly<Record<string, unknown>>;
|
|
4
4
|
/**
|
|
@@ -43,5 +43,10 @@ export declare function pagePathOf(type: ContentType, entry: PageEntry): string
|
|
|
43
43
|
export declare function hasPublicPage(type: ContentType, entry: PageEntry, now: string): boolean;
|
|
44
44
|
/** 公開中のページのパス(公開前・予約中・ページのない行は null、now は今の時刻の ISO 文字列) */
|
|
45
45
|
export declare function publicPathOf(type: ContentType, entry: PageEntry, now: string): string | null;
|
|
46
|
-
/** 画像の項目 key
|
|
46
|
+
/** 画像の項目 key の枠(最上位の項目から、枠の宣言がなければ null) */
|
|
47
47
|
export declare function imageFrameOf(type: ContentType, key: string, entry: PageEntry): ImageFrame | null;
|
|
48
|
+
/**
|
|
49
|
+
* 保存済みでページの宣言がない型に、最初の版の宣言を補う(宣言の導入より前に保存した版の読み込み)
|
|
50
|
+
* - ページなしにするのは空の配列(補わない)
|
|
51
|
+
*/
|
|
52
|
+
export declare function withPagesFrom(first: ContentModel, stored: ContentModel): ContentModel;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FieldDef } from './model.js';
|
|
2
|
+
/** 参照の逆引きの1行(toType は参照先の型、toId は参照先の ID) */
|
|
3
|
+
export type EntryRef = Readonly<{
|
|
4
|
+
key: string;
|
|
5
|
+
position: number;
|
|
6
|
+
toType: string;
|
|
7
|
+
toId: string;
|
|
8
|
+
}>;
|
|
9
|
+
/** 行の値の参照(fields は型の項目、values は行の値) */
|
|
10
|
+
export declare function entryRefsOf(fields: readonly FieldDef[], values: unknown): EntryRef[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { createDeliveryClient, createDeliveryClock, deliveredImageUrl, DELIVERY_PATH, DeliveryError, fieldOf, imageParamsOf, limitOf, listParams, mediaOf, optionLabels, pagePath, parseMediaPath, subPath, withImageParams } from '../../delivery-client/src/index.js';
|
|
2
|
-
export type { BundleResult, DeliveredAsset, Delivery, DeliveryClient, DeliveryClock, FieldValue, ImageParams, ImageUrl, Item, ItemOf, ListFilter, ListQuery, MediaOf, MediaPath, Page, Prefetched } from '../../delivery-client/src/index.js';
|
|
2
|
+
export type { BundleResult, DeliveredAsset, Delivery, DeliveryClient, DeliveryClock, FieldValue, ImageParams, ImageUrl, Item, ItemMeta, ItemOf, ListFilter, ListQuery, MediaOf, MediaPath, Page, Prefetched } from '../../delivery-client/src/index.js';
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import type { Delivery } from '../../delivery-client/src/index.js';
|
|
2
|
-
/**
|
|
2
|
+
/** 記録の1件(記録のファイルの JSON) */
|
|
3
|
+
export type FixtureResponse = Readonly<{
|
|
4
|
+
method: string;
|
|
5
|
+
path: string;
|
|
6
|
+
status: number;
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
body: string;
|
|
9
|
+
}>;
|
|
10
|
+
/** 呼び出しの記録のファイル名(メソッド・パス・If-None-Match・本文のハッシュ) */
|
|
11
|
+
export declare const fixtureName: (method: string, path: string, ifNoneMatch?: string, body?: string) => string;
|
|
12
|
+
/** 記録の再生(port Delivery、dir は記録のディレクトリ) */
|
|
13
|
+
export declare function fixtureDelivery(dir: string | URL): Delivery;
|
|
14
|
+
/** 公開の配信 API の中継先(origin は媒体の識別子までの URL、例: <公開の配信 API のオリジン>/demo) */
|
|
3
15
|
export type Upstream = Readonly<{
|
|
4
16
|
origin: string;
|
|
5
17
|
apiKey: string;
|
|
6
18
|
previewSecret?: string;
|
|
7
19
|
}>;
|
|
8
|
-
/**
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}>;
|
|
13
|
-
/** 記録の配信 API(port Delivery、upstream があれば中継して保存、並行の書き込みは一時のファイルから置き換え) */
|
|
14
|
-
export declare function fixtureDelivery({ dir, upstream }: FixtureOptions): Delivery;
|
|
15
|
-
/** 手元の配信 API のサーバー(/content/v1/… だけ、記録にない呼び出しは502、戻り値は origin と停止の関数) */
|
|
16
|
-
export declare function serveDelivery(options: FixtureOptions & Readonly<{
|
|
20
|
+
/** 公開の配信 API への中継(port Delivery、読み取りだけで書き込みの操作は405) */
|
|
21
|
+
export declare function relayDelivery({ origin, apiKey, previewSecret }: Upstream): Delivery;
|
|
22
|
+
/** 手元の配信 API のサーバー(/content/v1/… だけ、失敗は502、戻り値は origin と停止の関数) */
|
|
23
|
+
export declare function serveDelivery(delivery: Delivery, options?: Readonly<{
|
|
17
24
|
port?: number;
|
|
18
25
|
hostname?: string;
|
|
19
26
|
}>): Promise<Readonly<{
|