@yanqirenshi/model.deployment 0.0.0-stage → 0.1.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.
@@ -0,0 +1,35 @@
1
+ import type { MergePatch, DeploymentChange, DeploymentCollection, DeploymentMove, DeploymentNodeRecord, DeploymentRecord, DeploymentValues } from './types.js';
2
+ /** 入力の検証エラー。message は利用者向け */
3
+ export declare class ModelError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ /** レコードを置くコレクション */
7
+ export declare function collectionOf(record: DeploymentRecord): DeploymentCollection;
8
+ /** インスペクタの見出し。ラベルの文字、無ければ「種類 id」(線は「接続元 → 接続先」) */
9
+ export declare function titleOf(record: DeploymentRecord): string;
10
+ /** ドット区切りの path で値を取り出す */
11
+ export declare function getByPath(obj: unknown, path: string): unknown;
12
+ /**
13
+ * JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
14
+ * null の項目は除く。保存したあと、表示を先に更新するときに使う。
15
+ */
16
+ export declare function mergePatch<T extends object>(record: T, patch: MergePatch): T;
17
+ /**
18
+ * ドラッグで動かしたノードの位置を保存する patch。
19
+ * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
20
+ * 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
21
+ */
22
+ export declare function movePatch(node: DeploymentNodeRecord, move: DeploymentMove): DeploymentChange;
23
+ /**
24
+ * 色として読める文字列か。
25
+ * ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
26
+ * #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
27
+ */
28
+ export declare function isColor(value: string): boolean;
29
+ /**
30
+ * インスペクタの「適用」の値を、保存する patch に変換する。
31
+ * 図に描いている値から変えた項目だけを patch に入れる(変えていない既定の値まで書き込まないため)。
32
+ * 変更が無ければ patch は {}。
33
+ */
34
+ export declare function applyPatch(record: DeploymentRecord, values: DeploymentValues): DeploymentChange;
35
+ //# sourceMappingURL=patch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,UAAU,EAAE,gBAAgB,EAAE,oBAAoB,EAAwB,cAAc,EAAE,oBAAoB,EAC9G,gBAAgB,EAAE,gBAAgB,EACnC,MAAM,YAAY,CAAC;AAEpB,8BAA8B;AAC9B,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,oBAAoB;AACpB,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,oBAAoB,CAE3E;AAED,mDAAmD;AACnD,wBAAgB,OAAO,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAUxD;AAED,2BAA2B;AAC3B,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAK7D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,UAAU,GAAG,CAAC,CAmB5E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAa5F;AAUD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAY9C;AA4LD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAM/F"}
package/dist/patch.js ADDED
@@ -0,0 +1,251 @@
1
+ // UI の操作(ドラッグ・インスペクタの適用)を、保存する patch(JSON Merge Patch)に変換する。
2
+ //
3
+ // 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
4
+ // 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
5
+ // model.sitemap の patch.ts を元にしている(ラベルは label.text、種類は NODE / COMPONENT、線に矢印の有無がある)。
6
+ import { NODE_TYPES, isEdge, withDefaults } from './defaults.js';
7
+ import { fieldsOf } from './fields.js';
8
+ /** 入力の検証エラー。message は利用者向け */
9
+ export class ModelError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'ModelError';
13
+ }
14
+ }
15
+ /** レコードを置くコレクション */
16
+ export function collectionOf(record) {
17
+ return isEdge(record) ? 'edges' : 'nodes';
18
+ }
19
+ /** インスペクタの見出し。ラベルの文字、無ければ「種類 id」(線は「接続元 → 接続先」) */
20
+ export function titleOf(record) {
21
+ if (isEdge(record))
22
+ return `${String(record.from.id)} → ${String(record.to.id)}`;
23
+ const text = record.label && typeof record.label === 'object' ? record.label.text : null;
24
+ if (typeof text === 'string' && text !== '')
25
+ return text;
26
+ return `${record.type || ''} ${String(record.id)}`.trim();
27
+ }
28
+ /** ドット区切りの path で値を取り出す */
29
+ export function getByPath(obj, path) {
30
+ return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
31
+ }
32
+ /**
33
+ * JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
34
+ * null の項目は除く。保存したあと、表示を先に更新するときに使う。
35
+ */
36
+ export function mergePatch(record, patch) {
37
+ const out = { ...record };
38
+ for (const [key, value] of Object.entries(patch)) {
39
+ if (value === null) {
40
+ delete out[key];
41
+ }
42
+ else if (typeof value === 'object' && !Array.isArray(value)) {
43
+ const base = out[key];
44
+ out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
45
+ }
46
+ else {
47
+ out[key] = value;
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+ /**
53
+ * ドラッグで動かしたノードの位置を保存する patch。
54
+ * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。
55
+ * 子のノード(COMPONENT を含む)の position は親の中での位置なので、親を動かしても子の patch は要らない。
56
+ */
57
+ export function movePatch(node, move) {
58
+ const base = node.position || { x: 0, y: 0 };
59
+ return {
60
+ collection: 'nodes',
61
+ id: node.id,
62
+ patch: {
63
+ position: {
64
+ x: Math.round(base.x + move.dx),
65
+ y: Math.round(base.y + move.dy),
66
+ },
67
+ },
68
+ };
69
+ }
70
+ // ---- 入力の読み取り ------------------------------------------------------------
71
+ function toNumber(value) {
72
+ const n = Number(value);
73
+ return value !== '' && Number.isFinite(n) ? n : null;
74
+ }
75
+ /**
76
+ * 色として読める文字列か。
77
+ * ブラウザでは CSS.supports('color', v) で判定する。Node など CSS の無い環境では、
78
+ * #rgb / #rrggbb / #rrggbbaa・rgb() などの関数表記・英字だけの色名、を通す緩い判定にする。
79
+ */
80
+ export function isColor(value) {
81
+ if (value === '')
82
+ return false;
83
+ const css = globalThis.CSS;
84
+ if (css && typeof css.supports === 'function')
85
+ return css.supports('color', value);
86
+ return /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)
87
+ || /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(.+\)$/i.test(value)
88
+ || /^[a-z]+$/i.test(value);
89
+ }
90
+ function toColor(value, label) {
91
+ if (!isColor(value))
92
+ throw new ModelError(`${label}は、色 (#ff0000 や red など) で入力してください。`);
93
+ return value;
94
+ }
95
+ // 0 より大きい数(枠線・線の太さ。d3.deployment は 0 を「書いていない」と見て既定に倒すので、0 は入れられない)
96
+ function toWidth(value, label) {
97
+ const n = toNumber(value);
98
+ if (n === null || n <= 0)
99
+ throw new ModelError(`${label}は、0 より大きい数で入力してください。`);
100
+ return n;
101
+ }
102
+ // 0 以上の整数(幅・高さ。0 は「子に合わせる」)
103
+ function toLength(value, label) {
104
+ const n = toNumber(value);
105
+ if (n === null || n < 0)
106
+ throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で子に合わせる)`);
107
+ return Math.round(n);
108
+ }
109
+ // 角度(0 以上 360 未満の整数に正規化)
110
+ function toDegree(value, label) {
111
+ const n = toNumber(value);
112
+ if (n === null)
113
+ throw new ModelError(`${label}は、角度 (数) で入力してください。`);
114
+ return ((Math.round(n) % 360) + 360) % 360;
115
+ }
116
+ function toOption(value, options, label) {
117
+ if (!options.includes(value))
118
+ throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
119
+ return value;
120
+ }
121
+ // true / false
122
+ function toBoolean(value, label) {
123
+ if (value !== 'true' && value !== 'false')
124
+ throw new ModelError(`${label}は、true / false のどちらかで入力してください。`);
125
+ return value === 'true';
126
+ }
127
+ // 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
128
+ function toOptionalText(value) {
129
+ return value === '' ? null : value;
130
+ }
131
+ // patch の入れ子に値を置く(patch.border.color = v のように)
132
+ function put(patch, path, value) {
133
+ const keys = path.split('.');
134
+ let cur = patch;
135
+ for (const key of keys.slice(0, -1)) {
136
+ if (!cur[key] || typeof cur[key] !== 'object')
137
+ cur[key] = {};
138
+ cur = cur[key];
139
+ }
140
+ cur[keys[keys.length - 1]] = value;
141
+ }
142
+ /**
143
+ * インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
144
+ * 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
145
+ */
146
+ function changedValues(record, values) {
147
+ const effective = withDefaults(record);
148
+ const out = new Map();
149
+ for (const field of fieldsOf(record)) {
150
+ if (field.type === 'readonly')
151
+ continue;
152
+ const value = values[field.path];
153
+ if (value === undefined)
154
+ continue;
155
+ const current = getByPath(effective, field.path);
156
+ if (value.trim() !== String(current ?? ''))
157
+ out.set(field.path, value.trim());
158
+ }
159
+ return out;
160
+ }
161
+ // ノードの入力 → patch(label.text / type / description / link.url / size / background / border)
162
+ function nodePatch(node, values) {
163
+ const patch = {};
164
+ for (const [path, value] of changedValues(node, values)) {
165
+ switch (path) {
166
+ case 'label.text':
167
+ // 名称は空にできない(d3.deployment は名称の無いノードに '????????' を描く)
168
+ if (value === '')
169
+ throw new ModelError('名称を入力してください。');
170
+ put(patch, path, value);
171
+ break;
172
+ case 'type':
173
+ put(patch, path, toOption(value, NODE_TYPES, '種類'));
174
+ break;
175
+ case 'description':
176
+ put(patch, path, toOptionalText(value));
177
+ break;
178
+ case 'link.url':
179
+ // URL を空にするとリンクごと除く(d3.deployment は url の無い link を描かない)
180
+ if (value === '')
181
+ patch.link = null;
182
+ else
183
+ put(patch, path, value);
184
+ break;
185
+ case 'size.w':
186
+ put(patch, path, toLength(value, '幅'));
187
+ break;
188
+ case 'size.h':
189
+ put(patch, path, toLength(value, '高さ'));
190
+ break;
191
+ case 'background.color':
192
+ put(patch, path, toColor(value, '背景色'));
193
+ break;
194
+ case 'border.color':
195
+ put(patch, path, toColor(value, '枠線の色'));
196
+ break;
197
+ case 'border.width':
198
+ put(patch, path, toWidth(value, '枠線の太さ'));
199
+ break;
200
+ }
201
+ }
202
+ return patch;
203
+ }
204
+ const END_LABELS = { from: '接続元', to: '接続先' };
205
+ // 線の入力 → patch(from / to の角度・stroke・矢印)
206
+ function edgePatch(edge, values) {
207
+ const patch = {};
208
+ for (const [path, value] of changedValues(edge, values)) {
209
+ switch (path) {
210
+ case 'from.position':
211
+ case 'to.position':
212
+ put(patch, path, toDegree(value, `${END_LABELS[path.split('.')[0]]}の位置`));
213
+ break;
214
+ case 'stroke.color':
215
+ put(patch, path, toColor(value, '線の色'));
216
+ break;
217
+ case 'stroke.width':
218
+ put(patch, path, toWidth(value, '線の太さ'));
219
+ break;
220
+ case 'stroke.marker.start':
221
+ put(patch, path, toBoolean(value, '始点の矢印'));
222
+ break;
223
+ case 'stroke.marker.end':
224
+ put(patch, path, toBoolean(value, '終点の矢印'));
225
+ break;
226
+ }
227
+ }
228
+ // 矢印を片方だけ書くと d3.deployment の Edge.normalize は marker を丸ごと入力で置き換える(無い側は undefined =
229
+ // start は偽、end は truthy 判定で偽)ので、片方を変えたときはもう片方も描いている値で埋める
230
+ const marker = patch.stroke?.marker;
231
+ if (marker) {
232
+ const effective = withDefaults(edge).stroke.marker;
233
+ if (marker.start === undefined)
234
+ marker.start = effective.start;
235
+ if (marker.end === undefined)
236
+ marker.end = effective.end;
237
+ }
238
+ return patch;
239
+ }
240
+ /**
241
+ * インスペクタの「適用」の値を、保存する patch に変換する。
242
+ * 図に描いている値から変えた項目だけを patch に入れる(変えていない既定の値まで書き込まないため)。
243
+ * 変更が無ければ patch は {}。
244
+ */
245
+ export function applyPatch(record, values) {
246
+ return {
247
+ collection: collectionOf(record),
248
+ id: record.id,
249
+ patch: isEdge(record) ? edgePatch(record, values) : nodePatch(record, values),
250
+ };
251
+ }
@@ -0,0 +1,274 @@
1
+ export declare const NODES_SCHEMA: {
2
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
3
+ readonly title: "システム構成図のノード";
4
+ readonly description: "d3.deployment に渡すノード。children に子のノードを入れ子で持つ。id は子のノードも含めて一意にする。position は親の中での位置で、ドラッグで動かすと書き換わる。link を付けると、ノードの左下にリンクを出す。見た目の項目 (label・padding・border・background・description) は d3.deployment の入力の形のまま書ける。";
5
+ readonly 'x-id-start': 1;
6
+ readonly type: "array";
7
+ readonly items: {
8
+ readonly $ref: "#/$defs/node";
9
+ };
10
+ readonly $defs: {
11
+ readonly node: {
12
+ readonly type: "object";
13
+ readonly properties: {
14
+ readonly id: {
15
+ readonly type: readonly ["integer", "string"];
16
+ };
17
+ readonly type: {
18
+ readonly enum: readonly import("./types.js").DeploymentNodeType[];
19
+ };
20
+ readonly label: {
21
+ readonly type: "object";
22
+ readonly properties: {
23
+ readonly text: {
24
+ readonly type: readonly ["string", "null"];
25
+ };
26
+ };
27
+ };
28
+ readonly description: {
29
+ readonly type: readonly ["string", "null"];
30
+ };
31
+ readonly size: {
32
+ readonly type: "object";
33
+ readonly properties: {
34
+ readonly w: {
35
+ readonly type: "number";
36
+ readonly minimum: 0;
37
+ };
38
+ readonly h: {
39
+ readonly type: "number";
40
+ readonly minimum: 0;
41
+ };
42
+ };
43
+ readonly required: readonly ["w", "h"];
44
+ };
45
+ readonly position: {
46
+ readonly type: "object";
47
+ readonly properties: {
48
+ readonly x: {
49
+ readonly type: "number";
50
+ };
51
+ readonly y: {
52
+ readonly type: "number";
53
+ };
54
+ };
55
+ readonly required: readonly ["x", "y"];
56
+ };
57
+ readonly link: {
58
+ readonly type: readonly ["object", "null"];
59
+ readonly properties: {
60
+ readonly url: {
61
+ readonly type: readonly ["string", "null"];
62
+ };
63
+ };
64
+ };
65
+ readonly children: {
66
+ readonly type: "array";
67
+ readonly items: {
68
+ readonly $ref: "#/$defs/node";
69
+ };
70
+ };
71
+ };
72
+ readonly required: readonly ["id"];
73
+ readonly additionalProperties: true;
74
+ };
75
+ };
76
+ };
77
+ export declare const EDGES_SCHEMA: {
78
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
79
+ readonly title: "システム構成図の線";
80
+ readonly description: "ノードの間の線。from.id / to.id はノード (子を含む) の id。position は接続位置の角度 (0=真下・時計回り。省略すると 90=左)。stroke.marker は両端の矢印の有無 (既定: 終点だけ)。";
81
+ readonly 'x-id-start': 100;
82
+ readonly type: "array";
83
+ readonly items: {
84
+ readonly type: "object";
85
+ readonly properties: {
86
+ readonly id: {
87
+ readonly type: readonly ["integer", "string"];
88
+ };
89
+ readonly from: {
90
+ readonly $ref: "#/$defs/end";
91
+ };
92
+ readonly to: {
93
+ readonly $ref: "#/$defs/end";
94
+ };
95
+ readonly stroke: {
96
+ readonly type: "object";
97
+ readonly properties: {
98
+ readonly color: {
99
+ readonly type: "string";
100
+ };
101
+ readonly width: {
102
+ readonly type: "number";
103
+ };
104
+ readonly marker: {
105
+ readonly type: "object";
106
+ readonly properties: {
107
+ readonly start: {
108
+ readonly type: "boolean";
109
+ };
110
+ readonly end: {
111
+ readonly type: "boolean";
112
+ };
113
+ };
114
+ };
115
+ };
116
+ };
117
+ };
118
+ readonly required: readonly ["id", "from", "to"];
119
+ readonly additionalProperties: true;
120
+ };
121
+ readonly $defs: {
122
+ readonly end: {
123
+ readonly type: "object";
124
+ readonly properties: {
125
+ readonly id: {
126
+ readonly type: readonly ["integer", "string"];
127
+ };
128
+ readonly position: {
129
+ readonly type: "number";
130
+ };
131
+ };
132
+ readonly required: readonly ["id"];
133
+ };
134
+ };
135
+ };
136
+ /** コレクション名 → スキーマ */
137
+ export declare const SCHEMAS: {
138
+ readonly nodes: {
139
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
140
+ readonly title: "システム構成図のノード";
141
+ readonly description: "d3.deployment に渡すノード。children に子のノードを入れ子で持つ。id は子のノードも含めて一意にする。position は親の中での位置で、ドラッグで動かすと書き換わる。link を付けると、ノードの左下にリンクを出す。見た目の項目 (label・padding・border・background・description) は d3.deployment の入力の形のまま書ける。";
142
+ readonly 'x-id-start': 1;
143
+ readonly type: "array";
144
+ readonly items: {
145
+ readonly $ref: "#/$defs/node";
146
+ };
147
+ readonly $defs: {
148
+ readonly node: {
149
+ readonly type: "object";
150
+ readonly properties: {
151
+ readonly id: {
152
+ readonly type: readonly ["integer", "string"];
153
+ };
154
+ readonly type: {
155
+ readonly enum: readonly import("./types.js").DeploymentNodeType[];
156
+ };
157
+ readonly label: {
158
+ readonly type: "object";
159
+ readonly properties: {
160
+ readonly text: {
161
+ readonly type: readonly ["string", "null"];
162
+ };
163
+ };
164
+ };
165
+ readonly description: {
166
+ readonly type: readonly ["string", "null"];
167
+ };
168
+ readonly size: {
169
+ readonly type: "object";
170
+ readonly properties: {
171
+ readonly w: {
172
+ readonly type: "number";
173
+ readonly minimum: 0;
174
+ };
175
+ readonly h: {
176
+ readonly type: "number";
177
+ readonly minimum: 0;
178
+ };
179
+ };
180
+ readonly required: readonly ["w", "h"];
181
+ };
182
+ readonly position: {
183
+ readonly type: "object";
184
+ readonly properties: {
185
+ readonly x: {
186
+ readonly type: "number";
187
+ };
188
+ readonly y: {
189
+ readonly type: "number";
190
+ };
191
+ };
192
+ readonly required: readonly ["x", "y"];
193
+ };
194
+ readonly link: {
195
+ readonly type: readonly ["object", "null"];
196
+ readonly properties: {
197
+ readonly url: {
198
+ readonly type: readonly ["string", "null"];
199
+ };
200
+ };
201
+ };
202
+ readonly children: {
203
+ readonly type: "array";
204
+ readonly items: {
205
+ readonly $ref: "#/$defs/node";
206
+ };
207
+ };
208
+ };
209
+ readonly required: readonly ["id"];
210
+ readonly additionalProperties: true;
211
+ };
212
+ };
213
+ };
214
+ readonly edges: {
215
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
216
+ readonly title: "システム構成図の線";
217
+ readonly description: "ノードの間の線。from.id / to.id はノード (子を含む) の id。position は接続位置の角度 (0=真下・時計回り。省略すると 90=左)。stroke.marker は両端の矢印の有無 (既定: 終点だけ)。";
218
+ readonly 'x-id-start': 100;
219
+ readonly type: "array";
220
+ readonly items: {
221
+ readonly type: "object";
222
+ readonly properties: {
223
+ readonly id: {
224
+ readonly type: readonly ["integer", "string"];
225
+ };
226
+ readonly from: {
227
+ readonly $ref: "#/$defs/end";
228
+ };
229
+ readonly to: {
230
+ readonly $ref: "#/$defs/end";
231
+ };
232
+ readonly stroke: {
233
+ readonly type: "object";
234
+ readonly properties: {
235
+ readonly color: {
236
+ readonly type: "string";
237
+ };
238
+ readonly width: {
239
+ readonly type: "number";
240
+ };
241
+ readonly marker: {
242
+ readonly type: "object";
243
+ readonly properties: {
244
+ readonly start: {
245
+ readonly type: "boolean";
246
+ };
247
+ readonly end: {
248
+ readonly type: "boolean";
249
+ };
250
+ };
251
+ };
252
+ };
253
+ };
254
+ };
255
+ readonly required: readonly ["id", "from", "to"];
256
+ readonly additionalProperties: true;
257
+ };
258
+ readonly $defs: {
259
+ readonly end: {
260
+ readonly type: "object";
261
+ readonly properties: {
262
+ readonly id: {
263
+ readonly type: readonly ["integer", "string"];
264
+ };
265
+ readonly position: {
266
+ readonly type: "number";
267
+ };
268
+ };
269
+ readonly required: readonly ["id"];
270
+ };
271
+ };
272
+ };
273
+ };
274
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCf,CAAC;AAEX,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCf,CAAC;AAEX,qBAAqB;AACrB,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGV,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,94 @@
1
+ // d3.deployment の入力データの JSON Schema(2020-12)。
2
+ //
3
+ // iHub の server/schemas/deployment/{nodes,edges}.schema.json を移したもの。
4
+ // 検査するのは、図の構造にかかわる項目(id・type・size・position・children・link、線の from / to)だけで、
5
+ // 見た目の項目(label・border・background・padding・description)は d3.deployment の入力の形のまま書けるよう
6
+ // additionalProperties を閉じていない(iHub 版は閉じていた)。
7
+ //
8
+ // x-id-start はスキーマの検証には使わない注釈(コレクションが空のときに最初に振る id)。
9
+ // Ajv で使うときは addKeyword({ keyword: 'x-id-start', schemaType: 'number' }) を足す。
10
+ import { NODE_TYPES } from './defaults.js';
11
+ const ID = { type: ['integer', 'string'] };
12
+ const POINT = {
13
+ type: 'object',
14
+ properties: { x: { type: 'number' }, y: { type: 'number' } },
15
+ required: ['x', 'y'],
16
+ };
17
+ export const NODES_SCHEMA = {
18
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
19
+ title: 'システム構成図のノード',
20
+ description: 'd3.deployment に渡すノード。children に子のノードを入れ子で持つ。id は子のノードも含めて一意にする。position は親の中での位置で、ドラッグで動かすと書き換わる。link を付けると、ノードの左下にリンクを出す。見た目の項目 (label・padding・border・background・description) は d3.deployment の入力の形のまま書ける。',
21
+ 'x-id-start': 1,
22
+ type: 'array',
23
+ items: { $ref: '#/$defs/node' },
24
+ $defs: {
25
+ node: {
26
+ type: 'object',
27
+ properties: {
28
+ id: ID,
29
+ type: { enum: [...NODE_TYPES] },
30
+ label: {
31
+ type: 'object',
32
+ properties: { text: { type: ['string', 'null'] } },
33
+ },
34
+ description: { type: ['string', 'null'] },
35
+ size: {
36
+ type: 'object',
37
+ properties: { w: { type: 'number', minimum: 0 }, h: { type: 'number', minimum: 0 } },
38
+ required: ['w', 'h'],
39
+ },
40
+ position: POINT,
41
+ link: {
42
+ type: ['object', 'null'],
43
+ properties: { url: { type: ['string', 'null'] } },
44
+ },
45
+ children: { type: 'array', items: { $ref: '#/$defs/node' } },
46
+ },
47
+ required: ['id'],
48
+ additionalProperties: true,
49
+ },
50
+ },
51
+ };
52
+ export const EDGES_SCHEMA = {
53
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
54
+ title: 'システム構成図の線',
55
+ description: 'ノードの間の線。from.id / to.id はノード (子を含む) の id。position は接続位置の角度 (0=真下・時計回り。省略すると 90=左)。stroke.marker は両端の矢印の有無 (既定: 終点だけ)。',
56
+ 'x-id-start': 100,
57
+ type: 'array',
58
+ items: {
59
+ type: 'object',
60
+ properties: {
61
+ id: ID,
62
+ from: { $ref: '#/$defs/end' },
63
+ to: { $ref: '#/$defs/end' },
64
+ stroke: {
65
+ type: 'object',
66
+ properties: {
67
+ color: { type: 'string' },
68
+ width: { type: 'number' },
69
+ marker: {
70
+ type: 'object',
71
+ properties: { start: { type: 'boolean' }, end: { type: 'boolean' } },
72
+ },
73
+ },
74
+ },
75
+ },
76
+ required: ['id', 'from', 'to'],
77
+ additionalProperties: true,
78
+ },
79
+ $defs: {
80
+ end: {
81
+ type: 'object',
82
+ properties: {
83
+ id: ID,
84
+ position: { type: 'number' },
85
+ },
86
+ required: ['id'],
87
+ },
88
+ },
89
+ };
90
+ /** コレクション名 → スキーマ */
91
+ export const SCHEMAS = {
92
+ nodes: NODES_SCHEMA,
93
+ edges: EDGES_SCHEMA,
94
+ };