@g1cloud/entity-modeler-next 5.0.0-beta.9 → 5.0.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/README.md +19 -5
- package/dist/adapter/diagramAdapter.d.ts +6 -0
- package/dist/adapter/healBackfill.d.ts +38 -0
- package/dist/agent/resolver.d.ts +195 -7
- package/dist/agent/symbolicOp.d.ts +53 -4
- package/dist/command/op.d.ts +35 -1
- package/dist/command/opSync.d.ts +11 -1
- package/dist/command/stack.d.ts +11 -1
- package/dist/core/associationNav.d.ts +14 -0
- package/dist/core/columnResolve.d.ts +36 -0
- package/dist/core/embedSlots.d.ts +18 -0
- package/dist/core/entityPackage.d.ts +24 -0
- package/dist/core/fkDerived.d.ts +22 -2
- package/dist/core/indexCleanup.d.ts +16 -0
- package/dist/core/indexColumnRef.d.ts +27 -0
- package/dist/core/javaTypes.d.ts +5 -0
- package/dist/core/opSubject.d.ts +54 -0
- package/dist/core/projectActionLog.d.ts +7 -10
- package/dist/core/projectSeqDiff.d.ts +41 -0
- package/dist/core/propagation.d.ts +33 -2
- package/dist/core/quickFix.d.ts +9 -4
- package/dist/core/resolve.d.ts +0 -3
- package/dist/core/scaffoldJava.d.ts +30 -1
- package/dist/core/text.d.ts +2 -1
- package/dist/core/validation.d.ts +1 -13
- package/dist/editor/association.d.ts +18 -0
- package/dist/editor/attribute.d.ts +25 -0
- package/dist/editor/clipboardImport.d.ts +23 -0
- package/dist/editor/commandPlans.d.ts +111 -0
- package/dist/editor/controller.d.ts +61 -20
- package/dist/editor/entity.d.ts +19 -0
- package/dist/editor/group.d.ts +22 -0
- package/dist/editor/hostSync.d.ts +30 -0
- package/dist/editor/note.d.ts +11 -0
- package/dist/editor/selection.d.ts +22 -0
- package/dist/entity-modeler-next.css +1 -1
- package/dist/entity-modeler.js +16701 -13753
- package/dist/entity-modeler.umd.cjs +28 -24
- package/dist/i18n/ko.d.ts +64 -2
- package/dist/index.d.ts +26 -23
- package/dist/view/ColumnLangSelect.vue.d.ts +16 -0
- package/dist/view/DiagramCanvas.vue.d.ts +9 -0
- package/dist/view/DiagramExplorer.vue.d.ts +7 -2
- package/dist/view/HistoryPanel.vue.d.ts +3 -0
- package/dist/view/MultiLangTextField.vue.d.ts +7 -0
- package/dist/view/columnResolution.d.ts +91 -0
- package/dist/view/diffOverlay.d.ts +14 -0
- package/dist/view/embedSlotCollapse.d.ts +36 -0
- package/dist/view/nodeInternals.d.ts +15 -0
- package/dist/view/opConflictNotice.d.ts +8 -6
- package/dist/view/swatches.d.ts +3 -2
- package/dist/view/useAttributeEditing.d.ts +82 -0
- package/dist/view/useTreeCollapse.d.ts +16 -0
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -28,8 +28,10 @@ import '@g1cloud/entity-modeler-next/style.css' // required — components rend
|
|
|
28
28
|
src/
|
|
29
29
|
core/ logical/layout types (separated) · resolve (consistency) · routing · autolayout · propagation · validation · type catalogs. Framework-agnostic
|
|
30
30
|
command/ Command (do/undo) · CommandStack · op/opSync (semantic op emit + CAS concurrency)
|
|
31
|
-
editor/ reactive controller (composable wrapping CommandStack, EDITOR inject key)
|
|
31
|
+
editor/ reactive controller (composable wrapping CommandStack, EDITOR inject key) + axis modules split out of it
|
|
32
|
+
(commandPlans · clipboardImport · hostSync · selection — each takes a minimal injected context)
|
|
32
33
|
view/ Vue Flow components (DiagramCanvas · EntityNode · GroupNode · AssociationEdge · PropertyPanel · ValidationPanel, etc.)
|
|
34
|
+
plus composables (useAttributeEditing · useResizableWidth · useTreeCollapse) and pure helpers
|
|
33
35
|
adapter/ storage-schema mapping (persisted v1/v2 · fromPersisted/toPersisted/toPersistedV2 round-trip)
|
|
34
36
|
agent/ natural-language → op track (symbolicOp · resolver · schema · buildAgentBatch)
|
|
35
37
|
dev/ demo harness (excluded from the library build)
|
|
@@ -40,8 +42,20 @@ Core design: **layout references the logical model by `modelId` (one-way)**. The
|
|
|
40
42
|
## Scripts
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
|
-
pnpm dev
|
|
44
|
-
pnpm test
|
|
45
|
-
pnpm
|
|
46
|
-
pnpm
|
|
45
|
+
pnpm dev # demo harness dev server (src/dev)
|
|
46
|
+
pnpm test # Vitest (unit)
|
|
47
|
+
pnpm test:coverage # Vitest + v8 coverage report (no threshold — report only)
|
|
48
|
+
pnpm typecheck # vue-tsc --noEmit
|
|
49
|
+
pnpm build # library build (dist/)
|
|
50
|
+
pnpm verify # headless GUI probe suite (scripts/verify-*.mjs) — boots the dev harness once, runs all probes
|
|
51
|
+
pnpm verify:floor # raise the per-probe assertion-count floor to current (never lowers)
|
|
52
|
+
pnpm check # CI gate: test && build
|
|
47
53
|
```
|
|
54
|
+
|
|
55
|
+
`check` is the entry point CI calls; `verify` runs the browser probes and is kept separate because it needs a
|
|
56
|
+
dev server and takes ~100s.
|
|
57
|
+
|
|
58
|
+
`verify` also enforces a per-probe **assertion-count floor** (`scripts/probe-assertion-floor.json`): a probe whose
|
|
59
|
+
executed-assertion total drops below its floor fails even if every remaining assertion passes — deleting an `ok()`
|
|
60
|
+
would otherwise shrink the denominator too and pass as `0/0`. Adding assertions needs no bookkeeping; only a
|
|
61
|
+
deliberate reduction requires editing the JSON by hand, and that diff is the review signal.
|
|
@@ -27,6 +27,12 @@ export interface DiagramMeta {
|
|
|
27
27
|
/**
|
|
28
28
|
* 마커 분기 파서 — `schemaVersion`을 보고 v1(legacy flat) vs v2(logical/layout) 경로 선택(B-0).
|
|
29
29
|
* reload(프리즈 후 재적재)도 이 진입점을 경유해 동일 분기를 탄다.
|
|
30
|
+
*
|
|
31
|
+
* 산출 state는 입력 doc과 **참조 비공유**(계약) — 진입 시 deep-clone한다. 로드 자가치유
|
|
32
|
+
* (heal/normalize)와 이후 편집(commands의 in-place 변형)이 모두 사본 위에서 일어나므로,
|
|
33
|
+
* 호출자 소유 객체(호스트 useFetch payload 등 리액티브 소스)를 오염시키지 않는다.
|
|
34
|
+
* reactive proxy에 structuredClone은 throw할 수 있어 JSON deep-clone(controller 클립보드와
|
|
35
|
+
* 동일 패턴). undefined 값 키는 탈락하나 부재와 의미 동치.
|
|
30
36
|
*/
|
|
31
37
|
export declare function fromPersisted(doc: PDiagram | PDiagramV2): {
|
|
32
38
|
state: EditorState;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ModelId } from '../core/types';
|
|
2
|
+
import { OpShape } from '../command/op';
|
|
3
|
+
import { PDiagram, PDiagramV2 } from './persisted';
|
|
4
|
+
/**
|
|
5
|
+
* 로드 경로가 파생하는 **속성 스칼라 필드** 목록 — 이 플래너가 raw↔healed diff로 정렬하는 축.
|
|
6
|
+
*
|
|
7
|
+
* 전체 필드를 무조건 diff하지 않는 이유: 로드 경로가 의도적으로 바꾸지 않는 필드까지 잡아
|
|
8
|
+
* 오탐(JSON 왕복의 `null`↔부재 등)을 낼 수 있다. **선언된 목록**이 곧 "무엇이 파생값인가"의
|
|
9
|
+
* 레지스트리이며, 새 파생 축 추가 = 여기에 이름 한 줄(라우트·플래너 신설 불요).
|
|
10
|
+
*
|
|
11
|
+
* - `notNull` : FK는 부모 end 다중성 파생(`deriveFkNotNull`, CC-4). 비-FK 속성은 파생 대상이
|
|
12
|
+
* 아니지만 파생기가 FK만 건드리므로 diff도 자연히 FK에서만 발생한다.
|
|
13
|
+
*/
|
|
14
|
+
declare const DERIVED_ATTRIBUTE_FIELDS: readonly ["notNull"];
|
|
15
|
+
export interface HealBackfillPlan {
|
|
16
|
+
/** backfill op 배치 (비면 정렬 대상 없음 — 문서 skip). */
|
|
17
|
+
ops: OpShape[];
|
|
18
|
+
/** raw doc의 컨테이너 rev 스냅샷 — `buildAgentBatch(diagramId, revs, ops)` CAS base. */
|
|
19
|
+
revs: Record<ModelId, number>;
|
|
20
|
+
/** dry-run 리포트용 집계. */
|
|
21
|
+
summary: {
|
|
22
|
+
navAdds: number;
|
|
23
|
+
navRefPatches: number;
|
|
24
|
+
identifyingPatches: number;
|
|
25
|
+
/** 파생 스칼라 필드 정렬 건수(`DERIVED_ATTRIBUTE_FIELDS`) — 필드별 분해. */
|
|
26
|
+
attributeFieldPatches: number;
|
|
27
|
+
attributeFieldsByName: Partial<Record<(typeof DERIVED_ATTRIBUTE_FIELDS)[number], number>>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* v2 doc 하나에 대한 파생값 backfill 계획을 산출한다.
|
|
32
|
+
*
|
|
33
|
+
* v1 doc은 빈 계획 — 스윕 라우트가 v2 컬렉션 대상이고, v1은 마이그레이션을 거쳐 v2가 된 뒤
|
|
34
|
+
* 대상이 된다. 마이그레이션 산출은 hydration이 이미 현재 규칙으로 파생하므로(nav 실체화·
|
|
35
|
+
* identifying·notNull) 새로 마이그레이션된 문서는 정렬된 상태로 태어난다.
|
|
36
|
+
*/
|
|
37
|
+
export declare function planHealBackfillOps(doc: PDiagram | PDiagramV2): HealBackfillPlan;
|
|
38
|
+
export {};
|
package/dist/agent/resolver.d.ts
CHANGED
|
@@ -1,7 +1,47 @@
|
|
|
1
|
-
import { EmbeddableCatalog, LogicalModel, ModelId } from '../core/types';
|
|
1
|
+
import { ClassStereotype, MultiLangText, EmbeddableCatalog, LogicalModel, ModelId } from '../core/types';
|
|
2
2
|
import { OpShape } from '../command/op';
|
|
3
|
-
import { AssocHandle, Handle, SymbolicOp } from './symbolicOp';
|
|
3
|
+
import { AssocHandle, GroupHandle, Handle, SymbolicOp } from './symbolicOp';
|
|
4
4
|
/** 해소 실패 — 어느 심볼릭 op(`opIndex`)의 어떤 핸들이 0개/복수 매칭인지. */
|
|
5
|
+
/**
|
|
6
|
+
* 모호 엔티티 후보 — **판별 맥락을 함께 싣는다**.
|
|
7
|
+
*
|
|
8
|
+
* modelId 만 돌려주면 사람이 고를 근거가 없다(UUID 둘 사이에 우열이 없다). 실제로 stale/live 를 가르는 데
|
|
9
|
+
* 쓰인 축을 그대로 싣는다 — 패키지 소속·테이블·속성 수·참조 수, 그리고 스테레오타입(한 이름 아래
|
|
10
|
+
* `@Entity` 와 `@Embeddable` 이 섞인 형상이 실 데이터에 있다). 거부에 사람이 행동할 맥락을 동봉하는 것은
|
|
11
|
+
* 이 리포의 기존 패턴이다(`attribute-fk-managed` 가 소유 관계의 양 끝 이름을 싣는다).
|
|
12
|
+
*
|
|
13
|
+
* 고른 뒤 지목하는 수단은 `Handle.by:'modelId'`다 — 이 둘이 짝이라야 고리가 닫힌다.
|
|
14
|
+
*/
|
|
15
|
+
export interface EntityCandidate {
|
|
16
|
+
modelId: ModelId;
|
|
17
|
+
stereotype: ClassStereotype;
|
|
18
|
+
/** 실효 패키지(명시 packageName 또는 소속 그룹의 packageName). 없으면 null — 코드젠과 같은 판별 단계. */
|
|
19
|
+
packageName: string | null;
|
|
20
|
+
/** 테이블 물리명 **원문**. 미설정이면 null — 폴백하지 않는다(미설정 자체가 미완성 신호라서). */
|
|
21
|
+
table: string | null;
|
|
22
|
+
attributes: number;
|
|
23
|
+
/** 이 엔티티를 양 끝 중 하나로 갖는 관계 수 — 0이면 고립(버려진 쪽일 가능성). */
|
|
24
|
+
references: number;
|
|
25
|
+
/**
|
|
26
|
+
* 참조하는 상대 엔티티 식별자(이름 → 테이블 → modelId 순 폴백, 중복 제거).
|
|
27
|
+
*
|
|
28
|
+
* ★수치만으로는 갈리지 않는 실 형상이 있다 — 프로덕션 `module-catalog` 의 `Gift` 두 벌은 패키지·테이블·
|
|
29
|
+
* 참조 수가 **전부 같고** 속성 수(6 vs 8)만 다르다. *누가 쓰는가*가 실제 판별 축이라 함께 싣는다
|
|
30
|
+
* (이름이 빈 엔티티가 실재해 테이블·modelId 로 폴백한다 — 없는 이름을 지어내지 않는다).
|
|
31
|
+
*/
|
|
32
|
+
referencedBy: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 모호 그룹 후보 — 엔티티 축과 같은 이유로 판별 맥락을 싣는다(→ `EntityCandidate`).
|
|
36
|
+
* 그룹은 테이블·속성이 없으므로 축이 다르다: 이름(다국어 원문)·패키지·멤버 수.
|
|
37
|
+
*/
|
|
38
|
+
export interface GroupCandidate {
|
|
39
|
+
modelId: ModelId;
|
|
40
|
+
/** 다국어 원문 그대로 — 표시 로케일은 소비처가 정한다(resolver는 로케일을 모른다). */
|
|
41
|
+
name: MultiLangText | null;
|
|
42
|
+
packageName: string | null;
|
|
43
|
+
members: number;
|
|
44
|
+
}
|
|
5
45
|
export type ResolveError = {
|
|
6
46
|
code: 'entity-not-found';
|
|
7
47
|
opIndex: number;
|
|
@@ -10,7 +50,7 @@ export type ResolveError = {
|
|
|
10
50
|
code: 'entity-ambiguous';
|
|
11
51
|
opIndex: number;
|
|
12
52
|
handle: Handle;
|
|
13
|
-
matches:
|
|
53
|
+
matches: EntityCandidate[];
|
|
14
54
|
} | {
|
|
15
55
|
code: 'attribute-not-found';
|
|
16
56
|
opIndex: number;
|
|
@@ -43,6 +83,13 @@ export type ResolveError = {
|
|
|
43
83
|
entity: Handle;
|
|
44
84
|
handle: Handle;
|
|
45
85
|
}
|
|
86
|
+
/** 물리 컬럼을 만들면서 dataType 미지정 — 빈 타입 컬럼은 DDL에서 드롭된다(발명 대신 요구). */
|
|
87
|
+
| {
|
|
88
|
+
code: 'datatype-required';
|
|
89
|
+
opIndex: number;
|
|
90
|
+
entity: Handle;
|
|
91
|
+
handle: Handle;
|
|
92
|
+
}
|
|
46
93
|
/** index.add 컬럼 핸들이 엔티티 내 dbAttr와 0개 매칭. */
|
|
47
94
|
| {
|
|
48
95
|
code: 'column-not-found';
|
|
@@ -168,6 +215,7 @@ export type ResolveError = {
|
|
|
168
215
|
/**
|
|
169
216
|
* attribute.update patch에 Attribute 논리 노드에 실재하지 않는 미지 키 — 통과 시 orphan blind-write.
|
|
170
217
|
* 오탈자/스키마 밖 키를 조용히 흡수하지 않고 명시 거부(자기증식 오염 원천 차단).
|
|
218
|
+
* 허용 dotted 접두는 `dbAttrs.` 하나뿐이다(형제 op의 `jpaAttrs.`·`jpa.` 제한과 같은 근거 — 아래 분류 주석).
|
|
171
219
|
*/
|
|
172
220
|
| {
|
|
173
221
|
code: 'attribute-patch-unknown-key';
|
|
@@ -175,6 +223,150 @@ export type ResolveError = {
|
|
|
175
223
|
entity: Handle;
|
|
176
224
|
handle: Handle;
|
|
177
225
|
keys: string[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* entity.update patch에 Entity 논리 노드에 실재하지 않는 미지 키 — `attribute-patch-unknown-key`의 형제.
|
|
229
|
+
*
|
|
230
|
+
* ★이 op은 **스키마가 의도적으로 open**이다(dotted-key `jpaAttrs.*`가 필요해 `additionalProperties:false`로
|
|
231
|
+
* 닫지 못한다 — schema.ts §5.2 폐색 주석). 즉 LLM 가이드가 없는 경로이고 **resolver가 유일한 게이트**다.
|
|
232
|
+
* 특히 `attributes`/`indexes`/`operations`(자식 컬렉션)가 통과하면 호스트 인터프리터가 논리 노드에 통째
|
|
233
|
+
* `$set`해 **전 배열 blind-write**가 된다(모든 modelId 재발급 = 인덱스 columnRef·derivedFrom·end.attributeRef
|
|
234
|
+
* 전량 dangling). 자식 변경은 전용 op 소관.
|
|
235
|
+
*/
|
|
236
|
+
| {
|
|
237
|
+
code: 'entity-patch-unknown-key';
|
|
238
|
+
opIndex: number;
|
|
239
|
+
entity: Handle;
|
|
240
|
+
keys: string[];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* entity.add의 논리명이 모델에 이미 있는 엔티티와 충돌. 통과시키면 **도구가 스스로 `CHK-NAME-3`(error)
|
|
244
|
+
* 상태를 만든다**(CC-5 교훈: 엔진이 검증 error 상태를 생성하지 않는다). 더 나쁜 건 같은 배치의 형제
|
|
245
|
+
* op이다 — pending 가드는 `not-found`일 때만 격상하므로(`entityErr`), 이름이 겹치면 자식 op의 핸들이
|
|
246
|
+
* **기존 엔티티로 조용히 해소돼** 사용자가 의도한 신규 엔티티가 아니라 남의 엔티티에 붙는다.
|
|
247
|
+
* 이름은 사용자·AI가 정해야 할 값이라 도구가 유일화(`Order2`)로 발명하지 않는다([[dont-invent-unknown-values]]).
|
|
248
|
+
*/
|
|
249
|
+
| {
|
|
250
|
+
code: 'entity-name-conflict';
|
|
251
|
+
opIndex: number;
|
|
252
|
+
handle: Handle;
|
|
253
|
+
matches: ModelId[];
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* attribute.update patch의 `attrType` — 미지 값이거나 **전환**(현재 값과 다름)이라 거부.
|
|
257
|
+
*
|
|
258
|
+
* ★`attrType`은 patch로 자유 설정할 값이 아니라 **동반 구조에서 파생되는 성격**이다:
|
|
259
|
+
* `RELATION_*`은 관계가 소유(`derivedFrom`·`end.attributeRef`), `EMBED_*`는 `embedded` 또는 카탈로그
|
|
260
|
+
* 임베더블 `type`, `DIVIDER`는 `dbAttrs=[]`·`type=''`가 동반돼야 성립한다. patch는 그 동반 구조를
|
|
261
|
+
* 표현할 수 없으므로 전환을 통과시키면 **아무도 관리하지 않는데 사용자도 고칠 수 없는 잠긴 컬럼**이
|
|
262
|
+
* 된다(CC-10 ①-b에서 mermaid 파서가 만들던 바로 그 상태 — `fkDerived`가 GUI 편집을 잠그는데 전파·
|
|
263
|
+
* 코드젠은 그 속성을 관리하지 않는다). 각 축의 정상 경로는 따로 있다: 관계 실체화=관계 op,
|
|
264
|
+
* 임베드 서브컬럼=`embeddableOverrides`, inverse nav 토글=`navigable-toggle-unsupported`가 안내.
|
|
265
|
+
* ⇒ 라운드트립 에코(같은 값 재전송)만 통과시킨다. `reason`으로 사용자가 할 일이 갈린다.
|
|
266
|
+
*/
|
|
267
|
+
| {
|
|
268
|
+
code: 'attribute-attrtype-unsupported';
|
|
269
|
+
opIndex: number;
|
|
270
|
+
entity: Handle;
|
|
271
|
+
handle: Handle;
|
|
272
|
+
/** `unknown-value`=오탈자·존재하지 않는 값 / `transition`=값은 유효하나 patch로 바꿀 수 없는 축. */
|
|
273
|
+
reason: 'unknown-value' | 'transition';
|
|
274
|
+
current: string;
|
|
275
|
+
requested: string;
|
|
276
|
+
}
|
|
277
|
+
/** association.update patch 미지 키(스키마는 하드 폐색 — REST 직접 호출 대비 심층 방어). */
|
|
278
|
+
| {
|
|
279
|
+
code: 'association-patch-unknown-key';
|
|
280
|
+
opIndex: number;
|
|
281
|
+
handle: AssocHandle;
|
|
282
|
+
keys: string[];
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* associationEnd.update patch 미지 키. entity.update와 같은 이유로 스키마가 open(dotted `jpa.*`)이라
|
|
286
|
+
* resolver가 유일한 게이트다. `entityRef`·`attributeRef`는 구조 참조라 patch로 바꾸면 관계가 끊긴다.
|
|
287
|
+
*/
|
|
288
|
+
| {
|
|
289
|
+
code: 'association-end-patch-unknown-key';
|
|
290
|
+
opIndex: number;
|
|
291
|
+
handle: AssocHandle;
|
|
292
|
+
keys: string[];
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* index/operation 핸들에 `by:'physicalName'` — 두 대상엔 물리명 차원이 없어 고정할 키가 없다.
|
|
296
|
+
* 조용히 name으로 매칭하면 *지정하지 않은 키로 해소된 결과*가 성공으로 돌아온다(엔티티 핸들의 `by`는
|
|
297
|
+
* 실제로 키를 고정하므로 같은 필드가 대상에 따라 다르게 동작하는 무신호 비대칭). `by`를 빼거나 `'name'`으로.
|
|
298
|
+
*/
|
|
299
|
+
| {
|
|
300
|
+
code: 'handle-by-unsupported';
|
|
301
|
+
opIndex: number;
|
|
302
|
+
entity: Handle;
|
|
303
|
+
handle: Handle;
|
|
304
|
+
target: 'index' | 'operation';
|
|
305
|
+
}
|
|
306
|
+
/** index.update patch 미지 키(스키마 하드 폐색 — 심층 방어). `columns`는 전용 에러가 먼저 잡는다. */
|
|
307
|
+
| {
|
|
308
|
+
code: 'index-patch-unknown-key';
|
|
309
|
+
opIndex: number;
|
|
310
|
+
entity: Handle;
|
|
311
|
+
handle: Handle;
|
|
312
|
+
keys: string[];
|
|
313
|
+
}
|
|
314
|
+
/** operation.update patch 미지 키(스키마 하드 폐색 — 심층 방어). */
|
|
315
|
+
| {
|
|
316
|
+
code: 'operation-patch-unknown-key';
|
|
317
|
+
opIndex: number;
|
|
318
|
+
entity: Handle;
|
|
319
|
+
handle: Handle;
|
|
320
|
+
keys: string[];
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* associationEnd.update patch의 `navigable`(양 end) — end1(from): inverse nav 실체화/철거는
|
|
324
|
+
* RELATION_REF 속성 생성·삭제가 동반되는 GUI 토글 소유(통과 시 "navigable=true ⟺ RELATION_REF 존재"
|
|
325
|
+
* 불변식이 깨져 로드 자가치유가 비결정 실체화를 반복). end2(to): JPA 소유측 참조는 관계 존재와
|
|
326
|
+
* 동치라 고정 true — 참조 없는 FK는 관계가 아니라 일반 컬럼으로 모델링.
|
|
327
|
+
*/
|
|
328
|
+
| {
|
|
329
|
+
code: 'navigable-toggle-unsupported';
|
|
330
|
+
opIndex: number;
|
|
331
|
+
handle: AssocHandle;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* FK 파생 속성(RELATION* + 물리 컬럼) 직접 삭제 — 관계가 소유하는 파생물이라 직접 지우면 관계
|
|
335
|
+
* end.attributeRef가 끊긴다(GUI removeAttribute 가드 동형, Option B). `association`(양 끝 엔티티명)의
|
|
336
|
+
* 관계를 association.remove로 삭제하도록 유도.
|
|
337
|
+
*/
|
|
338
|
+
| {
|
|
339
|
+
code: 'group-not-found';
|
|
340
|
+
opIndex: number;
|
|
341
|
+
handle: GroupHandle;
|
|
342
|
+
} | {
|
|
343
|
+
code: 'group-ambiguous';
|
|
344
|
+
opIndex: number;
|
|
345
|
+
handle: GroupHandle;
|
|
346
|
+
matches: GroupCandidate[];
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* group.update patch에 `LogicalGroup` 논리 노드에 실재하지 않는 미지 키 — 형제 op의 화이트리스트와 같은 근거
|
|
350
|
+
* (호스트가 patch 를 논리 노드에 통째 `$set` 하므로 통과하면 스키마 밖 orphan 키가 그대로 영속된다).
|
|
351
|
+
*
|
|
352
|
+
* ★`memberEntityRefs` 는 **일부러 화이트리스트 밖**이다. patch 로 통과시키면 배열을 **통째 교체**하게 되어
|
|
353
|
+
* 동시 편집이 서로를 덮는다 — 호스트는 멤버십을 `$push`/`$pull` 원소 단위로 처리하는 전용 verb 를
|
|
354
|
+
* 갖고 있으므로(`group.addMember`/`removeMember`) 그쪽이 정상 경로다.
|
|
355
|
+
*/
|
|
356
|
+
| {
|
|
357
|
+
code: 'group-patch-unknown-key';
|
|
358
|
+
opIndex: number;
|
|
359
|
+
handle: GroupHandle;
|
|
360
|
+
keys: string[];
|
|
361
|
+
} | {
|
|
362
|
+
code: 'attribute-fk-managed';
|
|
363
|
+
opIndex: number;
|
|
364
|
+
entity: Handle;
|
|
365
|
+
handle: Handle;
|
|
366
|
+
association: {
|
|
367
|
+
from: string;
|
|
368
|
+
to: string;
|
|
369
|
+
};
|
|
178
370
|
};
|
|
179
371
|
export type ResolveResult = {
|
|
180
372
|
ok: true;
|
|
@@ -193,8 +385,4 @@ export interface ResolverOptions {
|
|
|
193
385
|
*/
|
|
194
386
|
embeddableCatalog?: EmbeddableCatalog;
|
|
195
387
|
}
|
|
196
|
-
/**
|
|
197
|
-
* 심볼릭 op 배치를 OpShape 배치로 해소한다.
|
|
198
|
-
* 전부 해소되면 `{ok:true, ops}`, 하나라도 실패하면 `{ok:false, errors}`(모든 실패 누적 — 에이전트가 한 번에 재질의).
|
|
199
|
-
*/
|
|
200
388
|
export declare function resolveSymbolicOps(model: LogicalModel, ops: SymbolicOp[], options?: ResolverOptions): ResolveResult;
|
|
@@ -5,10 +5,17 @@ import { ClassStereotype, MultiLangText, Multiplicity, OperationVisibility } fro
|
|
|
5
5
|
* 속성: 소속 엔티티 내 속성명(`name`) 또는 컬럼 물리명(`dbAttrs[].physicalName`) — 둘 다 엔티티 내 유일(CHK-NAME-1/2).
|
|
6
6
|
*/
|
|
7
7
|
export interface Handle {
|
|
8
|
-
/** 매칭할 이름/물리명. */
|
|
8
|
+
/** 매칭할 이름/물리명. `by:'modelId'`면 modelId 원문. */
|
|
9
9
|
ref: string;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* 매칭 키 고정. 미지정 시 대상별 기본 순서로 시도(엔티티=물리명→논리명, 속성=논리명→물리명).
|
|
12
|
+
*
|
|
13
|
+
* ★`'modelId'`는 **모호 해소 탈출구**다 — 이름·물리명이 둘 다 겹쳐 주소지정이 불가능한 대상이 실재한다
|
|
14
|
+
* (임베더블은 `table`이 없어 물리명 차원 자체가 없고, 같은 테이블에 매핑된 동명 엔티티도 실 데이터에
|
|
15
|
+
* 있다). resolver가 모호를 보고할 때 후보를 판별 맥락과 함께 돌려주므로, 사람이 그중 하나를 골라
|
|
16
|
+
* 이 키로 확정한다. 평시엔 쓰지 않는다(핸들 설계 취지는 "LLM은 사람 용어로만 말한다").
|
|
17
|
+
*/
|
|
18
|
+
by?: 'physicalName' | 'name' | 'modelId';
|
|
12
19
|
}
|
|
13
20
|
/**
|
|
14
21
|
* 관계 지정 — 관계는 무명이라 양 끝 엔티티로 주소한다.
|
|
@@ -143,6 +150,24 @@ export interface AssocSpec {
|
|
|
143
150
|
/** end2 composition(부모가 자식 생명주기 소유). */
|
|
144
151
|
composition?: boolean;
|
|
145
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* 그룹 지정 핸들 — 그룹엔 테이블(물리명) 차원이 없어 `Handle` 과 `by` 축이 다르다.
|
|
155
|
+
*
|
|
156
|
+
* 실측(프로덕션 v2 29문서·그룹 220): **`name` 은 220/220 전량 보유**하고 `packageName` 은 177(80%)만
|
|
157
|
+
* 있다. 그래서 기본은 엔티티와 같은 관용(물리 정체성 우선 → 논리 보조)으로 `packageName` → `name`
|
|
158
|
+
* 순서를 시도하되, packageName 이 없는 43개는 자연히 name 으로 잡힌다.
|
|
159
|
+
*
|
|
160
|
+
* ★`name` 은 `MultiLangText` 라 **어느 로케일 값이든 일치하면 매칭**한다(사람이 부르는 이름이 로케일마다
|
|
161
|
+
* 다를 수 있고, 어느 하나를 정본으로 고르면 나머지 로케일로 부른 요청이 조용히 not-found 가 된다).
|
|
162
|
+
* ★문서 내 중복이 실재한다(실측 packageName 4종·ko 이름 3종) ⇒ 모호는 **후보를 동봉해 보고**하고
|
|
163
|
+
* `by:'modelId'` 로 확정한다(엔티티 축과 같은 형태).
|
|
164
|
+
*/
|
|
165
|
+
export interface GroupHandle {
|
|
166
|
+
/** 매칭할 패키지명/이름. `by:'modelId'`면 modelId 원문. */
|
|
167
|
+
ref: string;
|
|
168
|
+
/** 매칭 키 고정. 미지정 시 packageName → name 순서. */
|
|
169
|
+
by?: 'packageName' | 'name' | 'modelId';
|
|
170
|
+
}
|
|
146
171
|
/**
|
|
147
172
|
* group.add 페이로드 — 엔티티 멤버를 묶는 논리 그룹(레거시 LogicalGroup). resolver가 modelId 발급 +
|
|
148
173
|
* 멤버 핸들을 entity modelId(`memberEntityRefs`)로 해소. 그룹 멤버십은 값 포함이 아닌 **id 참조**(엔티티는
|
|
@@ -283,10 +308,34 @@ export type SymbolicOp = {
|
|
|
283
308
|
} | {
|
|
284
309
|
kind: 'group.add';
|
|
285
310
|
spec: GroupSpec;
|
|
311
|
+
} | {
|
|
312
|
+
kind: 'group.update';
|
|
313
|
+
group: GroupHandle;
|
|
314
|
+
patch: Record<string, unknown>;
|
|
315
|
+
} | {
|
|
316
|
+
kind: 'group.remove';
|
|
317
|
+
group: GroupHandle;
|
|
318
|
+
} | {
|
|
319
|
+
kind: 'group.addMember';
|
|
320
|
+
group: GroupHandle;
|
|
321
|
+
entity: Handle;
|
|
322
|
+
} | {
|
|
323
|
+
kind: 'group.removeMember';
|
|
324
|
+
group: GroupHandle;
|
|
325
|
+
entity: Handle;
|
|
286
326
|
};
|
|
287
327
|
/**
|
|
288
328
|
* v1이 다루는 심볼릭 op 종류의 닫힌 집합(단일 출처). resolver·JSON Schema(`schema.ts`)가 공유한다.
|
|
289
329
|
* 아래 컴파일타임 단언이 이 튜플과 `SymbolicOp['kind']`의 일치를 강제 — 한쪽만 늘리면 타입 에러.
|
|
290
330
|
*/
|
|
291
|
-
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", "attribute.reorder", "operation.reorder", "group.add"];
|
|
331
|
+
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", "attribute.reorder", "operation.reorder", "group.add", "group.update", "group.remove", "group.addMember", "group.removeMember"];
|
|
292
332
|
export type SymbolicOpKind = (typeof SYMBOLIC_OP_KINDS)[number];
|
|
333
|
+
/**
|
|
334
|
+
* `AttributeType`(core) 런타임 목록 — JSON Schema enum과 resolver 값 검증이 공유한다.
|
|
335
|
+
* SoT는 core의 union이고 아래 단언이 양방향 일치를 강제한다(`SYMBOLIC_OP_KINDS`와 같은 형태).
|
|
336
|
+
*
|
|
337
|
+
* ★목록이 있다고 patch로 자유 전환이 되는 건 아니다 — `attrType`은 동반 구조에서 파생되는 성격이라
|
|
338
|
+
* (RELATION_*=관계 소유, EMBED_*=`embedded`/카탈로그 type, DIVIDER=dbAttrs 0·type '') resolver가
|
|
339
|
+
* *전환*은 거부하고 라운드트립 에코만 통과시킨다. 이 목록의 쓸모는 **미지 값 판별**이다.
|
|
340
|
+
*/
|
|
341
|
+
export declare const ATTRIBUTE_TYPES: readonly ["NORMAL", "RELATION_OWN", "RELATION_REF", "EMBED_OWN", "EMBED_REF", "EMBED_PREDEF", "DIVIDER"];
|
package/dist/command/op.d.ts
CHANGED
|
@@ -31,6 +31,25 @@ export interface OpShape {
|
|
|
31
31
|
*/
|
|
32
32
|
incidental?: boolean;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* update patch의 **clear 표기 정규화** — 값이 `undefined`인 키(= 그 필드를 부재로 되돌린다)를 `null`로 바꾼다.
|
|
36
|
+
*
|
|
37
|
+
* ★왜 필요한가: op은 JSON으로 호스트에 전송되는데 `JSON.stringify`가 **값이 undefined인 키를 통째로 드롭**한다.
|
|
38
|
+
* 그러면 그 키가 호스트 `$set`에 도달하지 못해 **로컬은 지웠는데 서버는 그대로**인 괴리가 남는다. 발동 경로는
|
|
39
|
+
* 둘 다 일상 편집이다 —
|
|
40
|
+
* ① **undo**: 원래 비어 있던 필드를 채운 뒤 되돌리면 invert patch가 `{field: undefined}`가 된다
|
|
41
|
+
* (`updateEntity` 등이 `old[k] = e[k]`로 캡처하므로 부재 필드는 undefined로 잡힌다).
|
|
42
|
+
* ② **명시적 clear**: 컨트롤러가 `{legacyEmbedRaw: undefined}`·`{attributeRef: undefined}`·
|
|
43
|
+
* 임베드 단일 전환의 `{collectionType/collectionTable/orderColumn: undefined}`처럼 "비움"을 patch로 낸다.
|
|
44
|
+
*
|
|
45
|
+
* `null`은 wire를 통과하고 호스트가 `$set field: null`로 집행한다. 이 리포는 **null ≡ 부재** 규약을 이미
|
|
46
|
+
* 쓰고 있어(`synthesizeRestoreOps`의 `fieldPatch`가 같은 이유로 clear를 null로 표기 + `deepEqual`이 둘을 동치로
|
|
47
|
+
* 비교, FK 전파 `diffSyncedFields`도 저장 null을 undefined로 정규화해 비교) 저장값 null은 부재와 같게 읽힌다.
|
|
48
|
+
*
|
|
49
|
+
* ★**얕은 정규화만** 한다 — 중첩 객체(`jpaAttrs`·`jpa`·`dbAttrs`)는 호스트가 **통째로 `$set`**하므로 내부의
|
|
50
|
+
* undefined 키는 드롭돼도 결과가 "그 키 부재"라 의미가 그대로 보존된다. 최상위 키만 `$set` 경로로 1:1 매핑된다.
|
|
51
|
+
*/
|
|
52
|
+
export declare function toWirePatch(patch: object): Record<string, unknown>;
|
|
34
53
|
/**
|
|
35
54
|
* apply 이후 호출 계약(계약 1)의 산출 — memento가 채워진 상태에서 do/undo 양방향 op.
|
|
36
55
|
* invert는 *역방향 forward op*(계약 2) — 같은 op의 역값이 아니라 역연산 op.
|
|
@@ -62,7 +81,22 @@ export interface PersistedOpBatch {
|
|
|
62
81
|
/** 멱등 키 — 재시도 중복 적용 차단(호스트가 직전 결과 반환). */
|
|
63
82
|
clientOpId: string;
|
|
64
83
|
}
|
|
65
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* 하드 프리즈 사유 — **방출 주체가 kind마다 다르다**(선언과 실제를 맞춘 기록, 2026-08-15 실측).
|
|
86
|
+
*
|
|
87
|
+
* - `rev` — **호스트가 내는 유일한 kind**. CAS filter 불일치(`matchedCount:0`)를 전부 이걸로 접는다.
|
|
88
|
+
* 호스트는 rev 불일치와 layout.version 불일치를 구분하지 않으므로(단일 updateOne filter) 두 원인이
|
|
89
|
+
* 모두 여기로 들어온다. = "다른 곳에서 이미 변경됨".
|
|
90
|
+
* - `layout` — **현재 어떤 writer도 방출하지 않는다**(예약). 위 사유로 호스트가 `rev`로 접고, 클라도
|
|
91
|
+
* 합성하지 않는다. 소비처(배너·호스트 watch)는 `rev`와 같은 경로로 처리한다.
|
|
92
|
+
* - `missing` — **클라이언트 합성 전용 = op 전송 실패**(4xx/5xx/네트워크로 transport가 throw).
|
|
93
|
+
* 동시편집이 아니라 혼자 편집 중에도 발생하므로 "다른 사용자가 변경"으로 안내하면 오진이다
|
|
94
|
+
* (`opConflictNotice`·호스트 watch가 이 kind로 분기해 손실 고지 후 재적재를 확인받는다).
|
|
95
|
+
* ★이름이 "대상 소실"처럼 읽히지만 그 의미로 쓰인 적이 없다 — 호스트 op 어휘엔 대상 소실 응답이 없다.
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ 새 kind를 늘리기 전에 **소비처 분기**(`opConflictNotice` + 호스트 `opConflict` watch)를 함께 넓힐 것.
|
|
98
|
+
* 넓히지 않으면 새 kind가 else 분기로 떨어져 조용히 다른 복구 경로를 탄다(CC-9의 무신호 실패 모드).
|
|
99
|
+
*/
|
|
66
100
|
export interface OpConflict {
|
|
67
101
|
kind: 'rev' | 'layout' | 'missing';
|
|
68
102
|
ref?: ModelId;
|
package/dist/command/opSync.d.ts
CHANGED
|
@@ -40,14 +40,24 @@ export interface OpSyncOptions {
|
|
|
40
40
|
newClientOpId?: () => string;
|
|
41
41
|
/** 충돌(또는 전송 실패)로 하드 프리즈 진입 시 — controller가 editable=false + reload UX 연동. */
|
|
42
42
|
onFreeze?: (conflict: OpConflict) => void;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* 전송 거부(네트워크 등) 통지 — 진단용(프리즈는 별도 onFreeze로).
|
|
45
|
+
* 재시도하는 경우 **시도마다** 발화한다(회복돼도 원인을 추적할 수 있어야 하므로).
|
|
46
|
+
*/
|
|
44
47
|
onError?: (err: unknown) => void;
|
|
48
|
+
/**
|
|
49
|
+
* 전송 실패 재시도 지연(ms) — 배열 길이가 곧 재시도 횟수. 기본 `[400, 1200]`(총 3회 시도).
|
|
50
|
+
* `[]`이면 재시도 없이 즉시 프리즈. 같은 배치를 **같은 `clientOpId`로** 재전송하므로 호스트
|
|
51
|
+
* 멱등 ring이 중복 적용을 흡수한다(응답만 유실된 경우 직전 결과를 그대로 돌려받는다).
|
|
52
|
+
*/
|
|
53
|
+
retryDelaysMs?: number[];
|
|
45
54
|
}
|
|
46
55
|
export declare class OpSyncAdapter {
|
|
47
56
|
private readonly opts;
|
|
48
57
|
private readonly cache;
|
|
49
58
|
private readonly newClientOpId;
|
|
50
59
|
private readonly debounceMs;
|
|
60
|
+
private readonly retryDelaysMs;
|
|
51
61
|
private readonly emissions;
|
|
52
62
|
private open;
|
|
53
63
|
private debounceHandle;
|
package/dist/command/stack.d.ts
CHANGED
|
@@ -32,7 +32,17 @@ export declare class CommandStack {
|
|
|
32
32
|
sealCoalesce(): void;
|
|
33
33
|
get canUndo(): boolean;
|
|
34
34
|
get canRedo(): boolean;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* 저장 시점 표시 — 이후 dirty 판정 기준.
|
|
37
|
+
*
|
|
38
|
+
* ★저장 경계는 곧 **undo 단위 경계**여야 한다. 봉인하지 않으면 직후의 동일 `coalesceKey` 명령이
|
|
39
|
+
* savePoint가 가리키는 스택 top을 **슬롯째 교체**해(execute의 coalesce 분기) savePoint 객체가
|
|
40
|
+
* 스택에서 사라진다 — 그러면 `isDirty`가 다시 false가 될 수 없어 **영구 dirty로 고착**된다.
|
|
41
|
+
* 플래그 문제가 아니라 경계 문제다: 병합된 단위의 invert는 그룹 시작으로 되돌아가므로 저장 지점
|
|
42
|
+
* 상태 자체가 undo로 **도달 불가**해진다. 근거는 op 어댑터의 flush 봉인과 동일하다(영속 경계
|
|
43
|
+
* 이후 동일 키는 새 단위). op-mode에서는 `controller.markSaved`가 `adapter.flush()`로 이미
|
|
44
|
+
* 봉인하지만, 그건 전송 경계라는 다른 이유의 우연한 커버라 여기서 구조적으로 닫는다.
|
|
45
|
+
*/
|
|
36
46
|
markSavePoint(): void;
|
|
37
47
|
/** 마지막 저장 이후 변경 여부 (undo로 저장 지점에 정확히 돌아오면 clean) */
|
|
38
48
|
get isDirty(): boolean;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { Multiplicity } from './types';
|
|
2
2
|
/** end2 multiplicity가 컬렉션(0..* / 1..*)인가 — 코드젠의 @OneToMany vs @OneToOne 분기와 동일 기준. */
|
|
3
3
|
export declare function isCollectionMultiplicity(m: Multiplicity): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* 최소 개수가 0인가(0..1 / 0..*) — 레거시 `Multiplicity.isOptional()` 이식.
|
|
6
|
+
*
|
|
7
|
+
* FK nullability의 파생원이다: 부모 end(end1)가 optional이 아니면(=`1`) 자식은 부모 없이 존재할 수
|
|
8
|
+
* 없으므로 FK는 NOT NULL이다. 레거시 코드젠이 `@ManyToOne(optional=…)`·`@JoinColumn(nullable=…)`을
|
|
9
|
+
* 정확히 이 값으로 방출했다(bluework-im `EntityJavaFile:900,932,1072,1120`).
|
|
10
|
+
*
|
|
11
|
+
* 자식 end(end2)의 `0..*` vs `1..*`는 이 축이 아니다 — "부모가 자식을 최소 1개 가진다"는 자식 테이블의
|
|
12
|
+
* 컬럼 제약으로 표현할 수 없다(레거시도 여기서 nullability를 뽑지 않는다).
|
|
13
|
+
*
|
|
14
|
+
* 판정을 "required 목록의 여집합"으로 쓴다(`!== '1' && !== '1..*'`) — union 밖 값(레거시는 다중성
|
|
15
|
+
* 미설정을 null로 허용)이 흘러들어도 optional=nullable로 안전하게 떨어진다.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isOptionalMultiplicity(m: Multiplicity): boolean;
|
|
4
18
|
/**
|
|
5
19
|
* 다중성 표시 기호(0..1 / 1 / 0..* / 1..*) — 영속용 enum(`*_INSTANCE(S)`)의 단일 표시 SoT.
|
|
6
20
|
* 기호는 언어 무관 UML 표기라 i18n 대상이 아니다. 캔버스 관계선 라벨·인스펙터 select·mermaid
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Attribute, DbColumn, EmbeddableCatalog, Entity, LogicalModel } from './types';
|
|
2
|
+
/** 상속 해소 컨텍스트 — `ValidationContext`·`DdlContext`와 동형(카탈로그는 호스트 주입). */
|
|
3
|
+
export interface ColumnResolveContext {
|
|
4
|
+
/** EMBED_PREDEF 서브컬럼의 선언값 해석용. 미주입 시 그 축만 graceful degrade(빈 값). */
|
|
5
|
+
embeddableCatalog?: EmbeddableCatalog;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 컬럼의 **실효 값** — 자기 값이 비어 있으면 선언원에서 상속. 문자열은 빈값 폴백(`||`), 수치는
|
|
9
|
+
* null/undefined 폴백(`??`)이다(저장 라운드트립이 미설정 수치를 `null`로 바꾸므로 — propagation `nn()` 동형).
|
|
10
|
+
*
|
|
11
|
+
* 축별 폴백(값 단위)이지 슬롯 통째 대체가 아니다: 사용처가 물리명만 override하고 타입은 상속하는 형태
|
|
12
|
+
* (@AttributeOverride의 실제 용법)가 지배적이라, 한 축을 채웠다고 나머지까지 자기 값으로 강제하면
|
|
13
|
+
* 대다수 정상 데이터가 도로 빈 값이 된다. 캔버스 `EntityNode.effectiveAttr`가 쓰던 시맨틱과 동일.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveEffectiveColumn(attr: Attribute, col: DbColumn, index: number, entity: Entity | undefined, model: LogicalModel, ctx?: ColumnResolveContext): DbColumn;
|
|
16
|
+
/**
|
|
17
|
+
* 실효 물리명 — override(비어있지 않은 physicalName)가 있으면 그 값, 없으면 상속 기본값.
|
|
18
|
+
* 컬럼 충돌 판정(CHK-NAME-2)은 raw `''`가 아니라 이 값으로 해야 한다: 한 테이블에 같은 임베더블
|
|
19
|
+
* (예: Money)이 여러 번 쓰이고 서브컬럼을 비워두면 전부 같은 기본명을 상속해 실제 컬럼 충돌이 나고,
|
|
20
|
+
* FK도 물리명 미지정 시 부모 PK 컬럼명을 그대로 쓰므로 자식 자기 컬럼과 충돌할 수 있다.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveColumnPhysicalName(attr: Attribute, col: DbColumn, index: number, entity: Entity | undefined, model: LogicalModel, ctx?: ColumnResolveContext): string;
|
|
23
|
+
/**
|
|
24
|
+
* 서브컬럼의 **선언 필드명** — 다중 컬럼 속성을 컬럼 단위로 표시할 때 각 슬롯이 선언원의 어느 필드인지.
|
|
25
|
+
*
|
|
26
|
+
* 축을 EMBED 두 종으로 한정하는 이유: 이 이름은 장식이 아니라 **`embeddableOverrides`의 키와 같은 축**이다
|
|
27
|
+
* (AI 경로가 `{field:"currency", …}`로 서브컬럼을 지목한다 — `agent/resolver.ts`). 그 op이 EMBED_PREDEF
|
|
28
|
+
* 에만 적용되므로 카탈로그 필드명이 곧 계약이고, EMBED_OWN 은 임베더블 소스 속성명(JPA 필드명)이 그에
|
|
29
|
+
* 대응한다. FK 등 나머지는 슬롯을 지목하는 어휘가 없고 **물리명이 곧 식별자**라 undefined 를 돌려준다
|
|
30
|
+
* (호출자가 물리명으로 폴백한다 — 없는 이름을 지어내지 않는다).
|
|
31
|
+
*
|
|
32
|
+
* ⚠️평탄화 순서는 `inheritedSlotOf`의 EMBED_OWN 분기와 **반드시 같아야** 한다(선언순 dbAttrs 이어붙이기).
|
|
33
|
+
* 어긋나면 라벨과 값이 서로 다른 슬롯을 가리키는 조용한 오답이 된다. 소스 속성이 다중 컬럼이면 그 이름이
|
|
34
|
+
* 슬롯 여럿에 반복되는데, 그것이 실제 구조(한 필드가 여러 컬럼)라 구분자를 덧붙이지 않는다.
|
|
35
|
+
*/
|
|
36
|
+
export declare function slotFieldName(attr: Attribute, index: number, model: LogicalModel, ctx?: ColumnResolveContext): string | undefined;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DbColumn, LogicalModel, ModelId } from './types';
|
|
2
|
+
/** owner 표시 속성 한 건의 슬롯 보충 계획 — 컨트롤러가 `updateAttribute { dbAttrs }`로 커밋한다. */
|
|
3
|
+
export interface EmbedSlotFill {
|
|
4
|
+
/** 표시 속성을 보유한 소유자 엔티티. */
|
|
5
|
+
entityId: ModelId;
|
|
6
|
+
/** `EMBED_OWN` 표시 속성. */
|
|
7
|
+
attributeId: ModelId;
|
|
8
|
+
/** 보충 후 전체 슬롯 목록(기존 슬롯 그대로 + 꼬리 빈 슬롯). */
|
|
9
|
+
dbAttrs: DbColumn[];
|
|
10
|
+
/** 보충한 슬롯 수(로그·테스트 가독용). */
|
|
11
|
+
added: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `embeddableRef`를 사용하는 모든 owner의 슬롯을 선언 컬럼 수까지 채우는 계획.
|
|
15
|
+
* 대상이 없거나 이미 맞으면 빈 배열(무변경). 컬렉션 EMBED(@ElementCollection)는 컬럼이 임베더블에
|
|
16
|
+
* 남고 owner 슬롯을 비우는 것이 정상이라 제외한다(`controller.setEmbedCardinality`가 `dbAttrs: []`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function planEmbedSlotFill(model: LogicalModel, embeddableRef: ModelId, genId?: () => ModelId): EmbedSlotFill[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Entity, LogicalModel } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 패키지 동일성 키. `kind` 를 함께 들고 다니는 이유는 **비교 가능성**이다 — 명시 패키지는 FQN 전체이고
|
|
4
|
+
* 그룹 세그먼트는 조각이라, 서로 다른 종류끼리는 같은지 다른지 판정할 수 없다(조각만으로는 base·layer 를
|
|
5
|
+
* 모른다). 그 경우를 조용히 "다르다"로 처리하면 실제 충돌을 놓치므로 호출부가 구분할 수 있게 남긴다.
|
|
6
|
+
*/
|
|
7
|
+
export type EntityPackageKey = {
|
|
8
|
+
kind: 'explicit';
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
/** 그룹 세그먼트. `''` = 세그먼트 생략(그룹 미소속 또는 그룹에 packageName 없음). */
|
|
12
|
+
| {
|
|
13
|
+
kind: 'group';
|
|
14
|
+
value: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function entityPackageKey(entity: Entity, logical: LogicalModel): EntityPackageKey;
|
|
17
|
+
/**
|
|
18
|
+
* 두 엔티티가 **서로 다른 패키지에 놓인다는 것이 증명되는가**.
|
|
19
|
+
*
|
|
20
|
+
* 이름은 일부러 `samePackage` 의 부정이 아니라 *증명 가능성*으로 잡았다 — 판정 불가(키 종류가 다름)를
|
|
21
|
+
* "다르다"로 흘리면 면제가 조용히 넓어져 **실제 충돌이 무신호**가 된다. 면제는 적극적으로 증명될 때만
|
|
22
|
+
* 준다(그 외에는 종전대로 신고 = 현행 동작 보존).
|
|
23
|
+
*/
|
|
24
|
+
export declare function provablyDifferentPackage(a: Entity, b: Entity, logical: LogicalModel): boolean;
|
package/dist/core/fkDerived.d.ts
CHANGED
|
@@ -2,9 +2,29 @@ import { Association, Attribute, DbColumn, Entity } from './types';
|
|
|
2
2
|
/**
|
|
3
3
|
* FK 유래 컬럼 = 물리 컬럼을 보유한 관계 속성(RELATION* + dbAttrs). 형상(dataType/length/scale/
|
|
4
4
|
* physicalName/notNull 등)은 부모 식별자(PK) + 관계 identifying에서 파생된다.
|
|
5
|
-
* derivedFrom이 아니라 이 불변식을 쓰는 이유: derivedFrom은
|
|
6
|
-
*
|
|
5
|
+
* derivedFrom이 아니라 이 불변식을 쓰는 이유: derivedFrom은 관계·전파가 심는 링크라 그 자체가 결손일 수
|
|
6
|
+
* 있는 반면, "관계 속성이 물리 컬럼을 갖는다"는 형상은 FK의 정의 자체다.
|
|
7
|
+
* (과거 이 자리엔 "derivedFrom은 영속 데이터엔 0건"이라는 근거가 적혀 있었으나 **실측과 어긋난다** —
|
|
8
|
+
* BNKR_SALES 32문서에서 FK 속성 666건 전량이 derivedFrom을 보유한다. v2는 문서에 그대로 저장되고,
|
|
9
|
+
* v1은 어댑터 `assocFromHydrated`(diagramAdapter.ts:350)가 하이드레이션 시 합성한다. 두 판정은 이
|
|
10
|
+
* 데이터에서 100% 일치하므로 동작 차이는 없다.)
|
|
7
11
|
* RELATION_REF(inverse nav, dbAttrs=0)는 물리 컬럼이 없어 제외.
|
|
12
|
+
*
|
|
13
|
+
* ── FK 판정 술어 인벤토리(리포 전역) ─────────────────────────────────────────
|
|
14
|
+
* 이 술어 말고도 "FK인가"를 묻는 자리가 몇 곳 더 있고, **의도적으로 다르다**. 새 판정을 추가하기 전에
|
|
15
|
+
* 아래 중 하나로 답할 수 있는지 먼저 확인할 것(중복이면 여기로 위임).
|
|
16
|
+
*
|
|
17
|
+
* | 자리 | 술어 | 왜 다른가 |
|
|
18
|
+
* |---|---|---|
|
|
19
|
+
* | `isFkDerivedColumn`(여기) | RELATION* && dbAttrs>0 | 뷰 잠금·형상 해소. `findFkOwningAssociation`이 위임 |
|
|
20
|
+
* | `attributeOrder.isPhysicalFk` | derivedFrom && dbAttrs>0 | 표시 순서 정렬. **강등 이후** 호출 전제라 `derivedFrom`이 기준(경계 강등된 컬럼은 제자리에 남아야 한다) |
|
|
21
|
+
* | `mermaid.isForeignKey` | RELATION_OWN‖RELATION_REF‖derivedFrom | ER **FK 마커**는 nav(dbAttrs=0)까지 표시 대상이라 컬럼 보유를 안 본다 |
|
|
22
|
+
* | `ddl.ts` FK 제약 방출 | derivedFrom 단독 | 부모 컬럼을 알아야 REFERENCES를 쓸 수 있어 링크가 필수 |
|
|
23
|
+
* | `propagation` `currentFk` | derivedFrom 단독 | 판정이 아니라 reconcile diff의 **매칭 키** |
|
|
24
|
+
*
|
|
25
|
+
* 실 데이터에서 두 축(RELATION* / derivedFrom)은 100% 일치하므로(위 문단) 이 차이는 대체로 관측되지
|
|
26
|
+
* 않는다. 발산했던 유일한 경로는 mermaid 임포트가 관계 없는 RELATION_OWN을 만들던 것이고
|
|
27
|
+
* (`parseMermaid.wireForeignKeyColumns` 주석), 그건 발생원에서 닫았다.
|
|
8
28
|
*/
|
|
9
29
|
export declare function isFkDerivedColumn(attr: Attribute): boolean;
|
|
10
30
|
/**
|