@yanqirenshi/model.tm 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.
- package/LICENSE +21 -0
- package/README.md +143 -2
- package/dist/check.d.ts +18 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +107 -0
- package/dist/cjs/check.js +111 -0
- package/dist/cjs/defaults.js +145 -0
- package/dist/cjs/fields.js +77 -0
- package/dist/cjs/index.js +55 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/patch.js +274 -0
- package/dist/cjs/schema.js +179 -0
- package/dist/cjs/types.js +8 -0
- package/dist/defaults.d.ts +67 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +133 -0
- package/dist/fields.d.ts +19 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +72 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/patch.d.ts +31 -0
- package/dist/patch.d.ts.map +1 -0
- package/dist/patch.js +264 -0
- package/dist/schema.d.ts +768 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +176 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/package.json +54 -5
- package/src/check.ts +137 -0
- package/src/defaults.ts +172 -0
- package/src/fields.ts +82 -0
- package/src/index.ts +30 -0
- package/src/patch.ts +338 -0
- package/src/schema.ts +194 -0
- package/src/types.ts +165 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// model.tm — TM(T字形ER。d3.ter)の「図ごとの知識」。描画はしない(react / d3 に依存しない)。
|
|
3
|
+
//
|
|
4
|
+
// スキーマ: SCHEMAS / *_SCHEMA(JSON Schema 2020-12。4 コレクション)
|
|
5
|
+
// 整合性ルール: check(data)(id の重複・参照先の存在・CLASS と mapping の結線の規則)
|
|
6
|
+
// 既定値の補完: withDefaults(record) / entityWithDefaults / relationshipWithDefaults / kindOf / nameOf
|
|
7
|
+
// 種別の表示: ENTITY_TYPES / TYPE_BADGES / TYPE_COLORS(d3.ter が読む)
|
|
8
|
+
// 編集できる項目: fieldsOf(record, collection?) / nameFields / ENTITY_FIELDS / RELATIONSHIP_FIELDS / MASTER_FIELDS
|
|
9
|
+
// patch への変換: movePatch(entity, move) / applyPatch(record, values, collection?) / mergePatch(record, patch)
|
|
10
|
+
//
|
|
11
|
+
// view(d3.ter)はこのパッケージの既定値(種別バッジ・配色・角度)を読む。インスペクタ(colonoscope)との間に横の依存は無い。
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.applyPatch = exports.movePatch = exports.mergePatch = exports.getByPath = exports.titleOf = exports.collectionOf = exports.ModelError = exports.fieldsOf = exports.nameFields = exports.MASTER_FIELDS = exports.RELATIONSHIP_FIELDS = exports.ENTITY_FIELDS = exports.findById = exports.check = exports.RELATIONSHIPS_SCHEMA = exports.ENTITIES_SCHEMA = exports.ATTRIBUTES_SCHEMA = exports.IDENTIFIERS_SCHEMA = exports.SCHEMAS = exports.withDefaults = exports.relationshipWithDefaults = exports.entityWithDefaults = exports.endWithDefaults = exports.nameOf = exports.kindOf = exports.isEntity = exports.isRelationship = exports.isEntityType = exports.RELATIONSHIP_DEFAULTS = exports.ENTITY_DEFAULTS = exports.OPTIONALITIES = exports.CARDINALITIES = exports.SUBSET_KINDS = exports.MAPPING_TYPES = exports.TYPE_COLORS = exports.TYPE_BADGES = exports.ENTITY_TYPES = void 0;
|
|
14
|
+
var defaults_js_1 = require("./defaults.js");
|
|
15
|
+
Object.defineProperty(exports, "ENTITY_TYPES", { enumerable: true, get: function () { return defaults_js_1.ENTITY_TYPES; } });
|
|
16
|
+
Object.defineProperty(exports, "TYPE_BADGES", { enumerable: true, get: function () { return defaults_js_1.TYPE_BADGES; } });
|
|
17
|
+
Object.defineProperty(exports, "TYPE_COLORS", { enumerable: true, get: function () { return defaults_js_1.TYPE_COLORS; } });
|
|
18
|
+
Object.defineProperty(exports, "MAPPING_TYPES", { enumerable: true, get: function () { return defaults_js_1.MAPPING_TYPES; } });
|
|
19
|
+
Object.defineProperty(exports, "SUBSET_KINDS", { enumerable: true, get: function () { return defaults_js_1.SUBSET_KINDS; } });
|
|
20
|
+
Object.defineProperty(exports, "CARDINALITIES", { enumerable: true, get: function () { return defaults_js_1.CARDINALITIES; } });
|
|
21
|
+
Object.defineProperty(exports, "OPTIONALITIES", { enumerable: true, get: function () { return defaults_js_1.OPTIONALITIES; } });
|
|
22
|
+
Object.defineProperty(exports, "ENTITY_DEFAULTS", { enumerable: true, get: function () { return defaults_js_1.ENTITY_DEFAULTS; } });
|
|
23
|
+
Object.defineProperty(exports, "RELATIONSHIP_DEFAULTS", { enumerable: true, get: function () { return defaults_js_1.RELATIONSHIP_DEFAULTS; } });
|
|
24
|
+
Object.defineProperty(exports, "isEntityType", { enumerable: true, get: function () { return defaults_js_1.isEntityType; } });
|
|
25
|
+
Object.defineProperty(exports, "isRelationship", { enumerable: true, get: function () { return defaults_js_1.isRelationship; } });
|
|
26
|
+
Object.defineProperty(exports, "isEntity", { enumerable: true, get: function () { return defaults_js_1.isEntity; } });
|
|
27
|
+
Object.defineProperty(exports, "kindOf", { enumerable: true, get: function () { return defaults_js_1.kindOf; } });
|
|
28
|
+
Object.defineProperty(exports, "nameOf", { enumerable: true, get: function () { return defaults_js_1.nameOf; } });
|
|
29
|
+
Object.defineProperty(exports, "endWithDefaults", { enumerable: true, get: function () { return defaults_js_1.endWithDefaults; } });
|
|
30
|
+
Object.defineProperty(exports, "entityWithDefaults", { enumerable: true, get: function () { return defaults_js_1.entityWithDefaults; } });
|
|
31
|
+
Object.defineProperty(exports, "relationshipWithDefaults", { enumerable: true, get: function () { return defaults_js_1.relationshipWithDefaults; } });
|
|
32
|
+
Object.defineProperty(exports, "withDefaults", { enumerable: true, get: function () { return defaults_js_1.withDefaults; } });
|
|
33
|
+
var schema_js_1 = require("./schema.js");
|
|
34
|
+
Object.defineProperty(exports, "SCHEMAS", { enumerable: true, get: function () { return schema_js_1.SCHEMAS; } });
|
|
35
|
+
Object.defineProperty(exports, "IDENTIFIERS_SCHEMA", { enumerable: true, get: function () { return schema_js_1.IDENTIFIERS_SCHEMA; } });
|
|
36
|
+
Object.defineProperty(exports, "ATTRIBUTES_SCHEMA", { enumerable: true, get: function () { return schema_js_1.ATTRIBUTES_SCHEMA; } });
|
|
37
|
+
Object.defineProperty(exports, "ENTITIES_SCHEMA", { enumerable: true, get: function () { return schema_js_1.ENTITIES_SCHEMA; } });
|
|
38
|
+
Object.defineProperty(exports, "RELATIONSHIPS_SCHEMA", { enumerable: true, get: function () { return schema_js_1.RELATIONSHIPS_SCHEMA; } });
|
|
39
|
+
var check_js_1 = require("./check.js");
|
|
40
|
+
Object.defineProperty(exports, "check", { enumerable: true, get: function () { return check_js_1.check; } });
|
|
41
|
+
Object.defineProperty(exports, "findById", { enumerable: true, get: function () { return check_js_1.findById; } });
|
|
42
|
+
var fields_js_1 = require("./fields.js");
|
|
43
|
+
Object.defineProperty(exports, "ENTITY_FIELDS", { enumerable: true, get: function () { return fields_js_1.ENTITY_FIELDS; } });
|
|
44
|
+
Object.defineProperty(exports, "RELATIONSHIP_FIELDS", { enumerable: true, get: function () { return fields_js_1.RELATIONSHIP_FIELDS; } });
|
|
45
|
+
Object.defineProperty(exports, "MASTER_FIELDS", { enumerable: true, get: function () { return fields_js_1.MASTER_FIELDS; } });
|
|
46
|
+
Object.defineProperty(exports, "nameFields", { enumerable: true, get: function () { return fields_js_1.nameFields; } });
|
|
47
|
+
Object.defineProperty(exports, "fieldsOf", { enumerable: true, get: function () { return fields_js_1.fieldsOf; } });
|
|
48
|
+
var patch_js_1 = require("./patch.js");
|
|
49
|
+
Object.defineProperty(exports, "ModelError", { enumerable: true, get: function () { return patch_js_1.ModelError; } });
|
|
50
|
+
Object.defineProperty(exports, "collectionOf", { enumerable: true, get: function () { return patch_js_1.collectionOf; } });
|
|
51
|
+
Object.defineProperty(exports, "titleOf", { enumerable: true, get: function () { return patch_js_1.titleOf; } });
|
|
52
|
+
Object.defineProperty(exports, "getByPath", { enumerable: true, get: function () { return patch_js_1.getByPath; } });
|
|
53
|
+
Object.defineProperty(exports, "mergePatch", { enumerable: true, get: function () { return patch_js_1.mergePatch; } });
|
|
54
|
+
Object.defineProperty(exports, "movePatch", { enumerable: true, get: function () { return patch_js_1.movePatch; } });
|
|
55
|
+
Object.defineProperty(exports, "applyPatch", { enumerable: true, get: function () { return patch_js_1.applyPatch; } });
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// UI の操作(ドラッグ・インスペクタの適用)を、保存する patch(JSON Merge Patch)に変換する。
|
|
3
|
+
//
|
|
4
|
+
// 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
|
|
5
|
+
// 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
|
|
6
|
+
// 使い方の誤り(マスタなのに collection を渡していない)は ModelError ではなく Error。
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.ModelError = void 0;
|
|
9
|
+
exports.collectionOf = collectionOf;
|
|
10
|
+
exports.titleOf = titleOf;
|
|
11
|
+
exports.getByPath = getByPath;
|
|
12
|
+
exports.mergePatch = mergePatch;
|
|
13
|
+
exports.movePatch = movePatch;
|
|
14
|
+
exports.applyPatch = applyPatch;
|
|
15
|
+
const defaults_js_1 = require("./defaults.js");
|
|
16
|
+
const fields_js_1 = require("./fields.js");
|
|
17
|
+
/** 入力の検証エラー。message は利用者向け */
|
|
18
|
+
class ModelError extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'ModelError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ModelError = ModelError;
|
|
25
|
+
/**
|
|
26
|
+
* レコードを置くコレクション。関係・エンティティは形から決まる。
|
|
27
|
+
* マスタは identifiers / attributes を形から判別できないので collection が要る。
|
|
28
|
+
*/
|
|
29
|
+
function collectionOf(record, collection) {
|
|
30
|
+
if ((0, defaults_js_1.isRelationship)(record))
|
|
31
|
+
return 'relationships';
|
|
32
|
+
if ((0, defaults_js_1.isEntity)(record))
|
|
33
|
+
return 'entities';
|
|
34
|
+
if (!collection)
|
|
35
|
+
throw new Error('model.tm: マスタ (識別子・属性) のレコードには collection を渡してください。');
|
|
36
|
+
return collection;
|
|
37
|
+
}
|
|
38
|
+
/** インスペクタの見出し。名前、無ければ id(関係はラベル、無ければ「種類 id」) */
|
|
39
|
+
function titleOf(record) {
|
|
40
|
+
if ((0, defaults_js_1.isRelationship)(record)) {
|
|
41
|
+
if (typeof record.label === 'string' && record.label !== '')
|
|
42
|
+
return record.label;
|
|
43
|
+
return `${record.type || ''} ${String(record.id)}`.trim();
|
|
44
|
+
}
|
|
45
|
+
const name = (0, defaults_js_1.nameOf)(record);
|
|
46
|
+
return name !== '' ? name : String(record.id);
|
|
47
|
+
}
|
|
48
|
+
/** ドット区切りの path で値を取り出す */
|
|
49
|
+
function getByPath(obj, path) {
|
|
50
|
+
return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
|
|
54
|
+
* null の項目は除く。保存したあと、表示を先に更新するときに使う。
|
|
55
|
+
*/
|
|
56
|
+
function mergePatch(record, patch) {
|
|
57
|
+
const out = { ...record };
|
|
58
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
59
|
+
if (value === null) {
|
|
60
|
+
delete out[key];
|
|
61
|
+
}
|
|
62
|
+
else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
63
|
+
const base = out[key];
|
|
64
|
+
out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
out[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* ドラッグで動かしたエンティティの位置を保存する patch。
|
|
74
|
+
* 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。z は触らない。
|
|
75
|
+
*/
|
|
76
|
+
function movePatch(entity, move) {
|
|
77
|
+
const base = entity.position || { x: 0, y: 0 };
|
|
78
|
+
return {
|
|
79
|
+
collection: 'entities',
|
|
80
|
+
id: entity.id,
|
|
81
|
+
patch: {
|
|
82
|
+
position: {
|
|
83
|
+
x: Math.round(base.x + move.dx),
|
|
84
|
+
y: Math.round(base.y + move.dy),
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ---- 入力の読み取り ------------------------------------------------------------
|
|
90
|
+
// 入力した文字列を数にする。数でないときは null。
|
|
91
|
+
function toNumber(value) {
|
|
92
|
+
const n = Number(value);
|
|
93
|
+
return value !== '' && Number.isFinite(n) ? n : null;
|
|
94
|
+
}
|
|
95
|
+
// 0 以上の整数(幅・高さ。0 は「内容に合わせる」)
|
|
96
|
+
function toLength(value, label) {
|
|
97
|
+
const n = toNumber(value);
|
|
98
|
+
if (n === null || n < 0)
|
|
99
|
+
throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で内容に合わせる)`);
|
|
100
|
+
return Math.round(n);
|
|
101
|
+
}
|
|
102
|
+
// 角度(0 以上 360 未満の整数に正規化)
|
|
103
|
+
function toDegree(value, label) {
|
|
104
|
+
const n = toNumber(value);
|
|
105
|
+
if (n === null)
|
|
106
|
+
throw new ModelError(`${label}は、角度 (数) で入力してください。`);
|
|
107
|
+
return ((Math.round(n) % 360) + 360) % 360;
|
|
108
|
+
}
|
|
109
|
+
// 候補の中の数、または空(null = 項目を除く)
|
|
110
|
+
function toOptionNumberOrNull(value, options, label) {
|
|
111
|
+
if (value === '')
|
|
112
|
+
return null;
|
|
113
|
+
const n = toNumber(value);
|
|
114
|
+
if (n === null || !options.includes(n))
|
|
115
|
+
throw new ModelError(`${label}は、${options.join(' / ')} のどれか (または空) で入力してください。`);
|
|
116
|
+
return n;
|
|
117
|
+
}
|
|
118
|
+
// 候補の中の文字列
|
|
119
|
+
function toOption(value, options, label) {
|
|
120
|
+
if (!options.includes(value))
|
|
121
|
+
throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
// 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
|
|
125
|
+
function toOptionalText(value) {
|
|
126
|
+
return value === '' ? null : value;
|
|
127
|
+
}
|
|
128
|
+
// patch の入れ子に値を置く(patch.from.position = v のように)
|
|
129
|
+
function put(patch, path, value) {
|
|
130
|
+
const keys = path.split('.');
|
|
131
|
+
let cur = patch;
|
|
132
|
+
for (const key of keys.slice(0, -1)) {
|
|
133
|
+
if (!cur[key] || typeof cur[key] !== 'object')
|
|
134
|
+
cur[key] = {};
|
|
135
|
+
cur = cur[key];
|
|
136
|
+
}
|
|
137
|
+
cur[keys[keys.length - 1]] = value;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
|
|
141
|
+
* 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
|
|
142
|
+
*/
|
|
143
|
+
function changedValues(record, values, collection) {
|
|
144
|
+
const effective = (0, defaults_js_1.withDefaults)(record);
|
|
145
|
+
const out = new Map();
|
|
146
|
+
for (const field of (0, fields_js_1.fieldsOf)(record, collection)) {
|
|
147
|
+
if (field.type === 'readonly')
|
|
148
|
+
continue;
|
|
149
|
+
const value = values[field.path];
|
|
150
|
+
if (value === undefined)
|
|
151
|
+
continue;
|
|
152
|
+
const current = getByPath(effective, field.path);
|
|
153
|
+
if (value.trim() !== String(current ?? ''))
|
|
154
|
+
out.set(field.path, value.trim());
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
// 名前(name / name.logical / name.physical)。required なら空にできない
|
|
159
|
+
function namePatch(patch, path, value, required) {
|
|
160
|
+
// name と name.logical は空にできない。name.physical は空にできる(論理名だけのモデル)
|
|
161
|
+
if (value === '' && required)
|
|
162
|
+
throw new ModelError('名称を入力してください。');
|
|
163
|
+
put(patch, path, value);
|
|
164
|
+
}
|
|
165
|
+
// エンティティの入力 → patch(name / type / description / size)
|
|
166
|
+
function entityPatch(entity, values) {
|
|
167
|
+
const patch = {};
|
|
168
|
+
for (const [path, value] of changedValues(entity, values)) {
|
|
169
|
+
switch (path) {
|
|
170
|
+
case 'name':
|
|
171
|
+
case 'name.logical':
|
|
172
|
+
namePatch(patch, path, value, true);
|
|
173
|
+
break;
|
|
174
|
+
case 'name.physical':
|
|
175
|
+
namePatch(patch, path, value, false);
|
|
176
|
+
break;
|
|
177
|
+
case 'type':
|
|
178
|
+
put(patch, path, toOption(value, defaults_js_1.ENTITY_TYPES, '種別'));
|
|
179
|
+
break;
|
|
180
|
+
case 'size.w':
|
|
181
|
+
put(patch, path, toLength(value, '幅'));
|
|
182
|
+
break;
|
|
183
|
+
case 'size.h':
|
|
184
|
+
put(patch, path, toLength(value, '高さ'));
|
|
185
|
+
break;
|
|
186
|
+
case 'description':
|
|
187
|
+
put(patch, path, toOptionalText(value));
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return patch;
|
|
192
|
+
}
|
|
193
|
+
const END_LABELS = { from: '接続元', to: '接続先' };
|
|
194
|
+
// 関係の入力 → patch(type / label / from / to / subset / description)
|
|
195
|
+
function relationshipPatch(relationship, values) {
|
|
196
|
+
const patch = {};
|
|
197
|
+
for (const [path, value] of changedValues(relationship, values)) {
|
|
198
|
+
const [head, key] = path.split('.');
|
|
199
|
+
if ((head === 'from' || head === 'to') && key) {
|
|
200
|
+
const label = END_LABELS[head];
|
|
201
|
+
switch (key) {
|
|
202
|
+
case 'position':
|
|
203
|
+
put(patch, path, toDegree(value, `${label}の位置`));
|
|
204
|
+
break;
|
|
205
|
+
case 'cardinality':
|
|
206
|
+
put(patch, path, toOptionNumberOrNull(value, defaults_js_1.CARDINALITIES, `${label}のカーディナリティ`));
|
|
207
|
+
break;
|
|
208
|
+
case 'optionality':
|
|
209
|
+
put(patch, path, toOptionNumberOrNull(value, defaults_js_1.OPTIONALITIES, `${label}のオプショナリティ`));
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
switch (path) {
|
|
215
|
+
case 'subset.kind':
|
|
216
|
+
put(patch, path, toOption(value, defaults_js_1.SUBSET_KINDS, 'サブセットの区分'));
|
|
217
|
+
break;
|
|
218
|
+
case 'subset.code':
|
|
219
|
+
// 区分コードを空にするとサブセットの表記を消す(subset ごと除く)
|
|
220
|
+
if (value === '')
|
|
221
|
+
patch.subset = null;
|
|
222
|
+
else
|
|
223
|
+
put(patch, path, value);
|
|
224
|
+
break;
|
|
225
|
+
case 'type':
|
|
226
|
+
case 'label':
|
|
227
|
+
case 'description':
|
|
228
|
+
put(patch, path, toOptionalText(value));
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// subset を除くときは kind の変更も要らない
|
|
233
|
+
if (patch.subset === null)
|
|
234
|
+
return patch;
|
|
235
|
+
// kind だけを新しく書いて code が無い subset は作らない(d3.ter は code が無ければ描かない)
|
|
236
|
+
const subset = patch.subset;
|
|
237
|
+
if (subset && subset.kind !== undefined && subset.code === undefined && !relationship.subset?.code)
|
|
238
|
+
throw new ModelError('サブセットの区分コードを入力してください。');
|
|
239
|
+
return patch;
|
|
240
|
+
}
|
|
241
|
+
// マスタの入力 → patch(name / description)
|
|
242
|
+
function masterPatch(master, values, collection) {
|
|
243
|
+
const patch = {};
|
|
244
|
+
for (const [path, value] of changedValues(master, values, collection)) {
|
|
245
|
+
switch (path) {
|
|
246
|
+
case 'name':
|
|
247
|
+
case 'name.logical':
|
|
248
|
+
// 識別子・属性の名前は空にできない(エンティティの行に描く文字が無くなる)
|
|
249
|
+
namePatch(patch, path, value, true);
|
|
250
|
+
break;
|
|
251
|
+
case 'name.physical':
|
|
252
|
+
namePatch(patch, path, value, false);
|
|
253
|
+
break;
|
|
254
|
+
case 'description':
|
|
255
|
+
put(patch, path, toOptionalText(value));
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return patch;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* インスペクタの「適用」の値を、保存する patch に変換する。
|
|
263
|
+
* 図に描いている値から変えた項目だけを patch に入れる(変えていない既定の値まで書き込まないため)。
|
|
264
|
+
* 変更が無ければ patch は {}。マスタのときは collection(identifiers / attributes)が要る。
|
|
265
|
+
*/
|
|
266
|
+
function applyPatch(record, values, collection) {
|
|
267
|
+
const target = collectionOf(record, collection);
|
|
268
|
+
const patch = (0, defaults_js_1.kindOf)(record) === 'relationship'
|
|
269
|
+
? relationshipPatch(record, values)
|
|
270
|
+
: (0, defaults_js_1.kindOf)(record) === 'entity'
|
|
271
|
+
? entityPatch(record, values)
|
|
272
|
+
: masterPatch(record, values, target);
|
|
273
|
+
return { collection: target, id: record.id, patch };
|
|
274
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// TM(d3.ter)の入力データの JSON Schema(2020-12)。コレクションごとに 1 つ。
|
|
3
|
+
//
|
|
4
|
+
// iHub の server/schemas/tm/*.schema.json を移したもの。iHub 版と違い additionalProperties を閉じていない
|
|
5
|
+
// (利用側の項目を落とさないため)。検査するのは、図の構造にかかわる項目(id・type・参照・position・size・
|
|
6
|
+
// 関係の端点・subset・mapping)。name は文字列でも { logical, physical } でもよい。
|
|
7
|
+
//
|
|
8
|
+
// x-id-start はスキーマの検証には使わない注釈(コレクションが空のときに最初に振る id)。
|
|
9
|
+
// Ajv で使うときは addKeyword({ keyword: 'x-id-start', schemaType: 'number' }) を足す。
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.SCHEMAS = exports.RELATIONSHIPS_SCHEMA = exports.ENTITIES_SCHEMA = exports.ATTRIBUTES_SCHEMA = exports.IDENTIFIERS_SCHEMA = void 0;
|
|
12
|
+
const defaults_js_1 = require("./defaults.js");
|
|
13
|
+
const ID = { type: ['integer', 'string'] };
|
|
14
|
+
const NAME = {
|
|
15
|
+
description: '文字列、または論理名と物理名の組',
|
|
16
|
+
oneOf: [
|
|
17
|
+
{ type: 'string' },
|
|
18
|
+
{
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
logical: { type: 'string' },
|
|
22
|
+
physical: { type: 'string' },
|
|
23
|
+
description: { type: 'string' },
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: true,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
// マスタ(identifiers / attributes)の共通形
|
|
30
|
+
function masterSchema(title, description, idStart) {
|
|
31
|
+
return {
|
|
32
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
33
|
+
title,
|
|
34
|
+
description,
|
|
35
|
+
'x-id-start': idStart,
|
|
36
|
+
type: 'array',
|
|
37
|
+
items: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
id: ID,
|
|
41
|
+
name: NAME,
|
|
42
|
+
description: { type: ['string', 'null'] },
|
|
43
|
+
},
|
|
44
|
+
required: ['id', 'name'],
|
|
45
|
+
additionalProperties: true,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
exports.IDENTIFIERS_SCHEMA = masterSchema('TM の識別子のマスタ', '識別子 (個体指定子) のマスタ。entities[].identifiers[].identifier から id で参照される。', 10000);
|
|
50
|
+
exports.ATTRIBUTES_SCHEMA = masterSchema('TM の属性のマスタ', '属性のマスタ。entities[].attributes[].attribute から id で参照される。', 20000);
|
|
51
|
+
// エンティティが持つインスタンス(識別子・属性)の共通形。ref はマスタを指す項目名
|
|
52
|
+
function instanceSchema(ref) {
|
|
53
|
+
return {
|
|
54
|
+
type: 'array',
|
|
55
|
+
items: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
id: ID,
|
|
59
|
+
[ref]: ID,
|
|
60
|
+
name: NAME,
|
|
61
|
+
description: { type: ['string', 'null'] },
|
|
62
|
+
},
|
|
63
|
+
required: ['id', ref],
|
|
64
|
+
additionalProperties: true,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
exports.ENTITIES_SCHEMA = {
|
|
69
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
70
|
+
title: 'TM のエンティティ',
|
|
71
|
+
description: 'TM (T字形ER) のエンティティ。type は 10 種の種別 (これ以外は d3.ter が描画時に例外にする)。識別子・属性はマスタの id を参照するインスタンスとして持ち、インスタンスの id は全エンティティを通して一意 (関係の details がこれを指す)。name は文字列か論理名・物理名の組。position はドラッグで書き換わる。size の w / h が 0 のときは内容に合わせる。',
|
|
72
|
+
'x-id-start': 30000,
|
|
73
|
+
type: 'array',
|
|
74
|
+
items: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
id: ID,
|
|
78
|
+
type: { enum: [...defaults_js_1.ENTITY_TYPES] },
|
|
79
|
+
name: NAME,
|
|
80
|
+
description: { type: ['string', 'null'] },
|
|
81
|
+
position: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
x: { type: 'number' },
|
|
85
|
+
y: { type: 'number' },
|
|
86
|
+
z: { type: 'number' },
|
|
87
|
+
},
|
|
88
|
+
required: ['x', 'y'],
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
},
|
|
91
|
+
size: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
w: { type: 'number', minimum: 0 },
|
|
95
|
+
h: { type: 'number', minimum: 0 },
|
|
96
|
+
},
|
|
97
|
+
required: ['w', 'h'],
|
|
98
|
+
additionalProperties: false,
|
|
99
|
+
},
|
|
100
|
+
identifiers: instanceSchema('identifier'),
|
|
101
|
+
attributes: instanceSchema('attribute'),
|
|
102
|
+
},
|
|
103
|
+
required: ['id', 'type', 'name'],
|
|
104
|
+
additionalProperties: true,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
exports.RELATIONSHIPS_SCHEMA = {
|
|
108
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
109
|
+
title: 'TM のリレーションシップ',
|
|
110
|
+
description: 'エンティティの間の結線。from.entity / to.entity はエンティティの id (同じエンティティを指すと自己結線 = 再帰のループ)。端点の cardinality (1 / 3) と optionality (0 / 1) は無ければ記号を描かない。position は接続位置の角度 (0=真下・時計回り)。details は線を取り付けるインスタンス (省略すると先頭の識別子)。subset はサブセット結線の区分コード (same は =、different は ×)。mapping は結線の中点から垂下する対応表・対照表・再帰表 (再帰表は自己結線にだけ)。',
|
|
111
|
+
'x-id-start': 1,
|
|
112
|
+
type: 'array',
|
|
113
|
+
items: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
id: ID,
|
|
117
|
+
type: { type: ['string', 'null'] },
|
|
118
|
+
from: { $ref: '#/$defs/end' },
|
|
119
|
+
to: { $ref: '#/$defs/end' },
|
|
120
|
+
details: {
|
|
121
|
+
type: 'array',
|
|
122
|
+
items: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
from: { $ref: '#/$defs/detail_end' },
|
|
126
|
+
to: { $ref: '#/$defs/detail_end' },
|
|
127
|
+
},
|
|
128
|
+
required: ['from', 'to'],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
subset: {
|
|
132
|
+
type: ['object', 'null'],
|
|
133
|
+
properties: {
|
|
134
|
+
kind: { enum: [...defaults_js_1.SUBSET_KINDS] },
|
|
135
|
+
code: { type: 'string' },
|
|
136
|
+
},
|
|
137
|
+
required: ['kind', 'code'],
|
|
138
|
+
},
|
|
139
|
+
mapping: {
|
|
140
|
+
type: ['object', 'null'],
|
|
141
|
+
properties: { entity: ID },
|
|
142
|
+
required: ['entity'],
|
|
143
|
+
},
|
|
144
|
+
label: { type: ['string', 'null'] },
|
|
145
|
+
description: { type: ['string', 'null'] },
|
|
146
|
+
},
|
|
147
|
+
required: ['id', 'from', 'to'],
|
|
148
|
+
additionalProperties: true,
|
|
149
|
+
},
|
|
150
|
+
$defs: {
|
|
151
|
+
end: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
entity: ID,
|
|
155
|
+
cardinality: { enum: [...defaults_js_1.CARDINALITIES], description: '1 か 多 (3)。無ければ記号を描かない' },
|
|
156
|
+
optionality: { enum: [...defaults_js_1.OPTIONALITIES], description: '0 は任意 (○)、1 は必須 (横棒)。無ければ記号を描かない' },
|
|
157
|
+
position: { type: 'number', description: '接続位置の角度。0=真下・時計回り (90=左 / 180=真上 / 270=右)' },
|
|
158
|
+
id: { ...ID, description: '旧来の項目。端点の識別子のインスタンスの id (details に置き換わった)' },
|
|
159
|
+
},
|
|
160
|
+
required: ['entity'],
|
|
161
|
+
additionalProperties: true,
|
|
162
|
+
},
|
|
163
|
+
detail_end: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
id: ID,
|
|
167
|
+
description: { type: ['string', 'null'] },
|
|
168
|
+
},
|
|
169
|
+
required: ['id'],
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
/** コレクション名 → スキーマ */
|
|
174
|
+
exports.SCHEMAS = {
|
|
175
|
+
identifiers: exports.IDENTIFIERS_SCHEMA,
|
|
176
|
+
attributes: exports.ATTRIBUTES_SCHEMA,
|
|
177
|
+
entities: exports.ENTITIES_SCHEMA,
|
|
178
|
+
relationships: exports.RELATIONSHIPS_SCHEMA,
|
|
179
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// model.tm の公開型。
|
|
3
|
+
//
|
|
4
|
+
// ここで言う「レコード」は、d3.ter の data() に渡す入力そのものの 1 要素。
|
|
5
|
+
// { identifiers, attributes, entities, relationships }
|
|
6
|
+
// 形は model.er(ER 図)と同じ階層形式で、TM 固有の項目(エンティティの type、関係の subset / mapping / label)が足されている。
|
|
7
|
+
// d3.ter が正規化したあとの datum(_id / _core を持つ Atman)ではない。
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Position, Size } from '@yanqirenshi/types';
|
|
2
|
+
import type { TmCardinality, TmEntityRecord, TmEntityType, TmKind, TmMasterRecord, TmOptionality, TmRecord, TmRelationshipEnd, TmRelationshipRecord } from './types.js';
|
|
3
|
+
/** エンティティの種別(10 種) */
|
|
4
|
+
export declare const ENTITY_TYPES: readonly TmEntityType[];
|
|
5
|
+
/** 種別バッジ(箱のヘッダ右に描く文字。CLASS は意図的に無し = '') */
|
|
6
|
+
export declare const TYPE_BADGES: Record<TmEntityType, string>;
|
|
7
|
+
/** 種別ごとの箱の背景色 */
|
|
8
|
+
export declare const TYPE_COLORS: Record<TmEntityType, string>;
|
|
9
|
+
/** 結線の中点から垂下(mapping)できる種別 */
|
|
10
|
+
export declare const MAPPING_TYPES: readonly TmEntityType[];
|
|
11
|
+
/** サブセット結線の区分コードの種類 */
|
|
12
|
+
export declare const SUBSET_KINDS: readonly ["same", "different"];
|
|
13
|
+
/** カーディナリティ(1 か 多) */
|
|
14
|
+
export declare const CARDINALITIES: readonly TmCardinality[];
|
|
15
|
+
/** オプショナリティ(0 は任意、1 は必須) */
|
|
16
|
+
export declare const OPTIONALITIES: readonly TmOptionality[];
|
|
17
|
+
/** エンティティの既定。size の 0 は「内容に合わせる」 */
|
|
18
|
+
export declare const ENTITY_DEFAULTS: {
|
|
19
|
+
readonly position: Position;
|
|
20
|
+
readonly size: Size;
|
|
21
|
+
};
|
|
22
|
+
/** 関係の既定。端点の角度は 0(真下)。cardinality / optionality は既定なし(無ければ記号を描かない) */
|
|
23
|
+
export declare const RELATIONSHIP_DEFAULTS: {
|
|
24
|
+
readonly end: {
|
|
25
|
+
readonly position: 0;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** 種別として正しいか */
|
|
29
|
+
export declare function isEntityType(value: unknown): value is TmEntityType;
|
|
30
|
+
/** 関係か(from と to がエンティティを指す) */
|
|
31
|
+
export declare function isRelationship(record: TmRecord | null | undefined): record is TmRelationshipRecord;
|
|
32
|
+
/**
|
|
33
|
+
* エンティティか。関係でなく、type・インスタンスの配列・位置・大きさのどれかを持つ。
|
|
34
|
+
* マスタ(id と name だけ)と区別するための判定なので、id と name しか無いエンティティは見分けられない
|
|
35
|
+
* (entities[] の要素と分かっているときは entityWithDefaults を使う)。
|
|
36
|
+
*/
|
|
37
|
+
export declare function isEntity(record: TmRecord | null | undefined): record is TmEntityRecord;
|
|
38
|
+
/** レコードの種類 */
|
|
39
|
+
export declare function kindOf(record: TmRecord): TmKind;
|
|
40
|
+
/** 表示する名前。文字列ならそのまま、Name なら logical(無ければ physical)。無ければ '' */
|
|
41
|
+
export declare function nameOf(record: TmMasterRecord | TmEntityRecord): string;
|
|
42
|
+
/** 既定を重ねた端点(position が必ずある) */
|
|
43
|
+
export type TmFilledEnd = TmRelationshipEnd & {
|
|
44
|
+
position: number;
|
|
45
|
+
};
|
|
46
|
+
/** 端点に既定を重ねる(角度が無ければ 0。cardinality / optionality は不正値なら除く) */
|
|
47
|
+
export declare function endWithDefaults(end: TmRelationshipEnd): TmFilledEnd;
|
|
48
|
+
/** 関係に既定を重ねる: from / to の position */
|
|
49
|
+
export declare function relationshipWithDefaults<T extends TmRelationshipRecord>(record: T): T & {
|
|
50
|
+
from: TmFilledEnd;
|
|
51
|
+
to: TmFilledEnd;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* エンティティに既定を重ねる: position(無ければ 0, 0)、size(無ければ 0, 0 = 内容に合わせる)。
|
|
55
|
+
* type は補わない(無ければ無いまま。d3.ter は描けないので check で知らせる)。
|
|
56
|
+
*/
|
|
57
|
+
export declare function entityWithDefaults<T extends TmEntityRecord>(record: T): T & {
|
|
58
|
+
position: Position;
|
|
59
|
+
size: Size;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* レコードに、view が既定で使う値を重ねて返す(入力は書き換えない)。インスペクタの初期値に使う。
|
|
63
|
+
* 形から種類を決める(kindOf)。マスタはそのまま(複製だけ)。
|
|
64
|
+
* name は文字列か Name かを保つ(どちらで書いたかで編集できる項目が変わる — fieldsOf)。
|
|
65
|
+
*/
|
|
66
|
+
export declare function withDefaults<T extends TmRecord>(record: T): T;
|
|
67
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EACV,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,QAAQ,EAC5F,iBAAiB,EAAE,oBAAoB,EACxC,MAAM,YAAY,CAAC;AAEpB,sBAAsB;AACtB,eAAO,MAAM,YAAY,EAAE,SAAS,YAAY,EAG/C,CAAC;AAEF,4CAA4C;AAC5C,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAWpD,CAAC;AAEF,iBAAiB;AACjB,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAWpD,CAAC;AAEF,8BAA8B;AAC9B,eAAO,MAAM,aAAa,EAAE,SAAS,YAAY,EAAmD,CAAC;AAErG,uBAAuB;AACvB,eAAO,MAAM,YAAY,gCAAiC,CAAC;AAE3D,sBAAsB;AACtB,eAAO,MAAM,aAAa,EAAE,SAAS,aAAa,EAAW,CAAC;AAE9D,4BAA4B;AAC5B,eAAO,MAAM,aAAa,EAAE,SAAS,aAAa,EAAW,CAAC;AAE9D,oCAAoC;AACpC,eAAO,MAAM,eAAe;uBACE,QAAQ;mBACZ,IAAI;CACpB,CAAC;AAEX,sEAAsE;AACtE,eAAO,MAAM,qBAAqB;;;;CAExB,CAAC;AAEX,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAElE;AAED,gCAAgC;AAChC,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,IAAI,oBAAoB,CAKlG;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,IAAI,cAAc,CAQtF;AAED,cAAc;AACd,wBAAgB,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAK/C;AAED,8DAA8D;AAC9D,wBAAgB,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,GAAG,MAAM,CAUtE;AAED,+BAA+B;AAC/B,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,GAAG,EAAE,iBAAiB,GAAG,WAAW,CAanE;AAED,sCAAsC;AACtC,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,oBAAoB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,WAAW,CAAA;CAAE,CAM9H;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,cAAc,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,CAM9G;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQ7D"}
|