@yanqirenshi/model.tm 0.1.0 → 0.1.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.
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAkB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAqB1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAuF1D;AAED,wCAAwC;AACxC,wBAAgB,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,IAAI,GAAG,CAAC,GAAG,IAAI,CAE/F"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAkB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAO1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAuF1D;AAED,wCAAwC;AACxC,wBAAgB,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,IAAI,GAAG,CAAC,GAAG,IAAI,CAE/F"}
package/dist/check.js CHANGED
@@ -6,17 +6,8 @@
6
6
  // Entity.entityTypeContents)も検査する。描く前に分かるほうがよい知識で、d3.ter 側の例外は残る。
7
7
  //
8
8
  // メッセージの先頭はコレクション名で、prefix を渡すと 'tm/entities: ...' のように付けられる。
9
+ import { checkUnique, findById as findInList } from '@yanqirenshi/model.core';
9
10
  import { MAPPING_TYPES, isEntityType } from './defaults.js';
10
- // list の中で id が重複していればエラーにする。id の集合を返す。
11
- function checkUnique(errors, label, list) {
12
- const values = new Set();
13
- for (const d of list) {
14
- if (values.has(d.id))
15
- errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
16
- values.add(d.id);
17
- }
18
- return values;
19
- }
20
11
  // エンティティが持つ識別子のマスタの集合(CLASS の親判定に使う)
21
12
  function identifierMasters(entity) {
22
13
  return new Set((entity.identifiers || []).map(d => d.identifier));
@@ -103,5 +94,5 @@ export function check(data, prefix = '') {
103
94
  }
104
95
  /** コレクションの中からレコードを id で探す(無ければ null) */
105
96
  export function findById(list, id) {
106
- return (list || []).find(d => d.id === id) || null;
97
+ return findInList(list, id);
107
98
  }
package/dist/cjs/check.js CHANGED
@@ -10,17 +10,8 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.check = check;
12
12
  exports.findById = findById;
13
+ const model_core_1 = require("@yanqirenshi/model.core");
13
14
  const defaults_js_1 = require("./defaults.js");
14
- // list の中で id が重複していればエラーにする。id の集合を返す。
15
- function checkUnique(errors, label, list) {
16
- const values = new Set();
17
- for (const d of list) {
18
- if (values.has(d.id))
19
- errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
20
- values.add(d.id);
21
- }
22
- return values;
23
- }
24
15
  // エンティティが持つ識別子のマスタの集合(CLASS の親判定に使う)
25
16
  function identifierMasters(entity) {
26
17
  return new Set((entity.identifiers || []).map(d => d.identifier));
@@ -44,12 +35,12 @@ function check(data, prefix = '') {
44
35
  const attributes = data.attributes || [];
45
36
  const entities = data.entities || [];
46
37
  const relationships = data.relationships || [];
47
- const identifier_ids = checkUnique(errors, `${prefix}identifiers`, identifiers);
48
- const attribute_ids = checkUnique(errors, `${prefix}attributes`, attributes);
49
- checkUnique(errors, `${prefix}entities`, entities);
50
- checkUnique(errors, `${prefix}relationships`, relationships);
38
+ const identifier_ids = (0, model_core_1.checkUnique)(errors, `${prefix}identifiers`, identifiers);
39
+ const attribute_ids = (0, model_core_1.checkUnique)(errors, `${prefix}attributes`, attributes);
40
+ (0, model_core_1.checkUnique)(errors, `${prefix}entities`, entities);
41
+ (0, model_core_1.checkUnique)(errors, `${prefix}relationships`, relationships);
51
42
  // インスタンスの id は、エンティティをまたいで一意にする(d3.ter はインスタンスを id だけで探すため)
52
- checkUnique(errors, `${prefix}entities の identifiers・attributes`, entities.flatMap(e => [...(e.identifiers || []), ...(e.attributes || [])]));
43
+ (0, model_core_1.checkUnique)(errors, `${prefix}entities の identifiers・attributes`, entities.flatMap(e => [...(e.identifiers || []), ...(e.attributes || [])]));
53
44
  for (const e of entities) {
54
45
  if (!(0, defaults_js_1.isEntityType)(e.type))
55
46
  errors.push(`${prefix}entities: type が種別のどれでもありません。(id=${String(e.id)}, type=${String(e.type)})`);
@@ -107,5 +98,5 @@ function check(data, prefix = '') {
107
98
  }
108
99
  /** コレクションの中からレコードを id で探す(無ければ null) */
109
100
  function findById(list, id) {
110
- return (list || []).find(d => d.id === id) || null;
101
+ return (0, model_core_1.findById)(list, id);
111
102
  }
package/dist/cjs/index.js CHANGED
@@ -9,6 +9,7 @@
9
9
  // patch への変換: movePatch(entity, move) / applyPatch(record, values, collection?) / mergePatch(record, patch)
10
10
  //
11
11
  // view(d3.ter)はこのパッケージの既定値(種別バッジ・配色・角度)を読む。インスペクタ(colonoscope)との間に横の依存は無い。
12
+ // ModelError / mergePatch / getByPath / movePatch と入力の読み取り部品は model.core(#151)のもの。
12
13
  Object.defineProperty(exports, "__esModule", { value: true });
13
14
  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
15
  var defaults_js_1 = require("./defaults.js");
package/dist/cjs/patch.js CHANGED
@@ -4,24 +4,21 @@
4
4
  // 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
5
5
  // 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
6
6
  // 使い方の誤り(マスタなのに collection を渡していない)は ModelError ではなく Error。
7
+ // 読み取り部品(toLength など)と patch の道具は model.core にある(#151)。
7
8
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.ModelError = void 0;
9
+ exports.mergePatch = exports.getByPath = exports.ModelError = void 0;
9
10
  exports.collectionOf = collectionOf;
10
11
  exports.titleOf = titleOf;
11
- exports.getByPath = getByPath;
12
- exports.mergePatch = mergePatch;
13
12
  exports.movePatch = movePatch;
14
13
  exports.applyPatch = applyPatch;
14
+ const model_core_1 = require("@yanqirenshi/model.core");
15
15
  const defaults_js_1 = require("./defaults.js");
16
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;
17
+ // model.core のものをそのまま公開する(利用側が model.tm だけを読めばよいように)
18
+ var model_core_2 = require("@yanqirenshi/model.core");
19
+ Object.defineProperty(exports, "ModelError", { enumerable: true, get: function () { return model_core_2.ModelError; } });
20
+ Object.defineProperty(exports, "getByPath", { enumerable: true, get: function () { return model_core_2.getByPath; } });
21
+ Object.defineProperty(exports, "mergePatch", { enumerable: true, get: function () { return model_core_2.mergePatch; } });
25
22
  /**
26
23
  * レコードを置くコレクション。関係・エンティティは形から決まる。
27
24
  * マスタは identifiers / attributes を形から判別できないので collection が要る。
@@ -45,122 +42,25 @@ function titleOf(record) {
45
42
  const name = (0, defaults_js_1.nameOf)(record);
46
43
  return name !== '' ? name : String(record.id);
47
44
  }
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
45
  /**
73
46
  * ドラッグで動かしたエンティティの位置を保存する patch。
74
47
  * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。z は触らない。
75
48
  */
76
49
  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);
50
+ return (0, model_core_1.movePatch)(entity, move, 'entities');
101
51
  }
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
- */
52
+ // インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
143
53
  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;
54
+ return (0, model_core_1.changedValues)((0, defaults_js_1.withDefaults)(record), values, (0, fields_js_1.fieldsOf)(record, collection));
157
55
  }
56
+ // 幅・高さは 0 以上(0 は「内容に合わせる」)
57
+ const LENGTH = { hint: '(0 で内容に合わせる)' };
158
58
  // 名前(name / name.logical / name.physical)。required なら空にできない
159
59
  function namePatch(patch, path, value, required) {
160
60
  // name と name.logical は空にできない。name.physical は空にできる(論理名だけのモデル)
161
61
  if (value === '' && required)
162
- throw new ModelError('名称を入力してください。');
163
- put(patch, path, value);
62
+ throw new model_core_1.ModelError('名称を入力してください。');
63
+ (0, model_core_1.put)(patch, path, value);
164
64
  }
165
65
  // エンティティの入力 → patch(name / type / description / size)
166
66
  function entityPatch(entity, values) {
@@ -175,16 +75,16 @@ function entityPatch(entity, values) {
175
75
  namePatch(patch, path, value, false);
176
76
  break;
177
77
  case 'type':
178
- put(patch, path, toOption(value, defaults_js_1.ENTITY_TYPES, '種別'));
78
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOption)(value, defaults_js_1.ENTITY_TYPES, '種別'));
179
79
  break;
180
80
  case 'size.w':
181
- put(patch, path, toLength(value, '幅'));
81
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '幅', LENGTH));
182
82
  break;
183
83
  case 'size.h':
184
- put(patch, path, toLength(value, '高さ'));
84
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toLength)(value, '高さ', LENGTH));
185
85
  break;
186
86
  case 'description':
187
- put(patch, path, toOptionalText(value));
87
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOptionalText)(value));
188
88
  break;
189
89
  }
190
90
  }
@@ -200,32 +100,32 @@ function relationshipPatch(relationship, values) {
200
100
  const label = END_LABELS[head];
201
101
  switch (key) {
202
102
  case 'position':
203
- put(patch, path, toDegree(value, `${label}の位置`));
103
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toDegree)(value, `${label}の位置`));
204
104
  break;
205
105
  case 'cardinality':
206
- put(patch, path, toOptionNumberOrNull(value, defaults_js_1.CARDINALITIES, `${label}のカーディナリティ`));
106
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOptionNumberOrNull)(value, defaults_js_1.CARDINALITIES, `${label}のカーディナリティ`));
207
107
  break;
208
108
  case 'optionality':
209
- put(patch, path, toOptionNumberOrNull(value, defaults_js_1.OPTIONALITIES, `${label}のオプショナリティ`));
109
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOptionNumberOrNull)(value, defaults_js_1.OPTIONALITIES, `${label}のオプショナリティ`));
210
110
  break;
211
111
  }
212
112
  continue;
213
113
  }
214
114
  switch (path) {
215
115
  case 'subset.kind':
216
- put(patch, path, toOption(value, defaults_js_1.SUBSET_KINDS, 'サブセットの区分'));
116
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOption)(value, defaults_js_1.SUBSET_KINDS, 'サブセットの区分'));
217
117
  break;
218
118
  case 'subset.code':
219
119
  // 区分コードを空にするとサブセットの表記を消す(subset ごと除く)
220
120
  if (value === '')
221
121
  patch.subset = null;
222
122
  else
223
- put(patch, path, value);
123
+ (0, model_core_1.put)(patch, path, value);
224
124
  break;
225
125
  case 'type':
226
126
  case 'label':
227
127
  case 'description':
228
- put(patch, path, toOptionalText(value));
128
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOptionalText)(value));
229
129
  break;
230
130
  }
231
131
  }
@@ -235,7 +135,7 @@ function relationshipPatch(relationship, values) {
235
135
  // kind だけを新しく書いて code が無い subset は作らない(d3.ter は code が無ければ描かない)
236
136
  const subset = patch.subset;
237
137
  if (subset && subset.kind !== undefined && subset.code === undefined && !relationship.subset?.code)
238
- throw new ModelError('サブセットの区分コードを入力してください。');
138
+ throw new model_core_1.ModelError('サブセットの区分コードを入力してください。');
239
139
  return patch;
240
140
  }
241
141
  // マスタの入力 → patch(name / description)
@@ -252,7 +152,7 @@ function masterPatch(master, values, collection) {
252
152
  namePatch(patch, path, value, false);
253
153
  break;
254
154
  case 'description':
255
- put(patch, path, toOptionalText(value));
155
+ (0, model_core_1.put)(patch, path, (0, model_core_1.toOptionalText)(value));
256
156
  break;
257
157
  }
258
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,YAAY,EACV,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAC3F,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,QAAQ,EAAE,oBAAoB,EACrG,QAAQ,EAAE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,EAC3D,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,GAC7D,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EACjG,eAAe,EAAE,qBAAqB,EACtC,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,YAAY,GACpI,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACpH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,YAAY,EACV,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAC3F,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,QAAQ,EAAE,oBAAoB,EACrG,QAAQ,EAAE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,EAC3D,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,GAC7D,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EACL,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EACjG,eAAe,EAAE,qBAAqB,EACtC,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,YAAY,GACpI,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACpH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtG,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@
8
8
  // patch への変換: movePatch(entity, move) / applyPatch(record, values, collection?) / mergePatch(record, patch)
9
9
  //
10
10
  // view(d3.ter)はこのパッケージの既定値(種別バッジ・配色・角度)を読む。インスペクタ(colonoscope)との間に横の依存は無い。
11
+ // ModelError / mergePatch / getByPath / movePatch と入力の読み取り部品は model.core(#151)のもの。
11
12
  export { ENTITY_TYPES, TYPE_BADGES, TYPE_COLORS, MAPPING_TYPES, SUBSET_KINDS, CARDINALITIES, OPTIONALITIES, ENTITY_DEFAULTS, RELATIONSHIP_DEFAULTS, isEntityType, isRelationship, isEntity, kindOf, nameOf, endWithDefaults, entityWithDefaults, relationshipWithDefaults, withDefaults, } from './defaults.js';
12
13
  export { SCHEMAS, IDENTIFIERS_SCHEMA, ATTRIBUTES_SCHEMA, ENTITIES_SCHEMA, RELATIONSHIPS_SCHEMA } from './schema.js';
13
14
  export { check, findById } from './check.js';
package/dist/patch.d.ts CHANGED
@@ -1,8 +1,5 @@
1
- import type { MergePatch, TmChange, TmCollection, TmEntityRecord, TmMove, TmRecord, TmValues } from './types.js';
2
- /** 入力の検証エラー。message は利用者向け */
3
- export declare class ModelError extends Error {
4
- constructor(message: string);
5
- }
1
+ import type { TmChange, TmCollection, TmEntityRecord, TmMove, TmRecord, TmValues } from './types.js';
2
+ export { ModelError, getByPath, mergePatch } from '@yanqirenshi/model.core';
6
3
  /**
7
4
  * レコードを置くコレクション。関係・エンティティは形から決まる。
8
5
  * マスタは identifiers / attributes を形から判別できないので collection が要る。
@@ -10,13 +7,6 @@ export declare class ModelError extends Error {
10
7
  export declare function collectionOf(record: TmRecord, collection?: TmCollection): TmCollection;
11
8
  /** インスペクタの見出し。名前、無ければ id(関係はラベル、無ければ「種類 id」) */
12
9
  export declare function titleOf(record: TmRecord): string;
13
- /** ドット区切りの path で値を取り出す */
14
- export declare function getByPath(obj: unknown, path: string): unknown;
15
- /**
16
- * JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
17
- * null の項目は除く。保存したあと、表示を先に更新するときに使う。
18
- */
19
- export declare function mergePatch<T extends object>(record: T, patch: MergePatch): T;
20
10
  /**
21
11
  * ドラッグで動かしたエンティティの位置を保存する patch。
22
12
  * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。z は触らない。
@@ -1 +1 @@
1
- {"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAkB,MAAM,EAAE,QAAQ,EAAwB,QAAQ,EACrH,MAAM,YAAY,CAAC;AAEpB,8BAA8B;AAC9B,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,YAAY,GAAG,YAAY,CAWtF;AAED,gDAAgD;AAChD,wBAAgB,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAWhD;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;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAaxE;AA2ND;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,YAAY,GAAG,QAAQ,CAUlG"}
1
+ {"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAkB,MAAM,EAAE,QAAQ,EAAwB,QAAQ,EACrH,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE5E;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,YAAY,GAAG,YAAY,CAWtF;AAED,gDAAgD;AAChD,wBAAgB,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAWhD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAExE;AAmID;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,YAAY,GAAG,QAAQ,CAUlG"}
package/dist/patch.js CHANGED
@@ -3,15 +3,12 @@
3
3
  // 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
4
4
  // 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
5
5
  // 使い方の誤り(マスタなのに collection を渡していない)は ModelError ではなく Error。
6
+ // 読み取り部品(toLength など)と patch の道具は model.core にある(#151)。
7
+ import { ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put, toDegree, toLength, toOption, toOptionNumberOrNull, toOptionalText, } from '@yanqirenshi/model.core';
6
8
  import { CARDINALITIES, ENTITY_TYPES, OPTIONALITIES, SUBSET_KINDS, isEntity, isRelationship, kindOf, nameOf, withDefaults, } from './defaults.js';
7
9
  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
- }
10
+ // model.core のものをそのまま公開する(利用側が model.tm だけを読めばよいように)
11
+ export { ModelError, getByPath, mergePatch } from '@yanqirenshi/model.core';
15
12
  /**
16
13
  * レコードを置くコレクション。関係・エンティティは形から決まる。
17
14
  * マスタは identifiers / attributes を形から判別できないので collection が要る。
@@ -35,116 +32,19 @@ export function titleOf(record) {
35
32
  const name = nameOf(record);
36
33
  return name !== '' ? name : String(record.id);
37
34
  }
38
- /** ドット区切りの path で値を取り出す */
39
- export function getByPath(obj, path) {
40
- return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
41
- }
42
- /**
43
- * JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
44
- * null の項目は除く。保存したあと、表示を先に更新するときに使う。
45
- */
46
- export function mergePatch(record, patch) {
47
- const out = { ...record };
48
- for (const [key, value] of Object.entries(patch)) {
49
- if (value === null) {
50
- delete out[key];
51
- }
52
- else if (typeof value === 'object' && !Array.isArray(value)) {
53
- const base = out[key];
54
- out[key] = mergePatch(base && typeof base === 'object' && !Array.isArray(base) ? base : {}, value);
55
- }
56
- else {
57
- out[key] = value;
58
- }
59
- }
60
- return out;
61
- }
62
35
  /**
63
36
  * ドラッグで動かしたエンティティの位置を保存する patch。
64
37
  * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。z は触らない。
65
38
  */
66
39
  export function movePatch(entity, move) {
67
- const base = entity.position || { x: 0, y: 0 };
68
- return {
69
- collection: 'entities',
70
- id: entity.id,
71
- patch: {
72
- position: {
73
- x: Math.round(base.x + move.dx),
74
- y: Math.round(base.y + move.dy),
75
- },
76
- },
77
- };
78
- }
79
- // ---- 入力の読み取り ------------------------------------------------------------
80
- // 入力した文字列を数にする。数でないときは null。
81
- function toNumber(value) {
82
- const n = Number(value);
83
- return value !== '' && Number.isFinite(n) ? n : null;
84
- }
85
- // 0 以上の整数(幅・高さ。0 は「内容に合わせる」)
86
- function toLength(value, label) {
87
- const n = toNumber(value);
88
- if (n === null || n < 0)
89
- throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で内容に合わせる)`);
90
- return Math.round(n);
40
+ return movePatchTo(entity, move, 'entities');
91
41
  }
92
- // 角度(0 以上 360 未満の整数に正規化)
93
- function toDegree(value, label) {
94
- const n = toNumber(value);
95
- if (n === null)
96
- throw new ModelError(`${label}は、角度 (数) で入力してください。`);
97
- return ((Math.round(n) % 360) + 360) % 360;
98
- }
99
- // 候補の中の数、または空(null = 項目を除く)
100
- function toOptionNumberOrNull(value, options, label) {
101
- if (value === '')
102
- return null;
103
- const n = toNumber(value);
104
- if (n === null || !options.includes(n))
105
- throw new ModelError(`${label}は、${options.join(' / ')} のどれか (または空) で入力してください。`);
106
- return n;
107
- }
108
- // 候補の中の文字列
109
- function toOption(value, options, label) {
110
- if (!options.includes(value))
111
- throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
112
- return value;
113
- }
114
- // 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
115
- function toOptionalText(value) {
116
- return value === '' ? null : value;
117
- }
118
- // patch の入れ子に値を置く(patch.from.position = v のように)
119
- function put(patch, path, value) {
120
- const keys = path.split('.');
121
- let cur = patch;
122
- for (const key of keys.slice(0, -1)) {
123
- if (!cur[key] || typeof cur[key] !== 'object')
124
- cur[key] = {};
125
- cur = cur[key];
126
- }
127
- cur[keys[keys.length - 1]] = value;
128
- }
129
- /**
130
- * インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
131
- * 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
132
- */
42
+ // インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
133
43
  function changedValues(record, values, collection) {
134
- const effective = withDefaults(record);
135
- const out = new Map();
136
- for (const field of fieldsOf(record, collection)) {
137
- if (field.type === 'readonly')
138
- continue;
139
- const value = values[field.path];
140
- if (value === undefined)
141
- continue;
142
- const current = getByPath(effective, field.path);
143
- if (value.trim() !== String(current ?? ''))
144
- out.set(field.path, value.trim());
145
- }
146
- return out;
44
+ return changedValuesOf(withDefaults(record), values, fieldsOf(record, collection));
147
45
  }
46
+ // 幅・高さは 0 以上(0 は「内容に合わせる」)
47
+ const LENGTH = { hint: '(0 で内容に合わせる)' };
148
48
  // 名前(name / name.logical / name.physical)。required なら空にできない
149
49
  function namePatch(patch, path, value, required) {
150
50
  // name と name.logical は空にできない。name.physical は空にできる(論理名だけのモデル)
@@ -168,10 +68,10 @@ function entityPatch(entity, values) {
168
68
  put(patch, path, toOption(value, ENTITY_TYPES, '種別'));
169
69
  break;
170
70
  case 'size.w':
171
- put(patch, path, toLength(value, '幅'));
71
+ put(patch, path, toLength(value, '幅', LENGTH));
172
72
  break;
173
73
  case 'size.h':
174
- put(patch, path, toLength(value, '高さ'));
74
+ put(patch, path, toLength(value, '高さ', LENGTH));
175
75
  break;
176
76
  case 'description':
177
77
  put(patch, path, toOptionalText(value));
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { MergePatch, ModelField, ModelFieldType, ModelMove, ModelValues } from '@yanqirenshi/model.core';
1
2
  import type { Name, Position, Size } from '@yanqirenshi/types';
2
3
  /** レコードの id(コレクションの中で一意。インスタンス(identifiers / attributes の行)の id は全エンティティを通して一意) */
3
4
  export type TmId = number | string;
@@ -107,10 +108,8 @@ export type TmMasterCollection = 'identifiers' | 'attributes';
107
108
  export type TmCollection = TmMasterCollection | 'entities' | 'relationships';
108
109
  /** レコードの種類(どの形か) */
109
110
  export type TmKind = 'master' | 'entity' | 'relationship';
110
- /** JSON Merge Patch(RFC 7386)。null は「その項目を除く」 */
111
- export type MergePatch = {
112
- [key: string]: unknown;
113
- };
111
+ /** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
112
+ export type { MergePatch };
114
113
  /** 保存する変更。利用側は図の種類を知らずにこれを保存すればよい */
115
114
  export interface TmChange {
116
115
  collection: TmCollection;
@@ -118,23 +117,13 @@ export interface TmChange {
118
117
  patch: MergePatch;
119
118
  }
120
119
  /** ドラッグの移動量(世界座標) */
121
- export interface TmMove {
122
- dx: number;
123
- dy: number;
124
- }
120
+ export type TmMove = ModelMove;
125
121
  /**
126
122
  * 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
127
123
  * 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
128
124
  */
129
- export type TmFieldType = 'text' | 'textarea' | 'number' | 'readonly';
130
- export interface TmField {
131
- /** レコードの項目のパス(ドット区切り。例: 'from.position')。写し先ではなく実際の項目を指す */
132
- path: string;
133
- label: string;
134
- type: TmFieldType;
135
- /** 取りうる値の一覧(種別・カーディナリティなど。文字列で持つ) */
136
- options?: readonly string[];
137
- }
125
+ export type TmFieldType = ModelFieldType;
126
+ export type TmField = ModelField;
138
127
  /** インスペクタの「適用」が渡す値(path → 入力した文字列) */
139
- export type TmValues = Record<string, string>;
128
+ export type TmValues = ModelValues;
140
129
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanqirenshi/model.tm",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Diagram model for TM (T-shaped ER method) diagrams (d3.ter) — JSON Schema, consistency rules, drawing defaults, editable fields and patch conversion. Pure data and functions: no React / d3 dependency",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,6 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
+ "@yanqirenshi/model.core": "^0.1.0",
40
41
  "@yanqirenshi/types": "^0.1.2"
41
42
  },
42
43
  "devDependencies": {
package/src/check.ts CHANGED
@@ -7,22 +7,11 @@
7
7
  //
8
8
  // メッセージの先頭はコレクション名で、prefix を渡すと 'tm/entities: ...' のように付けられる。
9
9
 
10
- import { MAPPING_TYPES, isEntityType } from './defaults.js';
11
- import type { TmEntityRecord, TmGraph, TmId, TmRecord } from './types.js';
12
-
13
- // list の中で id が重複していればエラーにする。id の集合を返す。
14
- function checkUnique(errors: string[], label: string, list: readonly { id: TmId }[]): Set<TmId> {
15
- const values = new Set<TmId>();
16
-
17
- for (const d of list) {
18
- if (values.has(d.id))
19
- errors.push(`${label}: id が重複しています。(id=${String(d.id)})`);
10
+ import { checkUnique, findById as findInList } from '@yanqirenshi/model.core';
20
11
 
21
- values.add(d.id);
22
- }
12
+ import { MAPPING_TYPES, isEntityType } from './defaults.js';
23
13
 
24
- return values;
25
- }
14
+ import type { TmEntityRecord, TmGraph, TmId, TmRecord } from './types.js';
26
15
 
27
16
  // エンティティが持つ識別子のマスタの集合(CLASS の親判定に使う)
28
17
  function identifierMasters(entity: TmEntityRecord): Set<TmId> {
@@ -133,5 +122,5 @@ export function check(data: TmGraph, prefix = ''): string[] {
133
122
 
134
123
  /** コレクションの中からレコードを id で探す(無ければ null) */
135
124
  export function findById<T extends TmRecord>(list: readonly T[] | undefined, id: TmId): T | null {
136
- return (list || []).find(d => d.id === id) || null;
125
+ return findInList(list, id);
137
126
  }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  // patch への変換: movePatch(entity, move) / applyPatch(record, values, collection?) / mergePatch(record, patch)
9
9
  //
10
10
  // view(d3.ter)はこのパッケージの既定値(種別バッジ・配色・角度)を読む。インスペクタ(colonoscope)との間に横の依存は無い。
11
+ // ModelError / mergePatch / getByPath / movePatch と入力の読み取り部品は model.core(#151)のもの。
11
12
 
12
13
  export type {
13
14
  TmId, TmName, TmEntityType, TmMasterRecord, TmIdentifierRef, TmAttributeRef, TmEntityRecord,
package/src/patch.ts CHANGED
@@ -3,6 +3,11 @@
3
3
  // 戻り値は { collection, id, patch }。利用側(iHub など)は図の種類を知らずにこれを保存すればよい。
4
4
  // 入力の検証に失敗したときは ModelError を投げる(メッセージは利用者にそのまま見せられる日本語)。
5
5
  // 使い方の誤り(マスタなのに collection を渡していない)は ModelError ではなく Error。
6
+ // 読み取り部品(toLength など)と patch の道具は model.core にある(#151)。
7
+
8
+ import {
9
+ ModelError, changedValues as changedValuesOf, movePatch as movePatchTo, put, toDegree, toLength, toOption, toOptionNumberOrNull, toOptionalText,
10
+ } from '@yanqirenshi/model.core';
6
11
 
7
12
  import {
8
13
  CARDINALITIES, ENTITY_TYPES, OPTIONALITIES, SUBSET_KINDS, isEntity, isRelationship, kindOf, nameOf, withDefaults,
@@ -12,13 +17,8 @@ import type {
12
17
  MergePatch, TmChange, TmCollection, TmEntityRecord, TmMasterRecord, TmMove, TmRecord, TmRelationshipRecord, TmValues,
13
18
  } from './types.js';
14
19
 
15
- /** 入力の検証エラー。message は利用者向け */
16
- export class ModelError extends Error {
17
- constructor(message: string) {
18
- super(message);
19
- this.name = 'ModelError';
20
- }
21
- }
20
+ // model.core のものをそのまま公開する(利用側が model.tm だけを読めばよいように)
21
+ export { ModelError, getByPath, mergePatch } from '@yanqirenshi/model.core';
22
22
 
23
23
  /**
24
24
  * レコードを置くコレクション。関係・エンティティは形から決まる。
@@ -51,154 +51,22 @@ export function titleOf(record: TmRecord): string {
51
51
  return name !== '' ? name : String(record.id);
52
52
  }
53
53
 
54
- /** ドット区切りの path で値を取り出す */
55
- export function getByPath(obj: unknown, path: string): unknown {
56
- return path.split('.').reduce<unknown>(
57
- (acc, key) => (acc == null ? acc : (acc as Record<string, unknown>)[key]),
58
- obj,
59
- );
60
- }
61
-
62
- /**
63
- * JSON Merge Patch をレコードに重ねたものを返す(入力は書き換えない)。
64
- * null の項目は除く。保存したあと、表示を先に更新するときに使う。
65
- */
66
- export function mergePatch<T extends object>(record: T, patch: MergePatch): T {
67
- const out: Record<string, unknown> = { ...(record as Record<string, unknown>) };
68
-
69
- for (const [key, value] of Object.entries(patch)) {
70
- if (value === null) {
71
- delete out[key];
72
- } else if (typeof value === 'object' && !Array.isArray(value)) {
73
- const base = out[key];
74
-
75
- out[key] = mergePatch(
76
- base && typeof base === 'object' && !Array.isArray(base) ? (base as object) : {},
77
- value as MergePatch,
78
- );
79
- } else {
80
- out[key] = value;
81
- }
82
- }
83
-
84
- return out as T;
85
- }
86
-
87
54
  /**
88
55
  * ドラッグで動かしたエンティティの位置を保存する patch。
89
56
  * 保存してある position に移動量を足し、整数に丸める(position が無いときは 0, 0 から)。z は触らない。
90
57
  */
91
58
  export function movePatch(entity: TmEntityRecord, move: TmMove): TmChange {
92
- const base = entity.position || { x: 0, y: 0 };
93
-
94
- return {
95
- collection: 'entities',
96
- id: entity.id,
97
- patch: {
98
- position: {
99
- x: Math.round(base.x + move.dx),
100
- y: Math.round(base.y + move.dy),
101
- },
102
- },
103
- };
59
+ return movePatchTo(entity, move, 'entities');
104
60
  }
105
61
 
106
- // ---- 入力の読み取り ------------------------------------------------------------
107
-
108
- // 入力した文字列を数にする。数でないときは null。
109
- function toNumber(value: string): number | null {
110
- const n = Number(value);
111
-
112
- return value !== '' && Number.isFinite(n) ? n : null;
113
- }
114
-
115
- // 0 以上の整数(幅・高さ。0 は「内容に合わせる」)
116
- function toLength(value: string, label: string): number {
117
- const n = toNumber(value);
118
-
119
- if (n === null || n < 0)
120
- throw new ModelError(`${label}は 0 以上の数で入力してください。(0 で内容に合わせる)`);
121
-
122
- return Math.round(n);
123
- }
124
-
125
- // 角度(0 以上 360 未満の整数に正規化)
126
- function toDegree(value: string, label: string): number {
127
- const n = toNumber(value);
128
-
129
- if (n === null)
130
- throw new ModelError(`${label}は、角度 (数) で入力してください。`);
131
-
132
- return ((Math.round(n) % 360) + 360) % 360;
133
- }
134
-
135
- // 候補の中の数、または空(null = 項目を除く)
136
- function toOptionNumberOrNull(value: string, options: readonly number[], label: string): number | null {
137
- if (value === '')
138
- return null;
139
-
140
- const n = toNumber(value);
141
-
142
- if (n === null || !options.includes(n))
143
- throw new ModelError(`${label}は、${options.join(' / ')} のどれか (または空) で入力してください。`);
144
-
145
- return n;
146
- }
147
-
148
- // 候補の中の文字列
149
- function toOption(value: string, options: readonly string[], label: string): string {
150
- if (!options.includes(value))
151
- throw new ModelError(`${label}は、${options.join(' / ')} のどれかで入力してください。`);
152
-
153
- return value;
154
- }
155
-
156
- // 空にできる文字列。空なら null(JSON Merge Patch で項目を除く)
157
- function toOptionalText(value: string): string | null {
158
- return value === '' ? null : value;
159
- }
160
-
161
- // patch の入れ子に値を置く(patch.from.position = v のように)
162
- function put(patch: MergePatch, path: string, value: unknown): void {
163
- const keys = path.split('.');
164
- let cur = patch;
165
-
166
- for (const key of keys.slice(0, -1)) {
167
- if (!cur[key] || typeof cur[key] !== 'object')
168
- cur[key] = {};
169
-
170
- cur = cur[key] as MergePatch;
171
- }
172
-
173
- cur[keys[keys.length - 1]] = value;
174
- }
175
-
176
- /**
177
- * インスペクタの入力のうち、図に描いている値(withDefaults で埋めた値)から変わった項目だけを返す。
178
- * 前後の空白は除く。読み取り専用の項目と、values に無い項目は含めない。
179
- */
62
+ // インスペクタの入力のうち、図に描いている値(withDefaults)から変わった項目だけ
180
63
  function changedValues(record: TmRecord, values: TmValues, collection?: TmCollection): Map<string, string> {
181
- const effective = withDefaults(record);
182
- const out = new Map<string, string>();
183
-
184
- for (const field of fieldsOf(record, collection)) {
185
- if (field.type === 'readonly')
186
- continue;
187
-
188
- const value = values[field.path];
189
-
190
- if (value === undefined)
191
- continue;
192
-
193
- const current = getByPath(effective, field.path);
194
-
195
- if (value.trim() !== String(current ?? ''))
196
- out.set(field.path, value.trim());
197
- }
198
-
199
- return out;
64
+ return changedValuesOf(withDefaults(record), values, fieldsOf(record, collection));
200
65
  }
201
66
 
67
+ // 幅・高さは 0 以上(0 は「内容に合わせる」)
68
+ const LENGTH = { hint: '(0 で内容に合わせる)' };
69
+
202
70
  // 名前(name / name.logical / name.physical)。required なら空にできない
203
71
  function namePatch(patch: MergePatch, path: string, value: string, required: boolean): void {
204
72
  // name と name.logical は空にできない。name.physical は空にできる(論理名だけのモデル)
@@ -225,10 +93,10 @@ function entityPatch(entity: TmEntityRecord, values: TmValues): MergePatch {
225
93
  put(patch, path, toOption(value, ENTITY_TYPES, '種別'));
226
94
  break;
227
95
  case 'size.w':
228
- put(patch, path, toLength(value, '幅'));
96
+ put(patch, path, toLength(value, '幅', LENGTH));
229
97
  break;
230
98
  case 'size.h':
231
- put(patch, path, toLength(value, '高さ'));
99
+ put(patch, path, toLength(value, '高さ', LENGTH));
232
100
  break;
233
101
  case 'description':
234
102
  put(patch, path, toOptionalText(value));
package/src/types.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  // 形は model.er(ER 図)と同じ階層形式で、TM 固有の項目(エンティティの type、関係の subset / mapping / label)が足されている。
6
6
  // d3.ter が正規化したあとの datum(_id / _core を持つ Atman)ではない。
7
7
 
8
+ import type { MergePatch, ModelField, ModelFieldType, ModelMove, ModelValues } from '@yanqirenshi/model.core';
8
9
  import type { Name, Position, Size } from '@yanqirenshi/types';
9
10
 
10
11
  /** レコードの id(コレクションの中で一意。インスタンス(identifiers / attributes の行)の id は全エンティティを通して一意) */
@@ -130,8 +131,8 @@ export type TmCollection = TmMasterCollection | 'entities' | 'relationships';
130
131
  /** レコードの種類(どの形か) */
131
132
  export type TmKind = 'master' | 'entity' | 'relationship';
132
133
 
133
- /** JSON Merge Patch(RFC 7386)。null は「その項目を除く」 */
134
- export type MergePatch = { [key: string]: unknown };
134
+ /** JSON Merge Patch(RFC 7386)。null は「その項目を除く」。model.core の MergePatch と同じ(iHub の PATCH /{collection}/{id} が受け取る形) */
135
+ export type { MergePatch };
135
136
 
136
137
  /** 保存する変更。利用側は図の種類を知らずにこれを保存すればよい */
137
138
  export interface TmChange {
@@ -141,25 +142,15 @@ export interface TmChange {
141
142
  }
142
143
 
143
144
  /** ドラッグの移動量(世界座標) */
144
- export interface TmMove {
145
- dx: number;
146
- dy: number;
147
- }
145
+ export type TmMove = ModelMove;
148
146
 
149
147
  /**
150
148
  * 編集できる項目。colonoscope の ColonoscopeField(path / label / type)と同じ形で、
151
149
  * 取りうる値の一覧 options だけ足している(型は構造的に互換。colonoscope には依存しない)。
152
150
  */
153
- export type TmFieldType = 'text' | 'textarea' | 'number' | 'readonly';
154
-
155
- export interface TmField {
156
- /** レコードの項目のパス(ドット区切り。例: 'from.position')。写し先ではなく実際の項目を指す */
157
- path: string;
158
- label: string;
159
- type: TmFieldType;
160
- /** 取りうる値の一覧(種別・カーディナリティなど。文字列で持つ) */
161
- options?: readonly string[];
162
- }
151
+ export type TmFieldType = ModelFieldType;
152
+
153
+ export type TmField = ModelField;
163
154
 
164
155
  /** インスペクタの「適用」が渡す値(path → 入力した文字列) */
165
- export type TmValues = Record<string, string>;
156
+ export type TmValues = ModelValues;