@g1cloud/entity-modeler-next 5.0.0-alpha.1 → 5.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -28
- package/dist/agent/resolver.d.ts +69 -0
- package/dist/agent/symbolicOp.d.ts +131 -4
- package/dist/command/commands.d.ts +4 -0
- package/dist/core/autolayout.d.ts +45 -0
- package/dist/core/migrateLegacyMoney.d.ts +8 -0
- package/dist/core/projectActionLog.d.ts +41 -0
- package/dist/core/projectEntityAudit.d.ts +22 -0
- package/dist/core/projectFieldAudit.d.ts +65 -0
- package/dist/core/synthesizeRestoreOps.d.ts +21 -0
- package/dist/editor/controller.d.ts +52 -0
- package/dist/entity-modeler-next.css +1 -1
- package/dist/entity-modeler.js +11687 -10184
- package/dist/entity-modeler.umd.cjs +15 -15
- package/dist/index.d.ts +6 -1
- package/dist/view/EditorToolbar.vue.d.ts +3 -0
- package/dist/view/EntityBasicsSection.vue.d.ts +8 -1
- package/dist/view/EntityIndexSection.vue.d.ts +7 -1
- package/dist/view/EntityOperationsSection.vue.d.ts +7 -1
- package/dist/view/HistoryPanel.vue.d.ts +11 -0
- package/dist/view/PropertyPanel.vue.d.ts +8 -1
- package/dist/view/ValidationPanel.vue.d.ts +5 -1
- package/dist/view/useResizableWidth.d.ts +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,42 +1,47 @@
|
|
|
1
1
|
# entity-modeler-next
|
|
2
2
|
|
|
3
|
-
Vue 3
|
|
4
|
-
|
|
3
|
+
Vue 3 visual entity-modeling / class-diagram editor.
|
|
4
|
+
Distributed as a library package (`@g1cloud/entity-modeler-next`) and embedded in `bluework4-tool` (Nuxt/Vue/Mongo).
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## Stack
|
|
7
7
|
|
|
8
|
-
TypeScript · Vue 3 · **Vue Flow** · Pinia(
|
|
8
|
+
TypeScript · Vue 3 · **Vue Flow** · Pinia (opt-in) · Vite (library mode) · Vitest · views built on `@g1cloud/open-bluesea-core` (BS) components
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add @g1cloud/entity-modeler-next
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires the peer dependencies `vue ^3.5.0` and `@g1cloud/open-bluesea-core` (installed by the host).
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { EntityModeler } from '@g1cloud/entity-modeler-next'
|
|
22
|
+
import '@g1cloud/entity-modeler-next/style.css' // required — components render unstyled without it
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Structure
|
|
11
26
|
|
|
12
27
|
```
|
|
13
28
|
src/
|
|
14
|
-
core/
|
|
15
|
-
command/ Command(do/undo)·CommandStack·op/opSync(
|
|
16
|
-
editor/ reactive controller (
|
|
17
|
-
view/ Vue Flow
|
|
18
|
-
adapter/
|
|
19
|
-
agent/
|
|
20
|
-
dev/
|
|
29
|
+
core/ logical/layout types (separated) · resolve (consistency) · routing · autolayout · propagation · validation · type catalogs. Framework-agnostic
|
|
30
|
+
command/ Command (do/undo) · CommandStack · op/opSync (semantic op emit + CAS concurrency)
|
|
31
|
+
editor/ reactive controller (composable wrapping CommandStack, EDITOR inject key)
|
|
32
|
+
view/ Vue Flow components (DiagramCanvas · EntityNode · GroupNode · AssociationEdge · PropertyPanel · ValidationPanel, etc.)
|
|
33
|
+
adapter/ storage-schema mapping (persisted v1/v2 · fromPersisted/toPersisted/toPersistedV2 round-trip)
|
|
34
|
+
agent/ natural-language → op track (symbolicOp · resolver · schema · buildAgentBatch)
|
|
35
|
+
dev/ demo harness (excluded from the library build)
|
|
21
36
|
```
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
**현재 구조·상태·문서 인덱스의 정본은 루트 `CLAUDE.md`** (저장 스키마 영향은 `docs/M0-schema-impact.md`).
|
|
38
|
+
Core design: **layout references the logical model by `modelId` (one-way)**. The logical model carries no geo/waypoint/style.
|
|
25
39
|
|
|
26
|
-
##
|
|
40
|
+
## Scripts
|
|
27
41
|
|
|
28
42
|
```bash
|
|
29
|
-
pnpm dev #
|
|
30
|
-
pnpm test # Vitest
|
|
31
|
-
pnpm typecheck # vue-tsc
|
|
32
|
-
pnpm build #
|
|
43
|
+
pnpm dev # demo harness dev server (src/dev)
|
|
44
|
+
pnpm test # Vitest
|
|
45
|
+
pnpm typecheck # vue-tsc --noEmit
|
|
46
|
+
pnpm build # library build (dist/)
|
|
33
47
|
```
|
|
34
|
-
|
|
35
|
-
## M0 스파이크 검증 포인트 (브라우저)
|
|
36
|
-
|
|
37
|
-
`pnpm dev` 후 다음을 확인:
|
|
38
|
-
- 엔티티 노드가 속성 행·PK 마커와 함께 렌더되는가
|
|
39
|
-
- 관계선이 **직각 라우팅**으로 그려지는가
|
|
40
|
-
- 파란 **waypoint**를 드래그하면 경로가 갱신되는가
|
|
41
|
-
- 세그먼트 중점(회색 원)을 **더블클릭**하면 waypoint가 추가되는가
|
|
42
|
-
- 관계선 끝이 특정 **컬럼 행(앵커)**에 붙는가 (Order.id ↔ OrderLine.orderId)
|
package/dist/agent/resolver.d.ts
CHANGED
|
@@ -35,6 +35,75 @@ export type ResolveError = {
|
|
|
35
35
|
code: 'self-identifying';
|
|
36
36
|
opIndex: number;
|
|
37
37
|
handle: AssocHandle;
|
|
38
|
+
}
|
|
39
|
+
/** `type:'GroupCodeEnum'` 인데 `groupCode` 미동반 — groupCode 바인딩 없는 깨진 파생 타입 방지(B3). */
|
|
40
|
+
| {
|
|
41
|
+
code: 'groupcode-required';
|
|
42
|
+
opIndex: number;
|
|
43
|
+
entity: Handle;
|
|
44
|
+
handle: Handle;
|
|
45
|
+
}
|
|
46
|
+
/** index.add 컬럼 핸들이 엔티티 내 dbAttr와 0개 매칭. */
|
|
47
|
+
| {
|
|
48
|
+
code: 'column-not-found';
|
|
49
|
+
opIndex: number;
|
|
50
|
+
entity: Handle;
|
|
51
|
+
handle: Handle;
|
|
52
|
+
}
|
|
53
|
+
/** index.add 컬럼 핸들이 복수 dbAttr와 매칭(예: 속성명 지정인데 다중 컬럼 임베드/Money). */
|
|
54
|
+
| {
|
|
55
|
+
code: 'column-ambiguous';
|
|
56
|
+
opIndex: number;
|
|
57
|
+
entity: Handle;
|
|
58
|
+
handle: Handle;
|
|
59
|
+
matches: ModelId[];
|
|
60
|
+
}
|
|
61
|
+
/** index.update/remove 핸들이 엔티티 내 인덱스와 0개/복수 매칭. */
|
|
62
|
+
| {
|
|
63
|
+
code: 'index-not-found';
|
|
64
|
+
opIndex: number;
|
|
65
|
+
entity: Handle;
|
|
66
|
+
handle: Handle;
|
|
67
|
+
} | {
|
|
68
|
+
code: 'index-ambiguous';
|
|
69
|
+
opIndex: number;
|
|
70
|
+
entity: Handle;
|
|
71
|
+
handle: Handle;
|
|
72
|
+
matches: ModelId[];
|
|
73
|
+
}
|
|
74
|
+
/** operation.update/remove 핸들이 엔티티 내 operation과 0개/복수 매칭. */
|
|
75
|
+
| {
|
|
76
|
+
code: 'operation-not-found';
|
|
77
|
+
opIndex: number;
|
|
78
|
+
entity: Handle;
|
|
79
|
+
handle: Handle;
|
|
80
|
+
} | {
|
|
81
|
+
code: 'operation-ambiguous';
|
|
82
|
+
opIndex: number;
|
|
83
|
+
entity: Handle;
|
|
84
|
+
handle: Handle;
|
|
85
|
+
matches: ModelId[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* index.update patch에 `columns` 키 — columnRef(dbAttr modelId)는 사람 핸들로 표현 불가라 패스스루 시
|
|
89
|
+
* 깨진 인덱스가 된다. 컬럼 변경은 index.remove + index.add(컬럼 핸들 해소 구현)로 유도(거부 권고안).
|
|
90
|
+
*/
|
|
91
|
+
| {
|
|
92
|
+
code: 'index-columns-patch-unsupported';
|
|
93
|
+
opIndex: number;
|
|
94
|
+
entity: Handle;
|
|
95
|
+
handle: Handle;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 같은 배치에서 `entity.add`로 추가되는 엔티티를 자식/관계 op가 핸들로 참조(A 백스톱 가드).
|
|
99
|
+
* mongo arrayFilter는 update 선이미지에 평가되므로 갓 push된 엔티티에 닿는 child push가 silent no-op이 된다.
|
|
100
|
+
* 신규 엔티티의 자식은 `attribute.add`/`index.add`/`operation.add` 분리가 아니라 `entity.add` spec에 inline fold할 것
|
|
101
|
+
* (관계는 inline 불가 → 엔티티 배치 확정 후 별도 배치). entity-not-found 대신 의도를 드러내는 명시 에러.
|
|
102
|
+
*/
|
|
103
|
+
| {
|
|
104
|
+
code: 'pending-entity-ref';
|
|
105
|
+
opIndex: number;
|
|
106
|
+
handle: Handle;
|
|
38
107
|
};
|
|
39
108
|
export type ResolveResult = {
|
|
40
109
|
ok: true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClassStereotype, Multiplicity } from '../core/types';
|
|
1
|
+
import { ClassStereotype, MultiLangText, Multiplicity, OperationVisibility } from '../core/types';
|
|
2
2
|
/**
|
|
3
3
|
* 요소 지정 핸들 — modelId 대신 이름/물리명.
|
|
4
4
|
* 엔티티: 물리명(`table.physicalName`) 우선, 논리명(`name`) 보조 — 논리명은 모델 내 중복 허용(CHK-NAME-3)이라 모호 가능.
|
|
@@ -19,18 +19,36 @@ export interface AssocHandle {
|
|
|
19
19
|
from: Handle;
|
|
20
20
|
to: Handle;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* entity.add 페이로드 — resolver가 modelId 발급 + 골격 조립.
|
|
24
|
+
*
|
|
25
|
+
* **inline fold**: 신규 엔티티의 자식(속성·인덱스·operation)은 별도 `attribute.add` 등으로 보내지 않고
|
|
26
|
+
* 여기에 inline 배열로 담는다. resolver가 완성형 엔티티를 한 `entity.add`로 조립해 단일 `$push`로 들어간다.
|
|
27
|
+
* (별도 child op는 mongo arrayFilter 선이미지 한계로 *같은 배치 신규 엔티티*에 닿지 못해 silent no-op이 된다 —
|
|
28
|
+
* 그래서 신규 엔티티 자식은 op 분리가 아니라 inline이 정공법. 분리 emit은 resolver가 `pending-entity-ref`로 거부.)
|
|
29
|
+
* `attribute.add`/`index.add`/`operation.add` op은 *기존* 엔티티에 자식을 추가하는 용도로 남는다.
|
|
30
|
+
*/
|
|
23
31
|
export interface EntitySpec {
|
|
24
32
|
name: string;
|
|
25
33
|
/** 기본 'JPA_ENTITY'. */
|
|
26
34
|
stereotype?: ClassStereotype;
|
|
27
35
|
/** 설정 시 `table.physicalName`. */
|
|
28
36
|
physicalName?: string;
|
|
37
|
+
/** inline 속성 — 배열 순서가 곧 `order`. GroupCodeEnum+groupCode 규칙은 `attribute.add`와 동일하게 강제. */
|
|
38
|
+
attributes?: AttrSpec[];
|
|
39
|
+
/** inline 인덱스 — 컬럼 핸들은 *이 엔티티가 inline으로 만드는* dbAttr 내에서 해소된다(속성 inline 동반 전제). */
|
|
40
|
+
indexes?: IndexSpec[];
|
|
41
|
+
/** inline 도메인 메서드 — 배열 순서가 곧 `order`. */
|
|
42
|
+
operations?: OperationSpec[];
|
|
29
43
|
}
|
|
30
44
|
/** attribute.add 페이로드 — resolver가 modelId 발급 + dbAttr 골격 조립. */
|
|
31
45
|
export interface AttrSpec {
|
|
32
46
|
name: string;
|
|
33
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Java 타입(예: 'Long', 'String'). 카탈로그 저장값.
|
|
49
|
+
* `'GroupCodeEnum'`은 파생 타입이라 **`groupCode` 동반 필수** — 없으면 resolver가 거부(groupCode 바인딩
|
|
50
|
+
* 없는 깨진 파생 타입 방지). groupCode 지정 시 코드성 속성으로 해소된다.
|
|
51
|
+
*/
|
|
34
52
|
type: string;
|
|
35
53
|
identifier?: boolean;
|
|
36
54
|
notNull?: boolean;
|
|
@@ -42,6 +60,26 @@ export interface AttrSpec {
|
|
|
42
60
|
length?: number;
|
|
43
61
|
/** dbAttr 소수 자릿수(DECIMAL/NUMERIC scale). length 동반이 일반적. 0/미설정은 미지정. */
|
|
44
62
|
scale?: number;
|
|
63
|
+
/**
|
|
64
|
+
* 코드성 속성의 group code 바인딩(레거시 AttributeModel.groupCode). `type:'GroupCodeEnum'`이면 필수.
|
|
65
|
+
* 모델→모델 복사·코드 동기화에서 GroupCodeEnum 속성을 온전히 재현하는 핵심 필드(없으면 깨진 파생 타입).
|
|
66
|
+
*/
|
|
67
|
+
groupCode?: string;
|
|
68
|
+
/** 속성 기본값(레거시 AttributeModel.defaultValue, 문자열 패스스루). */
|
|
69
|
+
defaultValue?: string;
|
|
70
|
+
/** 속성 분류 그룹(레거시 attributeGroup, 예: 'code'·'amt'). 표시·분류 전용 패스스루. */
|
|
71
|
+
attributeGroup?: string;
|
|
72
|
+
/** 속성 설명(다국어, 레거시 AttributeModel.description). */
|
|
73
|
+
description?: MultiLangText;
|
|
74
|
+
/**
|
|
75
|
+
* dbAttr 컬럼 논리명(다국어, 예: {ko:'체크아웃유형'}). 한글 업무명이 모델→모델 복사에서 보존되는 위치 —
|
|
76
|
+
* 속성 자체는 단일 식별자 `name`만 갖고, 다국어 업무명은 컬럼에 붙는다. 컬럼 생성 시에만 의미.
|
|
77
|
+
*/
|
|
78
|
+
columnLogicalName?: MultiLangText;
|
|
79
|
+
/** dbAttr UNIQUE 제약. */
|
|
80
|
+
unique?: boolean;
|
|
81
|
+
/** dbAttr 갱신 가능 여부(JPA @Column(updatable=)). */
|
|
82
|
+
updatable?: boolean;
|
|
45
83
|
}
|
|
46
84
|
/** association.add 페이로드 — from/to 엔티티는 resolver가 해소, modelId 발급 + end 조립. */
|
|
47
85
|
export interface AssocSpec {
|
|
@@ -58,6 +96,66 @@ export interface AssocSpec {
|
|
|
58
96
|
/** end2 composition(부모가 자식 생명주기 소유). */
|
|
59
97
|
composition?: boolean;
|
|
60
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* group.add 페이로드 — 엔티티 멤버를 묶는 논리 그룹(레거시 LogicalGroup). resolver가 modelId 발급 +
|
|
101
|
+
* 멤버 핸들을 entity modelId(`memberEntityRefs`)로 해소. 그룹 멤버십은 값 포함이 아닌 **id 참조**(엔티티는
|
|
102
|
+
* 그룹과 독립 존재). 멤버는 *기존* 엔티티여야 한다 — 같은 배치 신규 엔티티(entity.add)는 닿지 못하므로
|
|
103
|
+
* (top-level 그룹은 inline fold 불가) resolver가 `pending-entity-ref`로 거부, 엔티티 배치 확정 후 별도 배치로.
|
|
104
|
+
* 그룹 박스 좌표(groupLayout)는 호스트가 멤버 엔티티 레이아웃을 감싸 incidental로 첨부(C1 동형).
|
|
105
|
+
*/
|
|
106
|
+
export interface GroupSpec {
|
|
107
|
+
/** 그룹 논리명(다국어, 예: {ko:'주문'}). 패키지 기반 그룹이면 보통 패키지 leaf의 업무명. */
|
|
108
|
+
name?: MultiLangText;
|
|
109
|
+
/** 자바 패키지명 등 그룹의 물리 식별(레거시 LogicalGroup.packageName). */
|
|
110
|
+
packageName?: string;
|
|
111
|
+
/** 그룹 설명(자유 텍스트 — 모듈/도메인 경계 의도 메모). */
|
|
112
|
+
description?: string;
|
|
113
|
+
/** DDL 생성 제외 여부(기본 false). */
|
|
114
|
+
excludeDDLGeneration?: boolean;
|
|
115
|
+
/** 멤버 엔티티 핸들(물리명 우선/논리명 보조). resolver가 entity modelId로 해소. */
|
|
116
|
+
members: Handle[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* index.add 컬럼 지정 — 인덱스 컬럼은 dbAttr(컬럼) modelId(`IndexColumn.columnRef`)를 참조하나,
|
|
120
|
+
* 핸들은 사람이 지정 가능한 물리명/속성명이다. resolver가 소속 엔티티 내에서 dbAttr modelId로 해소한다.
|
|
121
|
+
*/
|
|
122
|
+
export interface IndexColumnSpec {
|
|
123
|
+
/**
|
|
124
|
+
* 컬럼 핸들 — 컬럼 물리명(`dbAttrs[].physicalName`) 우선, 속성명(`name`) 보조.
|
|
125
|
+
* 속성명으로 지정 시 그 속성이 단일 컬럼이어야 한다(다중 dbAttr=임베드/Money는 모호 → resolver 거부).
|
|
126
|
+
*/
|
|
127
|
+
column: Handle;
|
|
128
|
+
/** 내림차순 정렬 컬럼(기본 false=오름차순). */
|
|
129
|
+
descending?: boolean;
|
|
130
|
+
}
|
|
131
|
+
/** index.add 페이로드 — resolver가 modelId 발급 + 컬럼 핸들을 dbAttr modelId로 해소. */
|
|
132
|
+
export interface IndexSpec {
|
|
133
|
+
name: string;
|
|
134
|
+
/** UNIQUE 인덱스 여부(기본 false). */
|
|
135
|
+
unique?: boolean;
|
|
136
|
+
/** 인덱스 컬럼(순서 의미 있음). 비어 있으면 컬럼 없는 인덱스(허용 — 값 품질은 검증 엔진). */
|
|
137
|
+
columns: IndexColumnSpec[];
|
|
138
|
+
/** 인덱스 설명(레거시 IndexModel.description, 평문). */
|
|
139
|
+
description?: string;
|
|
140
|
+
/** 인덱스 파라미터(레거시 IndexModel.parameters, 패스스루). */
|
|
141
|
+
parameters?: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* operation.add 페이로드 — 도메인 메서드(레거시 OperationModel). resolver가 modelId 발급 + order 누적.
|
|
145
|
+
* 파라미터·반환 타입은 별도 모델 필드가 아니라 `sourceCode`(Java 메서드 본문 텍스트)에 담긴다.
|
|
146
|
+
* legacyRaw(소스 엔티티 parent back-ref·properties)는 복사 시 stale이 되므로 op로 다루지 않는다.
|
|
147
|
+
*/
|
|
148
|
+
export interface OperationSpec {
|
|
149
|
+
name: string;
|
|
150
|
+
/** 가시성(기본 미설정 → 호스트/렌더 기본). */
|
|
151
|
+
visibility?: OperationVisibility;
|
|
152
|
+
/** Java 메서드 본문(멀티라인, 파라미터·반환 시그니처 포함). */
|
|
153
|
+
sourceCode?: string;
|
|
154
|
+
/** 메서드 설명(다국어). 레거시 직렬화는 평문이라 어댑터가 평문↔{ko} 변환(types.ts Operation 주석). */
|
|
155
|
+
description?: MultiLangText;
|
|
156
|
+
/** 개인정보 속성 유형(레거시 personalInfoAttributeType 패스스루). */
|
|
157
|
+
personalInfoAttributeType?: string;
|
|
158
|
+
}
|
|
61
159
|
/**
|
|
62
160
|
* LLM이 산출하는 단일 심볼릭 op. `kind`는 op.ts 어휘와 1:1(로케이터만 심볼릭).
|
|
63
161
|
* `patch`는 OpShape.patch와 동형(부분 patch, 그대로 전달 — 값 품질은 검증 엔진이 사후 경고).
|
|
@@ -100,10 +198,39 @@ export type SymbolicOp = {
|
|
|
100
198
|
association: AssocHandle;
|
|
101
199
|
end: 'from' | 'to';
|
|
102
200
|
patch: Record<string, unknown>;
|
|
201
|
+
} | {
|
|
202
|
+
kind: 'index.add';
|
|
203
|
+
entity: Handle;
|
|
204
|
+
spec: IndexSpec;
|
|
205
|
+
} | {
|
|
206
|
+
kind: 'index.update';
|
|
207
|
+
entity: Handle;
|
|
208
|
+
index: Handle;
|
|
209
|
+
patch: Record<string, unknown>;
|
|
210
|
+
} | {
|
|
211
|
+
kind: 'index.remove';
|
|
212
|
+
entity: Handle;
|
|
213
|
+
index: Handle;
|
|
214
|
+
} | {
|
|
215
|
+
kind: 'operation.add';
|
|
216
|
+
entity: Handle;
|
|
217
|
+
spec: OperationSpec;
|
|
218
|
+
} | {
|
|
219
|
+
kind: 'operation.update';
|
|
220
|
+
entity: Handle;
|
|
221
|
+
operation: Handle;
|
|
222
|
+
patch: Record<string, unknown>;
|
|
223
|
+
} | {
|
|
224
|
+
kind: 'operation.remove';
|
|
225
|
+
entity: Handle;
|
|
226
|
+
operation: Handle;
|
|
227
|
+
} | {
|
|
228
|
+
kind: 'group.add';
|
|
229
|
+
spec: GroupSpec;
|
|
103
230
|
};
|
|
104
231
|
/**
|
|
105
232
|
* v1이 다루는 심볼릭 op 종류의 닫힌 집합(단일 출처). resolver·JSON Schema(`schema.ts`)가 공유한다.
|
|
106
233
|
* 아래 컴파일타임 단언이 이 튜플과 `SymbolicOp['kind']`의 일치를 강제 — 한쪽만 늘리면 타입 에러.
|
|
107
234
|
*/
|
|
108
|
-
export declare const SYMBOLIC_OP_KINDS: readonly ["entity.add", "entity.update", "entity.remove", "attribute.add", "attribute.update", "attribute.remove", "association.add", "association.remove", "association.update", "associationEnd.update"];
|
|
235
|
+
export declare const SYMBOLIC_OP_KINDS: readonly ["entity.add", "entity.update", "entity.remove", "attribute.add", "attribute.update", "attribute.remove", "association.add", "association.remove", "association.update", "associationEnd.update", "index.add", "index.update", "index.remove", "operation.add", "operation.update", "operation.remove", "group.add"];
|
|
109
236
|
export type SymbolicOpKind = (typeof SYMBOLIC_OP_KINDS)[number];
|
|
@@ -40,6 +40,8 @@ export declare function resizeGroup(groupId: ModelId, size: {
|
|
|
40
40
|
width: number;
|
|
41
41
|
height: number;
|
|
42
42
|
}): Command;
|
|
43
|
+
/** 그룹 색상 변경 (레이아웃만 변경 — setEntitySwatch와 동형) */
|
|
44
|
+
export declare function setGroupSwatch(groupId: ModelId, token: string | null): Command;
|
|
43
45
|
export declare function addAttribute(entityId: ModelId, attribute: Attribute): Command;
|
|
44
46
|
/** 속성 필드 부분 수정 (name·type·dataType·dbAttrs·identifier·notNull 등). 얕은 patch. */
|
|
45
47
|
export declare function updateAttribute(entityId: ModelId, attributeId: ModelId, patch: Partial<Attribute>): Command;
|
|
@@ -89,6 +91,8 @@ export declare function resizeNote(noteId: ModelId, size: {
|
|
|
89
91
|
height: number;
|
|
90
92
|
}): Command;
|
|
91
93
|
export declare function setNoteMemo(noteId: ModelId, memo: string): Command;
|
|
94
|
+
/** 노트 색상 변경 (레이아웃만 변경 — setEntitySwatch와 동형) */
|
|
95
|
+
export declare function setNoteSwatch(noteId: ModelId, token: string | null): Command;
|
|
92
96
|
/**
|
|
93
97
|
* 노트를 그룹 박스 멤버로 편입(레이아웃 GroupLayout.memberNoteRefs). 이미 멤버면 no-op.
|
|
94
98
|
* addEntityToGroup과 거울이나 대상이 논리(memberEntityRefs)가 아닌 레이아웃이다 — 노트가 순수
|
|
@@ -10,3 +10,48 @@ export declare function computeAutoLayout(logical: LogicalModel, layout: Diagram
|
|
|
10
10
|
id: ModelId;
|
|
11
11
|
location: Point;
|
|
12
12
|
}[];
|
|
13
|
+
/**
|
|
14
|
+
* 높이 인지(height-aware) 박스 패킹 — 신규 엔티티 다수를 겹침 없이 격자 배치 (순수 함수, 엔진 무관).
|
|
15
|
+
*
|
|
16
|
+
* `computeAutoLayout`(고정 셀 격자)는 모든 엔티티가 같은 높이라는 가정이라, 속성 수가 많은 박스가
|
|
17
|
+
* 아랫줄을 침범한다. 이 함수는 박스마다 **속성 수에 비례한 높이를 추정**하고, 열별로 누적 Y(`colBottom`)를
|
|
18
|
+
* 추적해 다음 박스를 그 아래에 둔다 → 어떤 박스 높이 분포에서도 겹침이 구조적으로 발생하지 않는다.
|
|
19
|
+
*
|
|
20
|
+
* 배치 순서(좌→우, 위→아래 라운드로빈)는 입력 순서를 보존한다. 높이 추정은 의도적으로 넉넉히
|
|
21
|
+
* (간격이 뜨는 건 무해하나 겹침은 깨져 보임) — EntityNode가 `min-height: min-content`로 콘텐츠 구동이라
|
|
22
|
+
* 픽셀 정확이 아닌 선형 추정으로 충분하다.
|
|
23
|
+
*
|
|
24
|
+
* 좌표는 절대좌표. 기존 레이아웃 하단 아래에서 시작하려면 `baseY`를 넘긴다(호출자가 계산).
|
|
25
|
+
*/
|
|
26
|
+
export interface EntityBoxSpec {
|
|
27
|
+
id: ModelId;
|
|
28
|
+
/** 엔티티의 속성(attribute) 개수 — 높이 추정 입력. */
|
|
29
|
+
attributeCount: number;
|
|
30
|
+
}
|
|
31
|
+
export interface PackedEntityBox {
|
|
32
|
+
id: ModelId;
|
|
33
|
+
location: Point;
|
|
34
|
+
size: {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface PackBoxesOptions {
|
|
40
|
+
cols: number;
|
|
41
|
+
boxWidth: number;
|
|
42
|
+
/** 박스 간(가로·세로) 여백. */
|
|
43
|
+
gap: number;
|
|
44
|
+
baseX: number;
|
|
45
|
+
baseY: number;
|
|
46
|
+
/** 헤더(스테레오타입+이름) 추정 높이. */
|
|
47
|
+
headerHeight: number;
|
|
48
|
+
/** 속성 1행 추정 높이. */
|
|
49
|
+
rowHeight: number;
|
|
50
|
+
/** 헤더·행 외 하단 여유(연산/인덱스 섹션 등) 추정 높이. */
|
|
51
|
+
footerHeight: number;
|
|
52
|
+
/** 박스 최소 높이(속성 0개여도 이 높이는 확보). */
|
|
53
|
+
minHeight: number;
|
|
54
|
+
}
|
|
55
|
+
/** 속성 수 → 박스 추정 높이 (넉넉히 over-reserve). */
|
|
56
|
+
export declare function estimateEntityHeight(attributeCount: number, opts?: Partial<PackBoxesOptions>): number;
|
|
57
|
+
export declare function packEntityBoxes(boxes: EntityBoxSpec[], options?: Partial<PackBoxesOptions>): PackedEntityBox[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LogicalModel } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 논리 모델 내 전 레거시 Money 속성을 신규 Money embeddable로 승격한 **새 모델**을 반환(순수).
|
|
4
|
+
* 대상이 없으면 입력 참조를 그대로 반환(no-op). 레이아웃은 논리 modelId만 참조하고 dbAttr을 참조하지
|
|
5
|
+
* 않으므로 무변경 — 호출자(useMigrateEntityDiagram)는 `{ ...state, logical: migrateLegacyMoney(state.logical) }`.
|
|
6
|
+
* amount 슬롯이 레거시 컬럼 modelId를 보존하므로 인덱스(IndexColumn.columnRef) 참조도 안전.
|
|
7
|
+
*/
|
|
8
|
+
export declare function migrateLegacyMoney(logical: LogicalModel): LogicalModel;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ModelId } from './types';
|
|
2
|
+
import { FieldAuditRecord } from './projectFieldAudit';
|
|
3
|
+
import { EntityAuditSubjectKind } from './projectEntityAudit';
|
|
4
|
+
export type ActionSubjectKind = EntityAuditSubjectKind | 'association' | 'associationEnd' | 'group';
|
|
5
|
+
/** 무엇이 바뀐 대상인가. entityRef는 자식(속성/연산/인덱스) subject의 부모(이름 조회·복원 스코프). */
|
|
6
|
+
export interface ActionSubject {
|
|
7
|
+
kind: ActionSubjectKind;
|
|
8
|
+
ref: ModelId;
|
|
9
|
+
entityRef?: ModelId;
|
|
10
|
+
end?: 'end1' | 'end2';
|
|
11
|
+
}
|
|
12
|
+
/** 한 대상의 한 필드 변경(old→new / tombstone). */
|
|
13
|
+
export interface ActionFieldChange {
|
|
14
|
+
field: string;
|
|
15
|
+
value: unknown;
|
|
16
|
+
oldValue?: unknown;
|
|
17
|
+
removed?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** 한 seq에서 한 대상에 일어난 액션 — verb + 변경 필드들(born=추가 요약, update=필드별). */
|
|
20
|
+
export interface SubjectAction {
|
|
21
|
+
subject: ActionSubject;
|
|
22
|
+
verb: 'add' | 'update' | 'remove';
|
|
23
|
+
fields: ActionFieldChange[];
|
|
24
|
+
}
|
|
25
|
+
/** 한 seq(= 한 op 배치 = 한 사용자 액션) 그룹. */
|
|
26
|
+
export interface ActionGroup {
|
|
27
|
+
seq: number;
|
|
28
|
+
ts?: string | number | Date;
|
|
29
|
+
origin?: string;
|
|
30
|
+
author?: string;
|
|
31
|
+
kind?: 'edit' | 'restore';
|
|
32
|
+
restoredFromSeq?: number;
|
|
33
|
+
actions: SubjectAction[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* op 이력 스트림을 seq(액션) 단위 그룹으로 투영한다(diagram-wide).
|
|
37
|
+
*
|
|
38
|
+
* @param records seq 정렬 이력 레코드.
|
|
39
|
+
* @returns seq **내림차순**(최신 먼저) 액션 그룹. 각 그룹은 subject별 액션(verb+필드)으로 구성.
|
|
40
|
+
*/
|
|
41
|
+
export declare function projectActionLog(records: readonly FieldAuditRecord[]): ActionGroup[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ModelId } from './types';
|
|
2
|
+
import { FieldAuditRecord, FieldAuditEntry } from './projectFieldAudit';
|
|
3
|
+
/** 감사 대상 종류 — 엔티티 자신 또는 자식 컬렉션. */
|
|
4
|
+
export type EntityAuditSubjectKind = 'entity' | 'attribute' | 'operation' | 'index';
|
|
5
|
+
/** 무엇이 바뀐 대상인가 — 피드에서 필드 값과 함께 표시. */
|
|
6
|
+
export interface EntityAuditSubject {
|
|
7
|
+
kind: EntityAuditSubjectKind;
|
|
8
|
+
/** 대상 ref. kind='entity'면 엔티티 자신(entityRef와 동일). */
|
|
9
|
+
ref: ModelId;
|
|
10
|
+
}
|
|
11
|
+
/** 엔티티 감사 피드 한 엔트리 — projectFieldAudit 엔트리 + 어느 대상인지(subject). */
|
|
12
|
+
export interface EntityAuditEntry extends FieldAuditEntry {
|
|
13
|
+
subject: EntityAuditSubject;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 한 엔티티의 감사 피드(엔티티 자신 + 모든 자식 속성/연산/인덱스의 필드 변경)를 seq 순으로 반환한다.
|
|
17
|
+
*
|
|
18
|
+
* @param records seq 정렬 이력 레코드(projectFieldAudit와 동일 입력). 필요 시 방어적 재정렬은 하위가 수행.
|
|
19
|
+
* @param entityRef 대상 엔티티.
|
|
20
|
+
* @returns seq 오름차순 병합 피드(각 엔트리에 subject 태그). 동일 seq는 원 삽입 순서 유지(안정 정렬).
|
|
21
|
+
*/
|
|
22
|
+
export declare function projectEntityAudit(records: readonly FieldAuditRecord[], entityRef: ModelId): EntityAuditEntry[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ModelId } from './types';
|
|
2
|
+
import { OpShape } from '../command/op';
|
|
3
|
+
/**
|
|
4
|
+
* 투영기 입력 — 이력 레코드의 lib-로컬 최소 형태(호스트 `OpHistoryRecord`의 부분집합).
|
|
5
|
+
* 호스트가 `{diagramId, seq}` 정렬로 조회해 넘긴다. lib는 호스트 타입에 의존하지 않으려 여기서 재선언한다.
|
|
6
|
+
*/
|
|
7
|
+
export interface FieldAuditRecord {
|
|
8
|
+
/** diagram 내 단조 증가 좌표 — 타임라인 정렬 키(갭 허용, 연속성 가정 금지 §5.4). */
|
|
9
|
+
seq: number;
|
|
10
|
+
/** 서버 시각(감사 표시용 패스스루). */
|
|
11
|
+
ts?: string | number | Date;
|
|
12
|
+
/** op 출처(gui/agent/rest). 감사 "누가"의 현 해상도(경로 수준, §4 결정 2). */
|
|
13
|
+
origin?: string;
|
|
14
|
+
/**
|
|
15
|
+
* 개별 사용자 식별자(감사 "누가"의 per-user 해상도, §4 결정 2-(나)).
|
|
16
|
+
* 슬라이스 1/3c 레코드에는 없음(forward-only) — 있으면 그대로 타임라인에 패스스루한다(author-aware).
|
|
17
|
+
*/
|
|
18
|
+
author?: string;
|
|
19
|
+
/** 편집 vs 복원 축(감사 표시). */
|
|
20
|
+
kind?: 'edit' | 'restore';
|
|
21
|
+
/** kind='restore'일 때 복원 대상 seq(패스스루). */
|
|
22
|
+
restoredFromSeq?: number;
|
|
23
|
+
/** ★필드 변경의 원천. edit=배치 그대로 / restore=합성 diff. */
|
|
24
|
+
ops: OpShape[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 감사 대상 로케이터. 설정된 ref로 대상 종류(엔티티/속성/연산/인덱스/연관/연관end/그룹)가 결정된다.
|
|
28
|
+
* `field`를 주면 그 필드 단일 타임라인, 생략하면 대상의 모든 변경 필드.
|
|
29
|
+
*/
|
|
30
|
+
export interface FieldAuditTarget {
|
|
31
|
+
entityRef?: ModelId;
|
|
32
|
+
attributeRef?: ModelId;
|
|
33
|
+
operationRef?: ModelId;
|
|
34
|
+
indexRef?: ModelId;
|
|
35
|
+
associationRef?: ModelId;
|
|
36
|
+
groupRef?: ModelId;
|
|
37
|
+
end?: 'end1' | 'end2';
|
|
38
|
+
/** 단일 필드로 좁힘(생략 시 전 필드). */
|
|
39
|
+
field?: string;
|
|
40
|
+
}
|
|
41
|
+
/** 타임라인 한 엔트리 — 한 필드가 한 seq에서 얻은 값(또는 tombstone). */
|
|
42
|
+
export interface FieldAuditEntry {
|
|
43
|
+
seq: number;
|
|
44
|
+
ts?: string | number | Date;
|
|
45
|
+
origin?: string;
|
|
46
|
+
author?: string;
|
|
47
|
+
kind?: 'edit' | 'restore';
|
|
48
|
+
restoredFromSeq?: number;
|
|
49
|
+
/** 바뀐 필드명. 멤버십은 'memberEntityRefs'. */
|
|
50
|
+
field: string;
|
|
51
|
+
/** 신규 값. null=필드 비움(clear). removed=true면 undefined. */
|
|
52
|
+
value: unknown;
|
|
53
|
+
/** 직전 엔트리에서 파생한 이전 값(§3.3-(가)). 최초 born·재-add는 없음. */
|
|
54
|
+
oldValue?: unknown;
|
|
55
|
+
/** 대상/필드가 이 seq에 소멸(remove/entity.remove/group.removeMember). */
|
|
56
|
+
removed?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* op 이력 스트림을 특정 대상의 per-field 변경 타임라인으로 투영한다(순수).
|
|
60
|
+
*
|
|
61
|
+
* @param records seq 오름차순 이력 레코드(방어적으로 seq 재정렬). 각 `ops`가 필드 변경 원천.
|
|
62
|
+
* @param target 대상 로케이터(+ 선택적 field 필터).
|
|
63
|
+
* @returns seq 순 필드 감사 엔트리 배열(edit·restore 혼합, born·update·tombstone·멤버십).
|
|
64
|
+
*/
|
|
65
|
+
export declare function projectFieldAudit(records: readonly FieldAuditRecord[], target: FieldAuditTarget): FieldAuditEntry[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DiagramLayout, LogicalModel } from './types';
|
|
2
|
+
import { OpShape } from '../command/op';
|
|
3
|
+
/** 합성기 입력 — 정합한 완전 상태(logical + layout). 호스트는 v2 doc 서브트리를 그대로 넘긴다(rev/version은 무시). */
|
|
4
|
+
export interface DiagramSnapshotState {
|
|
5
|
+
logical: LogicalModel;
|
|
6
|
+
layout: DiagramLayout;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* null≡undefined≡absent 정규화 deep-equal. 스칼라/배열(인덱스별)/객체(키 합집합)를 재귀 비교한다.
|
|
10
|
+
* (mongo가 optional 필드 clear를 null로 저장하는 것과 target의 absent를 동치로 본다.)
|
|
11
|
+
*/
|
|
12
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* current → target 복원 diff를 forward op으로 합성한다(순수, 감사 전용 — 실행 안 됨).
|
|
15
|
+
*
|
|
16
|
+
* 산출 순서(결정성): logical(entities → associations → groups; 각 remove → add → update/child) →
|
|
17
|
+
* layout(entityLayouts → associationLayouts → groupLayouts → notes). 각 컬렉션은 key 정렬.
|
|
18
|
+
*
|
|
19
|
+
* @returns OpShape[] — CAS 메타(rev 숫자·layout.version) 없는 어휘 형태(호스트 이력 레코드 `ops`에 그대로 기록).
|
|
20
|
+
*/
|
|
21
|
+
export declare function synthesizeRestoreOps(current: DiagramSnapshotState, target: DiagramSnapshotState): OpShape[];
|