@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018-2026 Satoshi Iwasaki (yanqirenshi@gmail.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,144 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @yanqirenshi/model.tm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TM(T字形ER手法)の図の**図ごとの知識**を、描画(view = `d3.ter`)から独立させたパッケージです。
|
|
4
|
+
react / d3 に依存しない、純粋なデータと関数だけを持ちます。
|
|
5
|
+
d3.ter は、このパッケージの種別バッジ・配色・端点の角度の既定を読みます。
|
|
6
|
+
|
|
7
|
+
| 持つもの | 入口 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| 入力データのスキーマ(JSON Schema 2020-12) | `SCHEMAS.identifiers` / `.attributes` / `.entities` / `.relationships` |
|
|
10
|
+
| コレクションをまたぐ整合性ルール(id の重複・参照先・TM の結線の規則) | `check(data)` |
|
|
11
|
+
| view が既定で使う値 | `withDefaults(record)` / `entityWithDefaults` / `relationshipWithDefaults`、`TYPE_BADGES` / `TYPE_COLORS` |
|
|
12
|
+
| 編集できる項目の一覧(インスペクタ用) | `fieldsOf(record, collection?)` |
|
|
13
|
+
| UI の操作を保存する patch に変える変換 | `movePatch(entity, move)` / `applyPatch(record, values, collection?)` |
|
|
14
|
+
|
|
15
|
+
利用側(iHub など)は、モデルが返す `{ collection, id, patch }` を保存するだけで、図の種類を知らずに書けます。
|
|
16
|
+
|
|
17
|
+
## 位置づけ
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
利用側 ──▶ d3.ter (view) ──▶ model.tm (種別バッジ・配色・角度の既定を読む)
|
|
21
|
+
──▶ colonoscope (ModelInspector)
|
|
22
|
+
──▶ model.tm
|
|
23
|
+
──▶ 自前の永続化
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
model.tm・colonoscope の間に横の依存はありません。組み合わせるのは利用側だけです。
|
|
27
|
+
これは [Foolsgolds/Assholes#138](https://github.com/Foolsgolds/Assholes/issues/138) の派生([#144](https://github.com/Foolsgolds/Assholes/issues/144))です。
|
|
28
|
+
レコードの形は [model.er](../model.er) と同じ階層形式で、TM 固有の項目(エンティティの `type`、関係の `subset` / `mapping` / `label`)が足されています。
|
|
29
|
+
|
|
30
|
+
## レコードの形
|
|
31
|
+
|
|
32
|
+
d3.ter の `data()` に渡す入力そのものです。
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
{
|
|
36
|
+
identifiers: [{ id, name, description }], // name は文字列でも { logical, physical } でも
|
|
37
|
+
attributes: [{ id, name, description }],
|
|
38
|
+
entities: [{ id, type, name, description, // type は 10 種(ENTITY_TYPES)
|
|
39
|
+
position: { x, y }, size: { w, h }, // size の 0 は「内容に合わせる」
|
|
40
|
+
identifiers: [{ id, identifier, name?, description? }], // identifier は identifiers[].id
|
|
41
|
+
attributes: [{ id, attribute, name?, description? }] }], // attribute は attributes[].id
|
|
42
|
+
relationships: [{ id, type, label,
|
|
43
|
+
from: { entity, cardinality, optionality, position }, // 1|3, 0|1(無ければ記号を描かない), 角度(0=真下・時計回り)
|
|
44
|
+
to: { entity, cardinality, optionality, position },
|
|
45
|
+
details: [{ from: { id }, to: { id } }], // 線を取り付けるインスタンス
|
|
46
|
+
subset: { kind: 'same' | 'different', code }, // サブセット結線の区分コード(= / ×)
|
|
47
|
+
mapping: { entity } }], // 中点から垂下する対応表・対照表・再帰表
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 使い方
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import D3Ter, { Rectum } from '@yanqirenshi/d3.ter';
|
|
55
|
+
import { ModelInspector } from '@yanqirenshi/colonoscope';
|
|
56
|
+
import {
|
|
57
|
+
check, fieldsOf, titleOf, withDefaults, movePatch, applyPatch, mergePatch, findById, ModelError,
|
|
58
|
+
} from '@yanqirenshi/model.tm';
|
|
59
|
+
|
|
60
|
+
const errors = check(data); // [] なら問題なし
|
|
61
|
+
|
|
62
|
+
const rectum = new Rectum({
|
|
63
|
+
callbacks: {
|
|
64
|
+
entity: {
|
|
65
|
+
// ドラッグの終わり → 位置の patch(core は entities[] の要素)
|
|
66
|
+
dragend: (core, entity, move) => save(movePatch(core, move)),
|
|
67
|
+
// → { collection: 'entities', id: 30000, patch: { position: { x: 120, y: 40 } } }
|
|
68
|
+
contextmenu: (d) => setTarget({ collection: 'entities', id: d._core.id }),
|
|
69
|
+
},
|
|
70
|
+
// 識別子・属性の行 → その行が参照するマスタ(d._master._core)
|
|
71
|
+
identifier: { contextmenu: (d) => setTarget({ collection: 'identifiers', id: d._master._core.id }) },
|
|
72
|
+
attribute: { contextmenu: (d) => setTarget({ collection: 'attributes', id: d._master._core.id }) },
|
|
73
|
+
relationship: { click: (d) => setTarget({ collection: 'relationships', id: d._core.id }) },
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const record = findById(data[target.collection], target.id);
|
|
78
|
+
<ModelInspector
|
|
79
|
+
target={record && withDefaults(record)}
|
|
80
|
+
fields={(t) => fieldsOf(t, target.collection)}
|
|
81
|
+
title={titleOf}
|
|
82
|
+
onApply={(values) => {
|
|
83
|
+
try {
|
|
84
|
+
save(applyPatch(record, values, target.collection)); // 変えた項目だけが patch に入る
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (e instanceof ModelError) alert(e.message); // 利用者向けの日本語
|
|
87
|
+
}
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `applyPatch` の検証
|
|
93
|
+
|
|
94
|
+
| 項目 | 規則 |
|
|
95
|
+
|---|---|
|
|
96
|
+
| 名称(`name` / `name.logical`) | 空にできない |
|
|
97
|
+
| 物理名・説明・種類・ラベル | 空にすると項目を除く(`null` — JSON Merge Patch) |
|
|
98
|
+
| 種別(`type`) | 10 種のどれか(`ENTITY_TYPES`) |
|
|
99
|
+
| 幅・高さ | 0 以上。整数に丸める(0 で内容に合わせる) |
|
|
100
|
+
| 接続位置(角度) | 数。0 以上 360 未満の整数に正規化 |
|
|
101
|
+
| カーディナリティ・オプショナリティ | `1` / `3`、`0` / `1`(数に戻す)。**空にすると除く**(記号を描かない) |
|
|
102
|
+
| サブセットの区分 | `same` / `different`。区分コードを空にすると `subset` ごと除く。コード無しで区分だけは作れない |
|
|
103
|
+
|
|
104
|
+
図に描いている値(`withDefaults` の結果)から**変えた項目だけ**を patch に入れます。
|
|
105
|
+
マスタ(識別子・属性)は形から区別できないので `collection` が要ります(無ければ使い方の誤りとして `Error`)。
|
|
106
|
+
|
|
107
|
+
### 編集できる項目(`fieldsOf`)
|
|
108
|
+
|
|
109
|
+
- 名前: `name` が文字列なら「名称」1 項目、`{ logical, physical }` なら「論理名」「物理名」の 2 項目
|
|
110
|
+
- エンティティ: 名前・種別・ID・説明・幅・高さ
|
|
111
|
+
- 関係: ID・種類・ラベル・接続元 / 接続先のエンティティと角度・カーディナリティ・オプショナリティ・サブセットの区分 / 区分コード・垂下する表・説明
|
|
112
|
+
- マスタ: 名前・説明・ID
|
|
113
|
+
|
|
114
|
+
エンティティのインスタンス(識別子・属性の行)は `entities[]` の入れ子で、JSON Merge Patch では 1 要素を指せないので編集の対象にしていません(行を右クリックしたときはマスタを開きます)。
|
|
115
|
+
|
|
116
|
+
### 整合性ルール(`check`)
|
|
117
|
+
|
|
118
|
+
iHub の `checkTm` と同じ検査(文言も同じ)に、d3.ter がデータ構築時に例外にしている規則を足しています。
|
|
119
|
+
|
|
120
|
+
- 各コレクションの id の重複、エンティティのインスタンスの id の重複(全エンティティを通して)
|
|
121
|
+
- `type` が 10 種のどれか
|
|
122
|
+
- `identifier` / `attribute` がマスタにある、`from.entity` / `to.entity` がエンティティにある
|
|
123
|
+
- 端点の旧 `id`(あれば)がその端の識別子に、`details[].from.id` / `to.id` が識別子・属性にある
|
|
124
|
+
- `mapping.entity` がエンティティにあり、種別が対応表・対照表・再帰表のどれか。再帰表は自己結線にだけ
|
|
125
|
+
- CLASS 同士は結線できず、CLASS は切り出し元(親 = クラスの識別子のマスタを持つ側)としか結線できない
|
|
126
|
+
|
|
127
|
+
## スコープ外(いまは持たない)
|
|
128
|
+
|
|
129
|
+
- インスタンス(識別子・属性の行)の追加・削除・名前の上書き、`details` の編集
|
|
130
|
+
- 「コレクション → view の入力」の変換(iHub の `buildTmData` のようなもの)
|
|
131
|
+
- ファイル・HTTP などの永続化(利用側の実装)
|
|
132
|
+
|
|
133
|
+
## 配布形式
|
|
134
|
+
|
|
135
|
+
ESM(`dist/`)に加えて CommonJS 版(`dist/cjs/`)も出します。
|
|
136
|
+
Babel 系の `d3.ter` を jest(CommonJS)でテストするためで、d3.ter の jest が ESM を読めるようになったら外せます。
|
|
137
|
+
|
|
138
|
+
## コマンド
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm run build -w @yanqirenshi/model.tm # tsc で dist/(ESM + d.ts)と dist/cjs/(CommonJS)へビルド
|
|
142
|
+
npm run typecheck -w @yanqirenshi/model.tm
|
|
143
|
+
npm test -w @yanqirenshi/model.tm # vitest(d3.ter が model.tm を読んでいることの確認を含む)
|
|
144
|
+
```
|
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TmGraph, TmId, TmRecord } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 図全体を検査する。
|
|
4
|
+
* - identifiers / attributes / entities / relationships の id が、それぞれのコレクションの中で重複していない
|
|
5
|
+
* - エンティティのインスタンス(identifiers[] / attributes[] の行)の id が、全エンティティを通して重複していない
|
|
6
|
+
* - entities[].type が 10 種のどれか
|
|
7
|
+
* - entities[].identifiers[].identifier が identifiers に、attributes[].attribute が attributes にある
|
|
8
|
+
* - relationships[].from.entity / to.entity がエンティティにある
|
|
9
|
+
* - 端点の旧 id(あれば)がその端のエンティティの識別子に、details[].from.id / to.id が識別子・属性にある
|
|
10
|
+
* - mapping.entity がエンティティにあり、種別が対応表・対照表・再帰表のどれか。再帰表は自己結線にだけ
|
|
11
|
+
* - CLASS 同士は結線できず、CLASS は切り出し元(親 = クラスの識別子のマスタを持つ側)としか結線できない
|
|
12
|
+
*
|
|
13
|
+
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'tm/')
|
|
14
|
+
*/
|
|
15
|
+
export declare function check(data: TmGraph, prefix?: string): string[];
|
|
16
|
+
/** コレクションの中からレコードを id で探す(無ければ null) */
|
|
17
|
+
export declare function findById<T extends TmRecord>(list: readonly T[] | undefined, id: TmId): T | null;
|
|
18
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// コレクションをまたぐ整合性の検査(id の重複・参照先の存在・TM の結線の規則)。
|
|
2
|
+
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
3
|
+
//
|
|
4
|
+
// iHub の server/validation/rules.mjs の checkTm を移したもの(文言も同じ)。
|
|
5
|
+
// それに加えて、d3.ter がデータ構築時に例外にしている規則(Rectum.assertClassLink / resolveMapping と
|
|
6
|
+
// Entity.entityTypeContents)も検査する。描く前に分かるほうがよい知識で、d3.ter 側の例外は残る。
|
|
7
|
+
//
|
|
8
|
+
// メッセージの先頭はコレクション名で、prefix を渡すと 'tm/entities: ...' のように付けられる。
|
|
9
|
+
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
|
+
// エンティティが持つ識別子のマスタの集合(CLASS の親判定に使う)
|
|
21
|
+
function identifierMasters(entity) {
|
|
22
|
+
return new Set((entity.identifiers || []).map(d => d.identifier));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 図全体を検査する。
|
|
26
|
+
* - identifiers / attributes / entities / relationships の id が、それぞれのコレクションの中で重複していない
|
|
27
|
+
* - エンティティのインスタンス(identifiers[] / attributes[] の行)の id が、全エンティティを通して重複していない
|
|
28
|
+
* - entities[].type が 10 種のどれか
|
|
29
|
+
* - entities[].identifiers[].identifier が identifiers に、attributes[].attribute が attributes にある
|
|
30
|
+
* - relationships[].from.entity / to.entity がエンティティにある
|
|
31
|
+
* - 端点の旧 id(あれば)がその端のエンティティの識別子に、details[].from.id / to.id が識別子・属性にある
|
|
32
|
+
* - mapping.entity がエンティティにあり、種別が対応表・対照表・再帰表のどれか。再帰表は自己結線にだけ
|
|
33
|
+
* - CLASS 同士は結線できず、CLASS は切り出し元(親 = クラスの識別子のマスタを持つ側)としか結線できない
|
|
34
|
+
*
|
|
35
|
+
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'tm/')
|
|
36
|
+
*/
|
|
37
|
+
export function check(data, prefix = '') {
|
|
38
|
+
const errors = [];
|
|
39
|
+
const identifiers = data.identifiers || [];
|
|
40
|
+
const attributes = data.attributes || [];
|
|
41
|
+
const entities = data.entities || [];
|
|
42
|
+
const relationships = data.relationships || [];
|
|
43
|
+
const identifier_ids = checkUnique(errors, `${prefix}identifiers`, identifiers);
|
|
44
|
+
const attribute_ids = checkUnique(errors, `${prefix}attributes`, attributes);
|
|
45
|
+
checkUnique(errors, `${prefix}entities`, entities);
|
|
46
|
+
checkUnique(errors, `${prefix}relationships`, relationships);
|
|
47
|
+
// インスタンスの id は、エンティティをまたいで一意にする(d3.ter はインスタンスを id だけで探すため)
|
|
48
|
+
checkUnique(errors, `${prefix}entities の identifiers・attributes`, entities.flatMap(e => [...(e.identifiers || []), ...(e.attributes || [])]));
|
|
49
|
+
for (const e of entities) {
|
|
50
|
+
if (!isEntityType(e.type))
|
|
51
|
+
errors.push(`${prefix}entities: type が種別のどれでもありません。(id=${String(e.id)}, type=${String(e.type)})`);
|
|
52
|
+
for (const d of e.identifiers || [])
|
|
53
|
+
if (!identifier_ids.has(d.identifier))
|
|
54
|
+
errors.push(`${prefix}entities: identifier が identifiers に見つかりません。(entity=${String(e.id)}, id=${String(d.id)}, identifier=${String(d.identifier)})`);
|
|
55
|
+
for (const d of e.attributes || [])
|
|
56
|
+
if (!attribute_ids.has(d.attribute))
|
|
57
|
+
errors.push(`${prefix}entities: attribute が attributes に見つかりません。(entity=${String(e.id)}, id=${String(d.id)}, attribute=${String(d.attribute)})`);
|
|
58
|
+
}
|
|
59
|
+
const index = new Map(entities.map(e => [e.id, e]));
|
|
60
|
+
for (const r of relationships) {
|
|
61
|
+
const ends = {};
|
|
62
|
+
for (const end of ['from', 'to']) {
|
|
63
|
+
const entity = index.get(r[end]?.entity);
|
|
64
|
+
if (!entity) {
|
|
65
|
+
errors.push(`${prefix}relationships: ${end}.entity のエンティティが見つかりません。(id=${String(r.id)}, entity=${String(r[end]?.entity)})`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
ends[end] = entity;
|
|
69
|
+
const identifiers_of = entity.identifiers || [];
|
|
70
|
+
const instances = [...identifiers_of, ...(entity.attributes || [])];
|
|
71
|
+
if (r[end].id !== undefined && !identifiers_of.some(d => d.id === r[end].id))
|
|
72
|
+
errors.push(`${prefix}relationships: ${end}.id がエンティティの識別子に見つかりません。(id=${String(r.id)}, entity=${String(entity.id)}, ${end}.id=${String(r[end].id)})`);
|
|
73
|
+
for (const detail of r.details || [])
|
|
74
|
+
if (!instances.some(d => d.id === detail[end]?.id))
|
|
75
|
+
errors.push(`${prefix}relationships: details の ${end}.id がエンティティの識別子・属性に見つかりません。(id=${String(r.id)}, entity=${String(entity.id)}, ${end}.id=${String(detail[end]?.id)})`);
|
|
76
|
+
}
|
|
77
|
+
if (r.mapping) {
|
|
78
|
+
const target = index.get(r.mapping.entity);
|
|
79
|
+
if (!target)
|
|
80
|
+
errors.push(`${prefix}relationships: mapping.entity のエンティティが見つかりません。(id=${String(r.id)}, entity=${String(r.mapping.entity)})`);
|
|
81
|
+
else if (!MAPPING_TYPES.includes(target.type))
|
|
82
|
+
errors.push(`${prefix}relationships: mapping に指定できるのは対応表 (CORRESPONDENCE)・対照表 (COMPARATIVE)・再帰表 (RECURSION) だけです。(id=${String(r.id)}, entity=${String(target.id)}, type=${String(target.type)})`);
|
|
83
|
+
else if (target.type === 'RECURSION' && r.from.entity !== r.to.entity)
|
|
84
|
+
errors.push(`${prefix}relationships: 再帰表 (RECURSION) は自己結線 (from と to が同じエンティティ) にだけ指定できます。(id=${String(r.id)}, from=${String(r.from.entity)}, to=${String(r.to.entity)})`);
|
|
85
|
+
}
|
|
86
|
+
// TM の「クラス」は切り出し元(親)としか結線できない(d3.ter の Rectum.assertClassLink と同じ判定)
|
|
87
|
+
const from = ends.from;
|
|
88
|
+
const to = ends.to;
|
|
89
|
+
if (from && to && (from.type === 'CLASS' || to.type === 'CLASS')) {
|
|
90
|
+
if (from.type === 'CLASS' && to.type === 'CLASS') {
|
|
91
|
+
errors.push(`${prefix}relationships: クラス (CLASS) 同士は結線できません。(id=${String(r.id)})`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const klass = from.type === 'CLASS' ? from : to;
|
|
95
|
+
const other = from.type === 'CLASS' ? to : from;
|
|
96
|
+
const masters = identifierMasters(klass);
|
|
97
|
+
if (!(other.identifiers || []).some(d => masters.has(d.identifier)))
|
|
98
|
+
errors.push(`${prefix}relationships: クラス (CLASS) は切り出し元 (親) としか結線できません。(id=${String(r.id)}, entity=${String(other.id)})`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return errors;
|
|
103
|
+
}
|
|
104
|
+
/** コレクションの中からレコードを id で探す(無ければ null) */
|
|
105
|
+
export function findById(list, id) {
|
|
106
|
+
return (list || []).find(d => d.id === id) || null;
|
|
107
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// コレクションをまたぐ整合性の検査(id の重複・参照先の存在・TM の結線の規則)。
|
|
3
|
+
// スキーマ(schema.ts)に合っているデータを渡す。戻り値はエラーのメッセージの配列(空なら問題なし)。
|
|
4
|
+
//
|
|
5
|
+
// iHub の server/validation/rules.mjs の checkTm を移したもの(文言も同じ)。
|
|
6
|
+
// それに加えて、d3.ter がデータ構築時に例外にしている規則(Rectum.assertClassLink / resolveMapping と
|
|
7
|
+
// Entity.entityTypeContents)も検査する。描く前に分かるほうがよい知識で、d3.ter 側の例外は残る。
|
|
8
|
+
//
|
|
9
|
+
// メッセージの先頭はコレクション名で、prefix を渡すと 'tm/entities: ...' のように付けられる。
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.check = check;
|
|
12
|
+
exports.findById = findById;
|
|
13
|
+
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
|
+
// エンティティが持つ識別子のマスタの集合(CLASS の親判定に使う)
|
|
25
|
+
function identifierMasters(entity) {
|
|
26
|
+
return new Set((entity.identifiers || []).map(d => d.identifier));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 図全体を検査する。
|
|
30
|
+
* - identifiers / attributes / entities / relationships の id が、それぞれのコレクションの中で重複していない
|
|
31
|
+
* - エンティティのインスタンス(identifiers[] / attributes[] の行)の id が、全エンティティを通して重複していない
|
|
32
|
+
* - entities[].type が 10 種のどれか
|
|
33
|
+
* - entities[].identifiers[].identifier が identifiers に、attributes[].attribute が attributes にある
|
|
34
|
+
* - relationships[].from.entity / to.entity がエンティティにある
|
|
35
|
+
* - 端点の旧 id(あれば)がその端のエンティティの識別子に、details[].from.id / to.id が識別子・属性にある
|
|
36
|
+
* - mapping.entity がエンティティにあり、種別が対応表・対照表・再帰表のどれか。再帰表は自己結線にだけ
|
|
37
|
+
* - CLASS 同士は結線できず、CLASS は切り出し元(親 = クラスの識別子のマスタを持つ側)としか結線できない
|
|
38
|
+
*
|
|
39
|
+
* @param prefix メッセージのコレクション名の前に付ける文字列(例: 'tm/')
|
|
40
|
+
*/
|
|
41
|
+
function check(data, prefix = '') {
|
|
42
|
+
const errors = [];
|
|
43
|
+
const identifiers = data.identifiers || [];
|
|
44
|
+
const attributes = data.attributes || [];
|
|
45
|
+
const entities = data.entities || [];
|
|
46
|
+
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);
|
|
51
|
+
// インスタンスの id は、エンティティをまたいで一意にする(d3.ter はインスタンスを id だけで探すため)
|
|
52
|
+
checkUnique(errors, `${prefix}entities の identifiers・attributes`, entities.flatMap(e => [...(e.identifiers || []), ...(e.attributes || [])]));
|
|
53
|
+
for (const e of entities) {
|
|
54
|
+
if (!(0, defaults_js_1.isEntityType)(e.type))
|
|
55
|
+
errors.push(`${prefix}entities: type が種別のどれでもありません。(id=${String(e.id)}, type=${String(e.type)})`);
|
|
56
|
+
for (const d of e.identifiers || [])
|
|
57
|
+
if (!identifier_ids.has(d.identifier))
|
|
58
|
+
errors.push(`${prefix}entities: identifier が identifiers に見つかりません。(entity=${String(e.id)}, id=${String(d.id)}, identifier=${String(d.identifier)})`);
|
|
59
|
+
for (const d of e.attributes || [])
|
|
60
|
+
if (!attribute_ids.has(d.attribute))
|
|
61
|
+
errors.push(`${prefix}entities: attribute が attributes に見つかりません。(entity=${String(e.id)}, id=${String(d.id)}, attribute=${String(d.attribute)})`);
|
|
62
|
+
}
|
|
63
|
+
const index = new Map(entities.map(e => [e.id, e]));
|
|
64
|
+
for (const r of relationships) {
|
|
65
|
+
const ends = {};
|
|
66
|
+
for (const end of ['from', 'to']) {
|
|
67
|
+
const entity = index.get(r[end]?.entity);
|
|
68
|
+
if (!entity) {
|
|
69
|
+
errors.push(`${prefix}relationships: ${end}.entity のエンティティが見つかりません。(id=${String(r.id)}, entity=${String(r[end]?.entity)})`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
ends[end] = entity;
|
|
73
|
+
const identifiers_of = entity.identifiers || [];
|
|
74
|
+
const instances = [...identifiers_of, ...(entity.attributes || [])];
|
|
75
|
+
if (r[end].id !== undefined && !identifiers_of.some(d => d.id === r[end].id))
|
|
76
|
+
errors.push(`${prefix}relationships: ${end}.id がエンティティの識別子に見つかりません。(id=${String(r.id)}, entity=${String(entity.id)}, ${end}.id=${String(r[end].id)})`);
|
|
77
|
+
for (const detail of r.details || [])
|
|
78
|
+
if (!instances.some(d => d.id === detail[end]?.id))
|
|
79
|
+
errors.push(`${prefix}relationships: details の ${end}.id がエンティティの識別子・属性に見つかりません。(id=${String(r.id)}, entity=${String(entity.id)}, ${end}.id=${String(detail[end]?.id)})`);
|
|
80
|
+
}
|
|
81
|
+
if (r.mapping) {
|
|
82
|
+
const target = index.get(r.mapping.entity);
|
|
83
|
+
if (!target)
|
|
84
|
+
errors.push(`${prefix}relationships: mapping.entity のエンティティが見つかりません。(id=${String(r.id)}, entity=${String(r.mapping.entity)})`);
|
|
85
|
+
else if (!defaults_js_1.MAPPING_TYPES.includes(target.type))
|
|
86
|
+
errors.push(`${prefix}relationships: mapping に指定できるのは対応表 (CORRESPONDENCE)・対照表 (COMPARATIVE)・再帰表 (RECURSION) だけです。(id=${String(r.id)}, entity=${String(target.id)}, type=${String(target.type)})`);
|
|
87
|
+
else if (target.type === 'RECURSION' && r.from.entity !== r.to.entity)
|
|
88
|
+
errors.push(`${prefix}relationships: 再帰表 (RECURSION) は自己結線 (from と to が同じエンティティ) にだけ指定できます。(id=${String(r.id)}, from=${String(r.from.entity)}, to=${String(r.to.entity)})`);
|
|
89
|
+
}
|
|
90
|
+
// TM の「クラス」は切り出し元(親)としか結線できない(d3.ter の Rectum.assertClassLink と同じ判定)
|
|
91
|
+
const from = ends.from;
|
|
92
|
+
const to = ends.to;
|
|
93
|
+
if (from && to && (from.type === 'CLASS' || to.type === 'CLASS')) {
|
|
94
|
+
if (from.type === 'CLASS' && to.type === 'CLASS') {
|
|
95
|
+
errors.push(`${prefix}relationships: クラス (CLASS) 同士は結線できません。(id=${String(r.id)})`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const klass = from.type === 'CLASS' ? from : to;
|
|
99
|
+
const other = from.type === 'CLASS' ? to : from;
|
|
100
|
+
const masters = identifierMasters(klass);
|
|
101
|
+
if (!(other.identifiers || []).some(d => masters.has(d.identifier)))
|
|
102
|
+
errors.push(`${prefix}relationships: クラス (CLASS) は切り出し元 (親) としか結線できません。(id=${String(r.id)}, entity=${String(other.id)})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return errors;
|
|
107
|
+
}
|
|
108
|
+
/** コレクションの中からレコードを id で探す(無ければ null) */
|
|
109
|
+
function findById(list, id) {
|
|
110
|
+
return (list || []).find(d => d.id === id) || null;
|
|
111
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// 「書いていない項目」に view(d3.ter)が既定で使う値と、レコードの種類の判別。
|
|
3
|
+
//
|
|
4
|
+
// 値の出どころ: d3.ter の src/js/Entity.js(entityTypeContents = 種別バッジ、entityBackground = 配色)、
|
|
5
|
+
// Port.js(degree() の既定 0)。d3.ter はこのファイルを読む(#144)ので、ここが正。
|
|
6
|
+
// cardinality / optionality に既定は置かない(d3.ter は無ければ記号を描かない)。
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
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;
|
|
9
|
+
exports.isEntityType = isEntityType;
|
|
10
|
+
exports.isRelationship = isRelationship;
|
|
11
|
+
exports.isEntity = isEntity;
|
|
12
|
+
exports.kindOf = kindOf;
|
|
13
|
+
exports.nameOf = nameOf;
|
|
14
|
+
exports.endWithDefaults = endWithDefaults;
|
|
15
|
+
exports.relationshipWithDefaults = relationshipWithDefaults;
|
|
16
|
+
exports.entityWithDefaults = entityWithDefaults;
|
|
17
|
+
exports.withDefaults = withDefaults;
|
|
18
|
+
/** エンティティの種別(10 種) */
|
|
19
|
+
exports.ENTITY_TYPES = [
|
|
20
|
+
'RESOURCE', 'RESOURCE-SUBSET', 'EVENT', 'EVENT-SUBSET',
|
|
21
|
+
'COMPARATIVE', 'CORRESPONDENCE', 'RECURSION', 'MANY-VALUED-OR', 'MANY-VALUED-AND', 'CLASS',
|
|
22
|
+
];
|
|
23
|
+
/** 種別バッジ(箱のヘッダ右に描く文字。CLASS は意図的に無し = '') */
|
|
24
|
+
exports.TYPE_BADGES = {
|
|
25
|
+
'RESOURCE': 'Rsc',
|
|
26
|
+
'RESOURCE-SUBSET': 'Rsc',
|
|
27
|
+
'EVENT': 'Evt',
|
|
28
|
+
'EVENT-SUBSET': 'Evt',
|
|
29
|
+
'COMPARATIVE': '対照',
|
|
30
|
+
'CORRESPONDENCE': '対応',
|
|
31
|
+
'RECURSION': '再帰',
|
|
32
|
+
'MANY-VALUED-OR': 'MO',
|
|
33
|
+
'MANY-VALUED-AND': 'MA',
|
|
34
|
+
'CLASS': '',
|
|
35
|
+
};
|
|
36
|
+
/** 種別ごとの箱の背景色 */
|
|
37
|
+
exports.TYPE_COLORS = {
|
|
38
|
+
'RESOURCE': '#89c3eb',
|
|
39
|
+
'RESOURCE-SUBSET': '#a0d8ef',
|
|
40
|
+
'EVENT': '#f09199',
|
|
41
|
+
'EVENT-SUBSET': '#f6bfbc',
|
|
42
|
+
'COMPARATIVE': '#c8d5bb',
|
|
43
|
+
'CORRESPONDENCE': '#f2f2b0',
|
|
44
|
+
'RECURSION': '#dbd0e6',
|
|
45
|
+
'MANY-VALUED-OR': '#fddea5',
|
|
46
|
+
'MANY-VALUED-AND': '#9fd5c9',
|
|
47
|
+
'CLASS': '#bdc0ba',
|
|
48
|
+
};
|
|
49
|
+
/** 結線の中点から垂下(mapping)できる種別 */
|
|
50
|
+
exports.MAPPING_TYPES = ['CORRESPONDENCE', 'COMPARATIVE', 'RECURSION'];
|
|
51
|
+
/** サブセット結線の区分コードの種類 */
|
|
52
|
+
exports.SUBSET_KINDS = ['same', 'different'];
|
|
53
|
+
/** カーディナリティ(1 か 多) */
|
|
54
|
+
exports.CARDINALITIES = [1, 3];
|
|
55
|
+
/** オプショナリティ(0 は任意、1 は必須) */
|
|
56
|
+
exports.OPTIONALITIES = [0, 1];
|
|
57
|
+
/** エンティティの既定。size の 0 は「内容に合わせる」 */
|
|
58
|
+
exports.ENTITY_DEFAULTS = {
|
|
59
|
+
position: { x: 0, y: 0 },
|
|
60
|
+
size: { w: 0, h: 0 },
|
|
61
|
+
};
|
|
62
|
+
/** 関係の既定。端点の角度は 0(真下)。cardinality / optionality は既定なし(無ければ記号を描かない) */
|
|
63
|
+
exports.RELATIONSHIP_DEFAULTS = {
|
|
64
|
+
end: { position: 0 },
|
|
65
|
+
};
|
|
66
|
+
/** 種別として正しいか */
|
|
67
|
+
function isEntityType(value) {
|
|
68
|
+
return typeof value === 'string' && exports.ENTITY_TYPES.includes(value);
|
|
69
|
+
}
|
|
70
|
+
/** 関係か(from と to がエンティティを指す) */
|
|
71
|
+
function isRelationship(record) {
|
|
72
|
+
const r = record;
|
|
73
|
+
return !!(r && r.from && typeof r.from === 'object' && 'entity' in r.from
|
|
74
|
+
&& r.to && typeof r.to === 'object' && 'entity' in r.to);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* エンティティか。関係でなく、type・インスタンスの配列・位置・大きさのどれかを持つ。
|
|
78
|
+
* マスタ(id と name だけ)と区別するための判定なので、id と name しか無いエンティティは見分けられない
|
|
79
|
+
* (entities[] の要素と分かっているときは entityWithDefaults を使う)。
|
|
80
|
+
*/
|
|
81
|
+
function isEntity(record) {
|
|
82
|
+
if (!record || isRelationship(record))
|
|
83
|
+
return false;
|
|
84
|
+
const e = record;
|
|
85
|
+
return typeof e.type === 'string' || Array.isArray(e.identifiers) || Array.isArray(e.attributes)
|
|
86
|
+
|| e.position !== undefined || e.size !== undefined;
|
|
87
|
+
}
|
|
88
|
+
/** レコードの種類 */
|
|
89
|
+
function kindOf(record) {
|
|
90
|
+
if (isRelationship(record))
|
|
91
|
+
return 'relationship';
|
|
92
|
+
return isEntity(record) ? 'entity' : 'master';
|
|
93
|
+
}
|
|
94
|
+
/** 表示する名前。文字列ならそのまま、Name なら logical(無ければ physical)。無ければ '' */
|
|
95
|
+
function nameOf(record) {
|
|
96
|
+
const name = record.name;
|
|
97
|
+
if (typeof name === 'string')
|
|
98
|
+
return name;
|
|
99
|
+
if (name && typeof name === 'object')
|
|
100
|
+
return name.logical || name.physical || '';
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
/** 端点に既定を重ねる(角度が無ければ 0。cardinality / optionality は不正値なら除く) */
|
|
104
|
+
function endWithDefaults(end) {
|
|
105
|
+
const out = {
|
|
106
|
+
...end,
|
|
107
|
+
position: typeof end.position === 'number' && Number.isFinite(end.position) ? end.position : exports.RELATIONSHIP_DEFAULTS.end.position,
|
|
108
|
+
};
|
|
109
|
+
if (!exports.CARDINALITIES.includes(end.cardinality))
|
|
110
|
+
delete out.cardinality;
|
|
111
|
+
if (!exports.OPTIONALITIES.includes(end.optionality))
|
|
112
|
+
delete out.optionality;
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
/** 関係に既定を重ねる: from / to の position */
|
|
116
|
+
function relationshipWithDefaults(record) {
|
|
117
|
+
return {
|
|
118
|
+
...record,
|
|
119
|
+
from: endWithDefaults(record.from),
|
|
120
|
+
to: endWithDefaults(record.to),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* エンティティに既定を重ねる: position(無ければ 0, 0)、size(無ければ 0, 0 = 内容に合わせる)。
|
|
125
|
+
* type は補わない(無ければ無いまま。d3.ter は描けないので check で知らせる)。
|
|
126
|
+
*/
|
|
127
|
+
function entityWithDefaults(record) {
|
|
128
|
+
return {
|
|
129
|
+
...record,
|
|
130
|
+
position: { ...exports.ENTITY_DEFAULTS.position, ...(record.position || {}) },
|
|
131
|
+
size: { ...exports.ENTITY_DEFAULTS.size, ...(record.size || {}) },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* レコードに、view が既定で使う値を重ねて返す(入力は書き換えない)。インスペクタの初期値に使う。
|
|
136
|
+
* 形から種類を決める(kindOf)。マスタはそのまま(複製だけ)。
|
|
137
|
+
* name は文字列か Name かを保つ(どちらで書いたかで編集できる項目が変わる — fieldsOf)。
|
|
138
|
+
*/
|
|
139
|
+
function withDefaults(record) {
|
|
140
|
+
if (isRelationship(record))
|
|
141
|
+
return relationshipWithDefaults(record);
|
|
142
|
+
if (isEntity(record))
|
|
143
|
+
return entityWithDefaults(record);
|
|
144
|
+
return { ...record };
|
|
145
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// 編集できる項目の一覧。インスペクタ(colonoscope の ModelInspector)に渡す。
|
|
3
|
+
//
|
|
4
|
+
// path はレコードの実際の項目を指す。既定値は withDefaults() で重ねてから渡すので、
|
|
5
|
+
// 書いていない項目(関係の角度など)も、図に描かれている値が初期値として出る。
|
|
6
|
+
// type: 'readonly' は表示だけ。options は取りうる値の一覧(検証にも使う。文字列で持つ)。
|
|
7
|
+
//
|
|
8
|
+
// 名前は、レコードが文字列で持っていれば name、{ logical, physical } で持っていれば
|
|
9
|
+
// name.logical / name.physical を出す(どちらで書いたかを変えない)。
|
|
10
|
+
// エンティティのインスタンス(識別子・属性の行)は entities[] の入れ子で、JSON Merge Patch では 1 要素を指せないので
|
|
11
|
+
// 項目にしない(行を右クリックしたときはマスタを開く)。
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.MASTER_FIELDS = exports.RELATIONSHIP_FIELDS = exports.ENTITY_FIELDS = void 0;
|
|
14
|
+
exports.nameFields = nameFields;
|
|
15
|
+
exports.fieldsOf = fieldsOf;
|
|
16
|
+
const defaults_js_1 = require("./defaults.js");
|
|
17
|
+
const CARDINALITY_OPTIONS = defaults_js_1.CARDINALITIES.map(String);
|
|
18
|
+
const OPTIONALITY_OPTIONS = defaults_js_1.OPTIONALITIES.map(String);
|
|
19
|
+
/** 名前の項目。name が Name(オブジェクト)なら論理名・物理名の 2 項目、それ以外は名称 1 項目 */
|
|
20
|
+
function nameFields(record) {
|
|
21
|
+
const name = record.name;
|
|
22
|
+
if (name && typeof name === 'object')
|
|
23
|
+
return [
|
|
24
|
+
{ path: 'name.logical', label: '論理名', type: 'text' },
|
|
25
|
+
{ path: 'name.physical', label: '物理名', type: 'text' },
|
|
26
|
+
];
|
|
27
|
+
return [{ path: 'name', label: '名称', type: 'text' }];
|
|
28
|
+
}
|
|
29
|
+
/** エンティティの項目(名前のあと) */
|
|
30
|
+
exports.ENTITY_FIELDS = [
|
|
31
|
+
{ path: 'type', label: '種別', type: 'text', options: defaults_js_1.ENTITY_TYPES },
|
|
32
|
+
{ path: 'id', label: 'ID', type: 'readonly' },
|
|
33
|
+
{ path: 'description', label: '説明', type: 'textarea' },
|
|
34
|
+
{ path: 'size.w', label: '幅 (0 で自動)', type: 'number' },
|
|
35
|
+
{ path: 'size.h', label: '高さ (0 で自動)', type: 'number' },
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* 関係の項目。接続位置はエンティティの中心から見た角度(0=真下・時計回り。90=左 / 180=真上 / 270=右)。
|
|
39
|
+
* カーディナリティ・オプショナリティは空にできる(記号を描かない)。
|
|
40
|
+
*/
|
|
41
|
+
exports.RELATIONSHIP_FIELDS = [
|
|
42
|
+
{ path: 'id', label: 'ID', type: 'readonly' },
|
|
43
|
+
{ path: 'type', label: '種類', type: 'text' },
|
|
44
|
+
{ path: 'label', label: 'ラベル', type: 'text' },
|
|
45
|
+
{ path: 'from.entity', label: '接続元のエンティティ', type: 'readonly' },
|
|
46
|
+
{ path: 'from.position', label: '接続元の位置 (角度)', type: 'number' },
|
|
47
|
+
{ path: 'from.cardinality', label: '接続元のカーディナリティ (空で無し)', type: 'text', options: CARDINALITY_OPTIONS },
|
|
48
|
+
{ path: 'from.optionality', label: '接続元のオプショナリティ (空で無し)', type: 'text', options: OPTIONALITY_OPTIONS },
|
|
49
|
+
{ path: 'to.entity', label: '接続先のエンティティ', type: 'readonly' },
|
|
50
|
+
{ path: 'to.position', label: '接続先の位置 (角度)', type: 'number' },
|
|
51
|
+
{ path: 'to.cardinality', label: '接続先のカーディナリティ (空で無し)', type: 'text', options: CARDINALITY_OPTIONS },
|
|
52
|
+
{ path: 'to.optionality', label: '接続先のオプショナリティ (空で無し)', type: 'text', options: OPTIONALITY_OPTIONS },
|
|
53
|
+
{ path: 'subset.kind', label: 'サブセットの区分 (same は =、different は ×)', type: 'text', options: defaults_js_1.SUBSET_KINDS },
|
|
54
|
+
{ path: 'subset.code', label: 'サブセットの区分コード', type: 'text' },
|
|
55
|
+
{ path: 'mapping.entity', label: '垂下する表 (エンティティ)', type: 'readonly' },
|
|
56
|
+
{ path: 'description', label: '説明', type: 'textarea' },
|
|
57
|
+
];
|
|
58
|
+
/** マスタ(識別子・属性)の項目(名前のあと) */
|
|
59
|
+
exports.MASTER_FIELDS = [
|
|
60
|
+
{ path: 'description', label: '説明', type: 'textarea' },
|
|
61
|
+
{ path: 'id', label: 'ID', type: 'readonly' },
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* レコードに応じた項目の一覧。
|
|
65
|
+
* マスタは identifiers / attributes のどちらかをレコードの形から判別できないので、利用側が collection を渡す
|
|
66
|
+
* (項目は同じなので、渡さなくても一覧は変わらない。applyPatch では必須)。
|
|
67
|
+
*/
|
|
68
|
+
function fieldsOf(record, _collection) {
|
|
69
|
+
switch ((0, defaults_js_1.kindOf)(record)) {
|
|
70
|
+
case 'relationship':
|
|
71
|
+
return [...exports.RELATIONSHIP_FIELDS];
|
|
72
|
+
case 'entity':
|
|
73
|
+
return [...nameFields(record), ...exports.ENTITY_FIELDS];
|
|
74
|
+
default:
|
|
75
|
+
return [...nameFields(record), ...exports.MASTER_FIELDS];
|
|
76
|
+
}
|
|
77
|
+
}
|