@g1cloud/entity-modeler-next 5.2.50 → 5.2.52
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/dist/agent/resolver.d.ts +96 -7
- package/dist/core/embedGraph.d.ts +19 -0
- package/dist/editor/association.d.ts +5 -0
- package/dist/entity-modeler-next.css +1 -1
- package/dist/entity-modeler.js +9005 -8837
- package/dist/entity-modeler.umd.cjs +34 -34
- package/dist/i18n/ko.d.ts +2 -0
- package/package.json +1 -1
package/dist/agent/resolver.d.ts
CHANGED
|
@@ -82,20 +82,74 @@ export type ResolveError = {
|
|
|
82
82
|
* `end.attributeRef` 셋이 EMBED 판정의 유일한 근거다 — `core/sharedColumn.sharedColumnScopeEntities`·
|
|
83
83
|
* `ddl.ts` 평탄화·코드젠 `@Embedded` 가 전부 그것을 본다).
|
|
84
84
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* 실측
|
|
85
|
+
* ★★**2026-09-13 — 범위가 «to 가 아니라 from» 으로 좁았다**(AA-5 S3 구현). 종전엔 end 중 하나라도
|
|
86
|
+
* 임베더블이면 전부 거부했는데, 그때의 근거는 *「resolver 에 EMBED 관계 생성 어휘가 없다」* 였고 그래서
|
|
87
|
+
* 통과시키면 *항상* 「스테레오타입은 임베더블인데 관계는 일반」 무신호 형상이 됐다(공유 통화 스코프가
|
|
88
|
+
* 자기 자신으로 좁아져 `SHARED_REF` 후보 0 · DDL 평탄화 없음 · 코드젠 `@Embedded` 없음 ·
|
|
89
|
+
* 실측 2026-08-25 프로덕션 4건 중 1건 증상·3건 잠복). **그 어휘가 생겼으므로 거부가 방출로 바뀌었다** —
|
|
90
|
+
* 종전 주석이 *「어휘가 생기면 거부 대신 EMBED 방출로 바뀔 자리」* 라 예고한 그 자리다.
|
|
90
91
|
*
|
|
91
|
-
* ⇒
|
|
92
|
-
*
|
|
92
|
+
* ⇒ 남는 거부는 **`from` 만 임베더블**인 경우뿐이다. 그것은 임베더블을 **FK 부모**로 세우는 것이고
|
|
93
|
+
* 임베더블엔 테이블도 PK 도 없어 전파할 식별자가 없다 ⇒ 만들 관계가 존재하지 않는다.
|
|
94
|
+
* (`to` 가 임베더블이면 — `from` 이 임베더블이어도 — EMBED 이고, 그 연쇄 임베드는 정당하다.)
|
|
93
95
|
*/
|
|
94
96
|
| {
|
|
95
97
|
code: 'embeddable-relation-unsupported';
|
|
96
98
|
opIndex: number;
|
|
97
99
|
handle: AssocHandle;
|
|
98
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* EMBED 로 분기된 `association.add` 인데 `AssocSpec` 에 **임베드에 성립하지 않는 필드**가 실려 있다.
|
|
103
|
+
*
|
|
104
|
+
* ★**조용히 무시하지 않는 이유** — 이 리포가 반복해 대가를 치른 「조용한 반쪽 적용」이다. `identifying`
|
|
105
|
+
* (식별 관계 = 부모 PK 를 자식 PK 로 전파)과 `composition`·`fromMultiplicity` 는 **FK 관계의 축**이고
|
|
106
|
+
* 임베드엔 대응물이 없다(임베드의 소유 관계는 `end2.composition=true` 고정이고, 카디널리티 권위는
|
|
107
|
+
* **임베더블을 가리키는 end** 하나다 — `editor/association.setEmbedCardinality` 의 결정 3).
|
|
108
|
+
* 보내 놓고 안 먹히면 호출자는 *「먹었다」* 고 믿는다 ⇒ 거부해서 그 자리를 지목한다.
|
|
109
|
+
*
|
|
110
|
+
* ⚠️**`toMultiplicity` 는 여기 들어오지 않는다** — 그쪽이 곧 카디널리티 축이다(단일 `@Embedded` ↔
|
|
111
|
+
* 컬렉션 `@ElementCollection`). 부적합이 아니라 **유일한 적용 필드**다.
|
|
112
|
+
*/
|
|
113
|
+
| {
|
|
114
|
+
code: 'embed-spec-inapplicable';
|
|
115
|
+
opIndex: number;
|
|
116
|
+
handle: AssocHandle;
|
|
117
|
+
fields: string[];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 임베더블이 **자기 자신을 임베드**하려 했다 — 구조적으로 성립하지 않는다
|
|
121
|
+
* (`class Addr { @Embedded Addr addr; }` = 무한 재귀).
|
|
122
|
+
*
|
|
123
|
+
* ★**연쇄 임베드(임베더블 → «다른» 임베더블)와 갈라야 한다** — 그쪽은 **정당하고 실재한다**
|
|
124
|
+
* (프로덕션 v2 43문서 실측: 연쇄 **3** · 자기 임베드 **0** — `ApplyPriceDcPromo.dcRule → PromoDcRule` 등).
|
|
125
|
+
*
|
|
126
|
+
* ★★**판정은 이제 `core/embedGraph.findEmbedCycle` 이 한다** — GUI 와 «같은 술어»다. 종전엔 이 자리가
|
|
127
|
+
* `from.modelId === to.modelId` 단순 비교였고 GUI 는 임베더블↔임베더블을 통째로 무시해서, **두 경로가
|
|
128
|
+
* 서로 다른 것을 막고 둘 다 간접 순환(A→B→A)을 못 봤다**(자기 비교는 1단만 본다). 그 GUI 쪽 차단은
|
|
129
|
+
* 해제됐고(연쇄는 정당), 남은 거부가 이 코드와 아래 `embed-cycle-unsupported` 둘이다.
|
|
130
|
+
*/
|
|
131
|
+
| {
|
|
132
|
+
code: 'embed-self-unsupported';
|
|
133
|
+
opIndex: number;
|
|
134
|
+
handle: AssocHandle;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 임베드가 **순환**을 이룬다 — 자기 임베드의 «간접» 형태(A 가 B 를 품고 B 가 A 를 품는다).
|
|
138
|
+
*
|
|
139
|
+
* ★**코드를 자기 임베드와 가르는 이유**는 호출자가 **고칠 자리가 다르기** 때문이다: 자기 임베드는
|
|
140
|
+
* 보낸 op 자체가 잘못이지만, 순환은 **이미 저장된 반대 방향 임베드**가 상대편이라 그쪽을 먼저 정리해야
|
|
141
|
+
* 한다. 그래서 `path` 에 순환을 이루는 엔터티 **이름**을 순서대로 담는다(모호할 때 사람이 고를 수 있는
|
|
142
|
+
* 지목 수단 — modelId 나열만으론 어느 자리를 지울지 못 고른다).
|
|
143
|
+
*
|
|
144
|
+
* ⚠️**같은 배치 안에서 나눠 보내는 우회도 막는다** — `A→B` 와 `B→A` 를 두 op 으로 보내면 각 op 은
|
|
145
|
+
* 저장 전 모델만 보고 무해해 보인다. 선행 op 이 만든 간선을 `pending` 으로 함께 넘겨 판정한다.
|
|
146
|
+
*/
|
|
147
|
+
| {
|
|
148
|
+
code: 'embed-cycle-unsupported';
|
|
149
|
+
opIndex: number;
|
|
150
|
+
handle: AssocHandle;
|
|
151
|
+
path: string[];
|
|
152
|
+
}
|
|
99
153
|
/**
|
|
100
154
|
* `association.add` 의 end 중 하나가 `SERIALIZED_TYPE` — **거부 사유가 임베더블과 다르다**.
|
|
101
155
|
*
|
|
@@ -632,6 +686,41 @@ export type ResolveError = {
|
|
|
632
686
|
from: string;
|
|
633
687
|
to: string;
|
|
634
688
|
};
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* `attribute.update` 가 **`NORMAL` 속성의 컬럼을 둘 이상으로** 만들려 했다 — 컬럼 «개수» 축 가드(AA-5 S5).
|
|
692
|
+
*
|
|
693
|
+
* ★★**형제 게이트 둘이 막던 것과 «같은 상태»인데 문이 달랐다**: `attribute-attrtype-unsupported` 는
|
|
694
|
+
* `attrType` 을 직접 준 경우를, `attribute-type-conversion-unsupported` 는 `type` 만 준 경우를 막는다.
|
|
695
|
+
* 그런데 **`dbAttrs` 만 늘리면** `attrType`·`type` 이 그대로라 **둘 다 비켜간다** — 실증(2026-09-13):
|
|
696
|
+
* ⓒ`patch.dbAttrs` 배열 통째 교체와 ⓑdotted `dbAttrs.<없는 i>.<field>` 가 **둘 다 `ok:true`** 였다.
|
|
697
|
+
* 그 결과 상태를 하류가 각자 다르게 읽는다: **DDL 은 2컬럼을 내고** 코드젠은 **1필드**만 내며
|
|
698
|
+
* (두 번째 컬럼이 산출에서 사라진다) 검증은 **신고 0**이고, GUI 는 `hasColumnSlots` 가 false 라
|
|
699
|
+
* **화면에 보이지도 않는다**. 게다가 사용자가 그 속성을 편집하면 `useAttributeEditing.setDb` 가
|
|
700
|
+
* `dbAttrs:[next]` 로 **통째 교체** = 두 번째 컬럼이 **조용히 삭제**된다 ⇒ 진입만 있고 출구가 파괴적인
|
|
701
|
+
* **단방향 트랩**이다(메모리 `affordance-must-be-reversible`).
|
|
702
|
+
*
|
|
703
|
+
* ⇒ **GUI 에 없는 조작이므로 막는 것이 패리티다**(GUI 는 NORMAL 을 단일 컬럼 블록으로만 편집한다).
|
|
704
|
+
* ★**그리고 이제 대안이 실재한다** — 다중 컬럼이 필요하면 **임베더블 + EMBED 관계**(AA-5 S3)가 그 자리다.
|
|
705
|
+
* 이 가드를 S3 «뒤»에 세운 이유가 그것이다: 먼저 세웠다면 표현력 부족을 가드로 덮는 것이었다.
|
|
706
|
+
* ⚠️판정은 **patch 적용 «후»** 의 `attrType` 으로 한다(계획 diff 규율 — 메모리
|
|
707
|
+
* `plan-diff-judgment-uses-post-plan-state`). ★★**다만 그 읽기는 «현재» 불활성이다** — 민감도로
|
|
708
|
+
* 실증했다(2026-09-13: 적용 «전» 으로 되돌려도 전량 green). `patch.attrType` 을 세우는 경로가 둘뿐이고
|
|
709
|
+
* **둘 다 여기 안 닿는다**: ⓐ사용자 직접 지정은 `attribute-attrtype-unsupported` 가 **먼저** 거부하고
|
|
710
|
+
* ⓑresolver 내부 in-place 값 타입 전환은 **컬럼 개수를 보존**한다(`columnPreservingValueTypeEntry`).
|
|
711
|
+
* ⇒ 선례(`fe47316`)대로 처방은 *「가드를 단순화」* 가 아니라 **「불활성의 전제를 테스트로 고정」** 이고,
|
|
712
|
+
* 그 앵커가 `resolver.test.ts` 의 «in-place 값 타입 전환은 컬럼 개수를 보존한다» 다. ⓑ가 컬럼을
|
|
713
|
+
* 늘리게 되면 그 테스트가 red 가 되고 이 post-patch 읽기가 **살아난다**.
|
|
714
|
+
*/
|
|
715
|
+
| {
|
|
716
|
+
code: 'attribute-column-count-unsupported';
|
|
717
|
+
opIndex: number;
|
|
718
|
+
entity: Handle;
|
|
719
|
+
handle: Handle;
|
|
720
|
+
/** patch 적용 후 attrType(판정 기준). */
|
|
721
|
+
attrType: string;
|
|
722
|
+
current: number;
|
|
723
|
+
requested: number;
|
|
635
724
|
};
|
|
636
725
|
export type ResolveNotice = {
|
|
637
726
|
code: 'inherited-slot-override';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Entity, ModelId } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 아직 모델에 반영되지 않은 임베드 간선 — AI 배치의 **같은 배치 선행 op** 산출분.
|
|
4
|
+
*
|
|
5
|
+
* ★이게 없으면 한 배치 안에서 `A→B`·`B→A` 를 나눠 보내 순환을 만들 수 있다(각 op 은 저장 전 모델만
|
|
6
|
+
* 보므로 혼자서는 무해해 보인다). resolver 가 `addedAttributes` 에서 뽑아 넘긴다.
|
|
7
|
+
*/
|
|
8
|
+
export interface PendingEmbedEdge {
|
|
9
|
+
ownerId: ModelId;
|
|
10
|
+
embeddableRef: ModelId;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `ownerId` 가 `embeddableId` 를 임베드하면 순환이 되는가 — 되면 **그 순환 경로**, 아니면 `null`.
|
|
14
|
+
*
|
|
15
|
+
* 경로는 `findIdentifyingCycles`(validation)의 관례를 따라 **닫힘 반복 없이 멤버만** 담는다:
|
|
16
|
+
* 자기 임베드는 `[A]`, `A→B→A` 는 `[A, B]`. 호출자는 `!= null` 로 거부하고, 필요하면 그 경로를
|
|
17
|
+
* 메시지·에러 payload 로 쓴다(«고칠 수 있는 자리»를 지목하기 위한 정보다).
|
|
18
|
+
*/
|
|
19
|
+
export declare function findEmbedCycle(entities: readonly Entity[], ownerId: ModelId, embeddableId: ModelId, pending?: readonly PendingEmbedEdge[]): ModelId[] | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Ref } from 'vue';
|
|
2
2
|
import { Command, EditorState } from '../command/types';
|
|
3
|
+
import { Translator } from '../i18n';
|
|
3
4
|
import { EditorController } from './controller';
|
|
4
5
|
import { ModelId } from '../core/types';
|
|
5
6
|
/** 이 축이 팩토리 클로저에서 쓰는 것 전부. 늘어나면 여기 선언이 먼저 늘어난다(= 의존 인벤토리). */
|
|
@@ -14,5 +15,9 @@ export interface AssociationContext {
|
|
|
14
15
|
gateEntityLocked: (id: ModelId) => boolean;
|
|
15
16
|
/** 잠금 게이트(관계) — 양끝 중 하나라도 잠기면 차단(그 의미론의 단일 출처). */
|
|
16
17
|
gateAssocLocked: (assocId: ModelId) => boolean;
|
|
18
|
+
/** 번역 — 아래 임베드 순환 차단의 «이유» 안내에 쓴다(엔티티·속성 축의 구조 차단 안내와 같은 짝). */
|
|
19
|
+
t: Translator;
|
|
20
|
+
/** 구조 차단 안내 — 무음 차단은 "고장"으로 읽힌다(VE-2 규율 · 직렬화 타입 분기의 실사용 보고 선례). */
|
|
21
|
+
emitGateNotice: (text: string) => void;
|
|
17
22
|
}
|
|
18
23
|
export declare function createAssociationApi(ctx: AssociationContext): Pick<EditorController, 'addAssociation' | 'addEmbedAssociation' | 'setEmbedCardinality' | 'removeAssociation' | 'setAssociationIdentifying' | 'updateAssociationEnd'>;
|