@g1cloud/entity-modeler-next 5.1.4 → 5.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { Association, Attribute, DbColumn, Entity } from './types';
1
+ import { Association, Attribute, DbColumn, Entity, ModelId } from './types';
2
2
  /**
3
3
  * FK 유래 컬럼 = 물리 컬럼을 보유한 관계 속성(RELATION* + dbAttrs). 형상(dataType/length/scale/
4
4
  * physicalName/notNull 등)은 부모 식별자(PK) + 관계 identifying에서 파생된다.
@@ -17,6 +17,8 @@ import { Association, Attribute, DbColumn, Entity } from './types';
17
17
  * | 자리 | 술어 | 왜 다른가 |
18
18
  * |---|---|---|
19
19
  * | `isFkDerivedColumn`(여기) | RELATION* && dbAttrs>0 | 뷰 잠금·형상 해소. `findFkOwningAssociation`이 위임 |
20
+ * | `isJoinColumnOnlyFk`(여기) | RELATION_OWN && 앵커 아님 && 형제가 앵커 | 복합 FK 형제 — **필드를 안 낸다**(아래) |
21
+ * | `isMultiColumnFk`(여기) | RELATION_OWN && dbAttrs>1 | **형상 A** 서브컬럼 표현 대상. 뷰 셋이 공유 |
20
22
  * | `attributeOrder.isPhysicalFk` | derivedFrom && dbAttrs>0 | 표시 순서 정렬. **강등 이후** 호출 전제라 `derivedFrom`이 기준(경계 강등된 컬럼은 제자리에 남아야 한다) |
21
23
  * | `mermaid.isForeignKey` | RELATION_OWN‖RELATION_REF‖derivedFrom | ER **FK 마커**는 nav(dbAttrs=0)까지 표시 대상이라 컬럼 보유를 안 본다 |
22
24
  * | `ddl.ts` FK 제약 방출 | derivedFrom 단독 | 부모 컬럼을 알아야 REFERENCES를 쓸 수 있어 링크가 필수 |
@@ -27,6 +29,48 @@ import { Association, Attribute, DbColumn, Entity } from './types';
27
29
  * (`parseMermaid.wireForeignKeyColumns` 주석), 그건 발생원에서 닫았다.
28
30
  */
29
31
  export declare function isFkDerivedColumn(attr: Attribute): boolean;
32
+ /**
33
+ * **다중 컬럼 FK**(형상 A) — 관계 하나가 **컬럼 여럿**을 갖는 속성인가.
34
+ *
35
+ * JPA 는 관계당 필드 하나이고 컬럼이 여럿이다(`@ManyToOne` + `@JoinColumns`) ⇒ 「한 속성이 여러 컬럼을
36
+ * 갖는다」는 형상이 **임베드와 같다**. 그래서 뷰 세 곳이 임베드의 서브컬럼 관용구를 이 술어로 함께 탄다:
37
+ * 캔버스 서브행(`EntityNode.hasSubColumns`) · 표시 해소(`columnResolution`) · 편집 슬롯
38
+ * (`useAttributeEditing.hasColumnSlots`).
39
+ *
40
+ * ⚠️**`> 1` 이다** — 단일 컬럼 FK 는 서브컬럼이 없어 대상이 아니다(실측 285/285 가 단일 컬럼 = 전원
41
+ * 무영향). `> 0` 으로 쓰면 FK 전량이 단일 컬럼 편집 블록에서 빠져 나간다.
42
+ * ⚠️`RELATION_REF`(inverse nav)는 컬럼이 0이라 자연히 제외된다.
43
+ */
44
+ export declare function isMultiColumnFk(attr: Attribute): boolean;
45
+ /**
46
+ * 관계 필드를 소유하는 **앵커 속성** 집합 — `end2.attributeRef`(owner, navigable 무관) +
47
+ * `end1.attributeRef`(inverse, navigable일 때만). `scaffoldJava`의 `relByAttr` 키와 **같은 규칙**이며
48
+ * 그쪽이 이 함수로 위임한다(산식 이중화 금지).
49
+ */
50
+ export declare function fkFieldAnchors(associations: readonly Association[]): Set<ModelId>;
51
+ /**
52
+ * 복합 FK의 **형제 컬럼**인가 — 즉 이 속성이 `@JoinColumns` 컬럼으로만 기여하고 **자기 필드는 내지
53
+ * 않는가**.
54
+ *
55
+ * 배경: 전파(`computeForeignAttributes`)는 부모 PK 멤버마다 속성을 하나씩 만들지만
56
+ * (`parentIdentifiers.map(...)`), JPA는 관계당 **필드 하나**다 — 앵커가 `@ManyToOne` +
57
+ * `@JoinColumns`를 소유하고 형제는 컬럼만 보탠다. ⇒ 형제의 `name`은 **어디에도 방출되지 않는다**.
58
+ *
59
+ * 그래서 이 술어를 쓰는 자리가 셋이다:
60
+ * 1. `scaffoldJava` — 방출 분기(형제는 `@Column`을 내면 같은 컬럼이 두 번 매핑돼 Hibernate가
61
+ * "Repeated column in mapping"으로 부트스트랩에 실패한다).
62
+ * 2. `validation` `CHK-NAME-7` — 형제의 빈 이름은 **필드 미방출을 일으키지 않으므로** error가 아니다
63
+ * (info `CHK-NAME-4`로 강등). 「검증 규칙은 고칠 수 있는 자리를 지목해야 한다」의 적용 —
64
+ * 여기선 *고칠 필요가 없는* 자리다.
65
+ * 3. 편집 뷰 — 이름 칸을 잠그고 앵커를 지목한다(사문 슬롯에 입력을 유도하지 않는다).
66
+ *
67
+ * ⚠️앵커가 **없는** 그룹은 제외한다(false). 그건 컬럼이 통째로 사라지는 별개 결함이고
68
+ * `CHK-JPA-19`·코드젠 `fillIn`이 이미 지목한다 — 여기서 삼키면 무신호가 된다.
69
+ *
70
+ * ★실측(2026-08-27, 프로덕션 v2 29문서): 복합 FK 그룹 **2**(참고 전용 제외 시 1) · 형제 **2**(실효 1) ·
71
+ * `CHK-NAME-7` 21건 중 형제 지목 **0**. 발동은 작지만 유입 경로는 열려 있다(v1 잔재·다중 컬럼 FK 신설).
72
+ */
73
+ export declare function isJoinColumnOnlyFk(attr: Attribute, entity: Entity, anchors: ReadonlySet<ModelId>): boolean;
30
74
  /**
31
75
  * FK 유래 컬럼 i의 원본(부모 식별자) 컬럼 — 물리명·타입·길이·스케일이 여기서 파생된다. FK 컬럼 자신의
32
76
  * dbAttrs가 비어 있을 때(v1→v2 로드/레거시 데이터, 어댑터가 재전파를 하지 않음) placeholder·캔버스
@@ -44,3 +88,8 @@ export declare function isFkDerivedColumn(attr: Attribute): boolean;
44
88
  * 둔 본래 이유)는 그대로다. id가 유일한 문서에서는 두 경로가 같은 속성을 찾아 **결과가 동일하다**.
45
89
  */
46
90
  export declare function resolveFkOriginColumn(attr: Attribute, childEntity: Entity | null | undefined, entities: readonly Entity[], associations: readonly Association[], i?: number): DbColumn | undefined;
91
+ /**
92
+ * 부모 식별자 컬럼 평탄화(선언 순서) — 복합 PK 를 한 속성이 담는 **형상 A** 의 슬롯 원본.
93
+ * `scaffoldJava.parentIdColumns` 와 같은 규칙이며 그쪽이 이 함수로 위임한다(산식 이중화 금지).
94
+ */
95
+ export declare function parentIdColumns(parent: Entity | undefined): DbColumn[];
@@ -74,7 +74,9 @@ export declare function embedCollectionType(attr: Attribute, associations: Assoc
74
74
  * 속성 타입 칸 표시 — 모델은 type(자바 타입)·dbAttrs[].dataType(물리 DB 타입)을 분리 보관하므로
75
75
  * 뷰 모드별로 맞는 출처를 고른다(결정 2026-06-26):
76
76
  * CLASS = 자바 타입 라벨 / PHYSICAL·LOGICAL_PHYSICAL = 물리 DB 타입 / LOGICAL = 숨김.
77
+ * ★**관계 행의 CLASS 타입은 대상 엔티티 클래스**다(`relationTargetName`) — 그 필드의 Java 타입이 곧
78
+ * 그 클래스이기 때문이다. 호출부는 `scaffoldJava.classFields` 가 해소한 값을 넘긴다(뷰가 재기술하지 않음).
77
79
  * CustomType 마커는 실제 타입명(customType)을, 임베드 참조 헤더는 임베더블 타입명을 보인다
78
80
  * (임베드의 물리 컬럼 타입은 펼침 서브행이 담당). Diagram 노드·Explorer 트리 공용.
79
81
  */
80
- export declare function attrTypeCell(mode: DisplayMode, attr: Attribute, collectionType?: 'LIST' | 'SET'): string;
82
+ export declare function attrTypeCell(mode: DisplayMode, attr: Attribute, collectionType?: 'LIST' | 'SET', relationTargetName?: string): string;
@@ -12,8 +12,13 @@ export interface ForeignAttributeSpec {
12
12
  type: string;
13
13
  identifier: boolean;
14
14
  notNull: boolean;
15
- /** 부모 식별자의 컬럼을 미러한 dbAttrs (modelId 없음 — reconcile이 부여) */
15
+ /** 부모 식별자의 컬럼을 미러한 dbAttrs (modelId 없음 — reconcile이 부여). **물리명은 빈값**(상속). */
16
16
  dbAttrs: Omit<DbColumn, 'modelId'>[];
17
+ /**
18
+ * 부모 PK 컬럼의 물리명 — **유일화 판정 전용 내부 축**이고 `specToAttribute` 가 영속하지 않는다.
19
+ * 물리명을 비워 상속시키므로(위) 충돌 시 무엇을 기준으로 유일화할지가 spec 안에 남아 있어야 한다.
20
+ */
21
+ originPhysicalNames?: readonly string[];
17
22
  }
18
23
  export interface ReconcilePlan {
19
24
  /** 신규 생성할 FK 속성 (modelId·dbAttr modelId 부여됨, order 포함) */
@@ -1,4 +1,4 @@
1
- import { Entity, LogicalModel, ModelId, SuperClassCatalog, EmbeddableCatalog } from './types';
1
+ import { Association, Attribute, Entity, LogicalModel, ModelId, SuperClassCatalog, EmbeddableCatalog } from './types';
2
2
  import { JavaTypeDef } from './javaTypes';
3
3
  /**
4
4
  * `AttributeConverter` FQN 기본값 — **레거시 생성기 상수 미러**(`bluework-im` `EntityJavaFile.java:77~78`
@@ -128,4 +128,89 @@ export interface ScaffoldedEntity {
128
128
  */
129
129
  export declare function isGeneratable(entity: Entity): boolean;
130
130
  export declare function scaffoldEntityJava(logical: LogicalModel, targetIds: readonly ModelId[], opts?: ScaffoldOptions): ScaffoldedEntity[];
131
+ /**
132
+ * 식별 FK가 `@MapsId`(관용구4)인지 판정 — **단일 컬럼 @OneToOne 식별 FK**는 대상 PK를 공유하는 파생
133
+ * 식별이다(실물 OrderShipping: `@Id String orderNo` + `@MapsId @OneToOne order`가 order_no 컬럼 공유).
134
+ * 이 경우 FK는 스칼라 @Id에 매핑될 뿐 별도 PK 멤버가 아니므로 PK 카디널리티 카운트에서 제외한다.
135
+ * 걸러지는 쪽 = 직접 @Id 멤버(관용구3 @Id@ManyToOne[단수 아님]·관용구5 대상 복합키[컬럼 2+]).
136
+ *
137
+ * ★★**종전엔 조건이 하나 더 있었다** — *"스칼라 @Id와 같은 물리 컬럼을 공유"*. 그 조건은 모델에 스칼라
138
+ * PK가 **실재할 것**을 요구했는데 FK 전파는 FK 속성만 만들고 스칼라를 만들지 않으므로(`propagation.ts`)
139
+ * **프로덕션 발동이 0**이었고, 실물이 `@MapsId` 21/21인데 모델은 22건 전부 관용구5로 방출되는 **전면
140
+ * 드리프트**가 났다. 스칼라 필드는 이제 **방출 시점에 합성**하므로(`derivedIdFieldName` ?? 부모 PK
141
+ * 물리명 camelCase — 실물 관행 19/19) 그 조건이 불요해졌고, 모델은 **속성 하나**를 유지한다
142
+ * (⇒ 물리명 중복이 생기지 않아 `CHK-NAME-2`·DDL 이 무변경). 근거=`.claude/docs/MapsIdIdiom-entry_2026-08-26.md` §3.A.
143
+ * ★관용구5(`@Id`를 연관 필드에 직접)는 JPA 정식이지만 **이 코드베이스 실물에 0건**이라 선택지를 두지
144
+ * 않았다(§3.A (가) 확정 — 요구가 관측되면 관용구 선택 축을 신설).
145
+ */
146
+ export declare function isMapsIdFk(fk: Attribute, assoc: Association | undefined): boolean;
147
+ /**
148
+ * `@MapsId` FK가 매핑되는 **스칼라 @Id의 필드명**. 모델 지정(`derivedIdFieldName`)이 우선이고, 없으면
149
+ * **부모 PK 물리명의 camelCase**로 파생한다(실물 21건 대조 19/19 — 모델 속성명 폴백은 18/19로 이름 결손
150
+ * 1건에서 어긋나므로 물리명 쪽을 택했다). 부모 컬럼도 미해소면 FK 자신의 물리명, 그마저 없으면 빈 문자열
151
+ * (호출부가 fillIn으로 표면화).
152
+ */
153
+ export declare function mapsIdScalarName(fk: Attribute, assoc: Association, byId: Map<ModelId, Entity>): string;
154
+ /**
155
+ * `@MapsId` 스칼라 @Id의 **Java 타입** — 이름과 **같은 경로**(`derivedFrom.sourceAttributeRef`)로 조달한다.
156
+ *
157
+ * ★`pkMemberFieldType(fk, …)`을 직접 쓰면 안 된다 — 그 함수는 부모를 **`member.type`(엔티티명)으로 조회**
158
+ * 하는데 **실 데이터의 FK 속성은 `type`이 빈 문자열**이다(전파가 채우지 않는다 — 실측 `PaymentMethodPolicy`
159
+ * `{name:'paymentMethod', type:''}`). 그러면 부모 미해소로 `Object`가 나간다(실물은 `String`). 반면
160
+ * `derivedFrom`은 부모 PK 속성을 **modelId로 직접** 가리키므로 해소율이 19/19다.
161
+ * ⚠️이것은 이 변경이 만든 결함이 아니라 **선재 결함의 노출**이다 — 같은 경로를 `{Entity}PK` 필드 타입과
162
+ * 단일키 Repository 타입도 쓰고 있어 그쪽도 `Object`가 나가고 있었다.
163
+ */
164
+ export declare function mapsIdScalarType(fk: Attribute, assoc: Association, byId: Map<ModelId, Entity>, logical: LogicalModel, opts: ScaffoldOptions): string;
165
+ /** 이 엔티티가 내는 Java 필드 한 줄의 역할. */
166
+ export type ClassFieldRole = 'scalar' | 'embed' | 'relation-owner' | 'relation-inverse';
167
+ /** 방출되지 않는 모델 속성의 사유 — `no-anchor` 만 신호 대상(나머지는 정상 스킵). */
168
+ export type ClassFieldOmitReason = 'join-column-only' | 'no-anchor' | 'nav-not-materialized';
169
+ export interface ClassField {
170
+ /** 표시·방출 대상. 합성 스칼라(`synthetic`)는 **모델에 없는 가상 속성**이다. */
171
+ attr: Attribute;
172
+ role: ClassFieldRole;
173
+ /** 모델에 없는 합성 행(@MapsId 스칼라 @Id) — 편집·삭제·재정렬 대상이 아니다. */
174
+ synthetic: boolean;
175
+ /** 이 행을 만든 모델 속성(합성 스칼라면 그 FK, 아니면 `attr` 자신). 행 → 모델 역참조. */
176
+ sourceAttr: Attribute;
177
+ assoc?: Association;
178
+ /** relation-* 의 대상 엔티티 — **CLASS 타입 칸의 출처**(JPA 필드 타입이 곧 이 클래스다). */
179
+ target?: Entity;
180
+ /** relation-inverse 의 컬렉션 래핑(to-many). */
181
+ collection?: 'LIST' | 'SET';
182
+ }
183
+ export interface ClassFieldPlan {
184
+ /** 방출 순서대로의 필드 목록 — **CLASS 뷰의 행 목록이 이것과 같아야 한다**. */
185
+ fields: ClassField[];
186
+ /** 방출되지 않는 모델 속성 + 사유. `no-anchor` 는 컬럼 누락이라 호출부가 신호한다. */
187
+ omitted: {
188
+ attr: Attribute;
189
+ reason: ClassFieldOmitReason;
190
+ }[];
191
+ /** @MapsId 스칼라 필드명 미해소 — 코드젠이 `fillIn` 으로 표면화한다. */
192
+ unresolvedDerivedIds: Attribute[];
193
+ }
194
+ /**
195
+ * **이 엔티티가 방출하는 Java 필드 목록** — 코드젠·캔버스·탐색기의 단일 출처.
196
+ *
197
+ * 모델 속성과 **1:1이 아니다**. 세 축에서 갈라진다:
198
+ * 1. **@MapsId**: 속성 하나가 **두 필드**를 낸다 — 합성 스칼라 `@Id` + `@MapsId` 연관 필드.
199
+ * (모델이 속성 하나를 유지하는 것은 *모델 결정*[물리명 중복·전파·검증·DDL 무변경]이지 표시 결정이
200
+ * 아니다. 상세=`.claude/docs/MapsIdIdiom-entry_2026-08-26.md` §3.A)
201
+ * 2. **복합 FK 형제**: 앵커가 `@ManyToOne` + `@JoinColumns` 를 소유하고 형제는 컬럼만 보태므로
202
+ * **필드를 내지 않는다**(`fkDerived.isJoinColumnOnlyFk`).
203
+ * 3. **navigable off 인 `RELATION_REF`**: 앵커에 안 잡혀 `emitInverseRelation` 이 호출되지 않는다.
204
+ *
205
+ * ⇒ CLASS 뷰가 모델 속성을 1:1로 그리면 **방출되지 않는 행을 보여주고 방출되는 필드를 감춘다**.
206
+ * 실측(프로덕션 v2 29문서, 2026-08-27): ①+22행 ②−1행 ③−18행 = **41행 어긋남**.
207
+ *
208
+ * ★**타입 해소는 여기 없다** — 소비처마다 답이 다르기 때문이다. 코드젠은 카탈로그·패키지를 타고
209
+ * Java 타입 FQN을 뽑고(`syntheticType` 주입), 뷰는 `javaTypes.attrTypeCell` 로 표시 라벨을 만든다.
210
+ * 이 함수가 소유하는 것은 **행 정체성**(무엇이 몇 줄로 나가는가)이고 그것만 단일 출처다.
211
+ *
212
+ * @param syntheticType 합성 스칼라의 `type` 해소기. 미주입 시 **부모 PK 속성의 `type`을 그대로** 쓴다
213
+ * (모델 값 — 뷰의 표시 라벨엔 그것으로 충분하다).
214
+ */
215
+ export declare function classFields(entity: Entity, logical: LogicalModel, syntheticType?: (fk: Attribute, assoc: Association) => string): ClassFieldPlan;
131
216
  export {};
@@ -224,6 +224,20 @@ export interface Attribute {
224
224
  /** 부모의 어느 식별자 속성을 미러하는가 */
225
225
  sourceAttributeRef: ModelId;
226
226
  };
227
+ /**
228
+ * 파생 식별 관용구(`@MapsId`)에서 방출되는 **스칼라 식별자 필드명**.
229
+ *
230
+ * 이 속성의 `name`은 **연관 필드명**(`@MapsId @OneToOne` 필드 · `mappedBy` 소스)이고, 같은 관계가
231
+ * 만드는 **두 번째 Java 필드**가 스칼라 `@Id`다 — 종전엔 `name` 하나가 두 역할을 겸해 이 이름을 담을
232
+ * 자리가 없었다(엔티티 행과 엣지 인스펙터가 **같은 `name`을 편집**한다: `PropertyPanel.navFieldName`
233
+ * ↔ `EntityAttributesWide`). 컬럼은 공유하므로 **속성은 여전히 하나**이고, 물리명·타입은 파생이다
234
+ * (`dbAttrs[0].physicalName` 또는 부모 컬럼명 / 부모 PK의 Java 타입).
235
+ *
236
+ * 미지정 시 **부모 PK 물리명의 camelCase**로 폴백한다 — 실물 관행 19/19이므로 발명이 아니다
237
+ * (모델 속성명 폴백은 18/19: 이름 결손 1건에서 어긋난다). `isMapsIdFk` 미발동 형상(관용구3
238
+ * `@Id@ManyToOne`·관용구5 대상 복합키·비식별 FK)에서는 무시된다.
239
+ */
240
+ derivedIdFieldName?: string;
227
241
  /**
228
242
  * 임베더블 인라인 EMBED hydration의 출처(load-only, 화면 표시 전용).
229
243
  * 레거시는 임베더블 타입의 플래튼 컬럼 묶음을 소유자 association end에 인라인(EMBED_OWN)으로만