import { ComputedRef, InjectionKey } from 'vue'; import { Attribute, DbColumn, EmbeddableCatalog, LogicalModel, ModelId } from '../core/types'; /** * 캔버스 행 표시에 필요한 **상속·공유 해소를 노드 밖에서 한 번에** 계산한 인덱스 (VE-7 ②). * * ── 왜 노드 밖으로 뺐나 ──────────────────────────────────────────────────────── * `EntityNode`가 자기 행을 그리려면 **다른 엔티티를 읽어야 한다**(FK 물리명·타입은 부모 PK에서, * 임베드 슬롯은 임베더블 소스에서, 공유 통화 컬럼은 owner 컬럼에서 상속). 그 읽기가 렌더 이펙트 * 안에 있으면 Vue가 `logical.entities`와 **각 엔티티의 `attributes`까지** 노드의 의존으로 추적하므로, * 무관한 엔티티 1개를 추가해도 이 경로를 타는 노드가 전부 무효화된다 — VE-1이 좁혀 둔 스코프가 * 되돌아간다. 실측(module-order 미러 시드 188노드): 엔티티 추가 1회에 EntityNode patch **124**, * 해소 두 축을 끄면 **4**(각 축 단독으로도 발동 · 이 경로를 타는 노드 169/188). * * ── 무엇을 dedup 하는가 ─────────────────────────────────────────────────────── * 노드별 해소는 각자 자기 속성만 처리하므로 **중복이 아니다**(총 작업량은 같다). 실제 중복은 * `indexColumnsByModelId(collectSharedColumns(...))`를 **임베드 속성마다** 다시 만들던 것 하나이고 * (그 안의 owner 역참조는 embeddable 노드에서 전 엔티티 스캔이다) → **엔티티별 1회**로 접었다. * 상속·스코프 계산 자체는 core 헬퍼(`collectSharedColumns`·`resolveEffectiveColumn`· * `embedCollectionType`)를 **그대로 호출한다**(FK 부모 PK 해소도 그 안에 있다) — 시맨틱을 여기 옮겨 적으면 이 * 리포가 반복해서 싸운 미러 드리프트(VE-4 축)를 뷰 계층에 새로 만드는 셈이다. ★초안에서 owner 역참조 * 맵을 직접 만들어 `sharedColumnScopeEntities`의 규칙을 재구현했다가 **되돌렸다**: 그것이 그 규칙의 네 * 번째 사본이 되는데, 참조 안정화가 이미 성능 목표(하한)를 달성했으므로 그 최적화는 드리프트 위험만 * 남기고 얻는 게 없었다(실측 차이 없음 — 헤더 하단 수치). * * ── 반복 서브컬럼(redundantSlots) ───────────────────────────────────────────── * 임베드 서브행은 캔버스 렌더 행의 큰 몫인데(실측 BNKR_SALES v2 576엔티티: 속성 6,214 + 서브행 2,683 = * **30.2%**), 그중 상당수가 **엔티티 안에서 같은 값을 반복**한다. 지배적 형상은 Money의 currency 슬롯이다: * - 미지정(빈 슬롯) 656건 → 카탈로그 기본명 `'currency'`로 해소되어 전부 같은 이름 * (그중 613건은 한 엔티티에 2개 이상이라 `CHK-NAME-2` 컬럼 충돌까지 유발한다 — OrderItem 59/59) * - 공유 지정(SHARED_REF) 43건 → `Claim.order_local_currency` **하나를 43개가 함께 가리킨다** * 즉 **정상 지정된 경우에도 반복**이고(오히려 더 완전한 반복이다), 그 43행이 주는 정보는 "이 엔티티의 * Money는 저 통화 컬럼을 공유한다" 한 줄이다. 반대로 amount 슬롯은 704건 전부 고유 물리명을 가져 * 같은 규칙에서 자동으로 빠진다 — 판정 축은 "상속이냐"가 아니라 **"엔티티 안에서 새 정보를 주느냐"**다. * * 판정: 같은 `(attrType, 임베더블 타입, 슬롯 인덱스)` 자리를 묶고, 그 안에서 실효 물리명이 **2회 이상 * 나오는 값**을 가진 슬롯만 반복으로 본다(자리가 2개 이상일 때). Money 하드코딩 없이 Tel·Name·Address는 * 사용처마다 물리명을 오버라이드하고 있어 자동으로 걸러진다(실측: Tel #0·#1은 상속 0/고유 37). * * ★★판정이 **값 단위**인 이유: 종전엔 그룹이 *전부* 같을 것을 요구했는데, 그러면 한 자리만 달라도 나머지 * 반복분까지 통째로 안 접힌다. 실 데이터가 정확히 그 형상이 됐다 — 코드↔모델 sync 로 Money currency 가 * SHARED_REF(공유 통화 컬럼)·OWN(고유 컬럼)·빈 슬롯으로 갈리면서 `SalesOrder` 53자리가 **14종**, * `OrderItem` 66자리가 **19종**이 됐고 접힘이 꺼졌다. 그런데 그중 `order_local_currency` 40·48행은 * 여전히 서로 같은 말을 반복한다(새 정보 0). ⇒ 축("새 정보를 주느냐")은 자리 단위가 아니라 값 단위로 * 성립한다. 초기 표본(`Claim` SHARED_REF 43건이 **전부** 같은 타깃)이 균일해서 이 차이가 안 보였다. * * ★뷰는 이것을 **기본 접힘**으로만 쓴다(행을 지우지 않는다) — 접힌 자리에 숨긴 개수를 표시하고 캐럿으로 * 펼치면 전부 보인다. 위 613건처럼 `CHK-NAME-2`가 걸린 슬롯도 여기 포함되지만, 그 신호의 소유자는 * 검증 패널이고 이 모듈은 **표시 반복만** 접는다(미완성 상태를 지우지 않는다). * * ── 계약 ───────────────────────────────────────────────────────────────────── * 순수 함수다(모델을 변형하지 않고 표시용 파생값만 만든다). 값 시맨틱은 종전 `EntityNode`의 세 함수 * (`effectiveAttr`·`embedCols`·`typeCell`의 collectionType)와 **동일**해야 한다 — 이 모듈은 성능 리팩터이고 * 표시 결과를 바꾸지 않는다(`redundantSlots`는 그 뒤에 얹힌 **표시 힌트**이고, 소비는 뷰가 결정한다). * 해소가 불필요한 속성은 **맵에 넣지 않는다**(호출자가 원본을 쓴다) ⇒ 인덱스 미주입(read-only 임베드)이면 * 종전 `!editor` 폴백과 같은 원형 표시가 된다. * * 판정은 **엔티티 로컬**이다(다른 엔티티를 읽지 않는다) — 위 「참조 안정화」가 좁혀 둔 무효화 범위를 * 넓히지 않기 위해서다. */ export interface EntityColumnResolution { /** attr modelId → FK 상속 해소된 표시용 속성. 해소가 일어난 속성만 포함. */ effectiveAttrs: ReadonlyMap; /** attr modelId → 임베드 펼침 컬럼(상속·공유 해소 적용). EMBED 속성만 포함. */ embedCols: ReadonlyMap; /** attr modelId → 컬렉션 임베드 래핑 타입. 단일 임베드(undefined)는 포함하지 않는다. */ collectionTypes: ReadonlyMap; /** * attr modelId → **반복 서브컬럼** 슬롯 인덱스. 같은 `(임베더블 타입, 슬롯 위치)` 자리들 중 실효 물리명이 * 그 엔티티 안에서 **2회 이상 나오는 값**을 가진 슬롯을 담는다 — 아래 「반복 서브컬럼」 참조. 그 자리에서 * 고유한 값(1회)은 새 정보이므로 포함하지 않고, 반복이 없는 속성은 맵에 아예 넣지 않는다. */ redundantSlots: ReadonlyMap>; /** * **CLASS 표시 모드의 행 목록** — 모델 속성이 아니라 «방출될 Java 필드» 기준(`scaffoldJava.classFields`). * * 행 목록이 모델과 다른 경우는 셋이다: `@MapsId` 1행→2행 · 복합 FK 형제 1행→0행 · * navigable off inverse 1행→0행. ⚠️**같아도 담는다** — 관계 행의 `targetName`(CLASS 타입 칸의 출처)은 * 행 목록이 모델과 같은 엔티티에도 필요하기 때문이다(다를 때만 담았다가 타입 칸이 비는 결함을 냈다). * 비용은 아래 `sameClassRows` 가 **속성 참조 단축**으로 흡수한다(비합성 행은 모델 객체를 그대로 든다). * * ★여기서 계산하는 이유는 `derivedIdFieldName` 과 같다 — 노드는 반응성 스코프 때문에 `logical` 을 * 직접 못 본다(위 「참조 안정화」). 판정·산식은 코드젠 것을 그대로 쓰고 뷰가 재기술하지 않는다. * ⚠️이 축은 **엔티티 로컬이 아니다**(관계·대상 엔티티를 읽는다) — MapsId 해소가 이미 그렇고, * 무효화 범위는 아래 값 비교가 좁힌다. */ classRows?: readonly ClassDisplayRow[]; } /** CLASS 행 하나 — 값 비교가 싸도록 **평탄**하게 담는다(대상 엔티티 객체를 통째로 들지 않는다). */ export interface ClassDisplayRow { /** 표시용 속성. 합성 스칼라(@MapsId)는 **모델에 없는 가상 속성**이다. */ attr: Attribute; synthetic: boolean; /** * 이 행을 만든 **모델 속성**의 id(합성 행이면 그 FK, 아니면 자기 자신). * 합성 행을 클릭했을 때 «없는 것»을 선택하지 않고 **실물 FK 를 지목**하는 데 쓴다 — * 그 FK 의 인스펙터가 스칼라명 서브행을 갖고 있으므로 사용자가 갈 곳이 정확히 거기다. */ sourceAttrId: ModelId; /** relation-* 의 대상 클래스명 — CLASS 타입 칸의 출처. */ targetName?: string; /** inverse nav 의 컬렉션 래핑. */ collection?: 'LIST' | 'SET'; } /** 엔티티 modelId → 그 엔티티 행들의 해소 결과. */ export type ColumnResolutionIndex = ReadonlyMap; /** * 캔버스가 provide, `EntityNode`가 inject. 미주입 시 노드는 원형(raw) 표시로 graceful degrade한다 * (종전 `!editor` 분기와 동일한 폴백) — 독립 렌더·테스트에서 캔버스 없이 노드를 쓸 수 있게 유지. */ export declare const COLUMN_RESOLUTION: InjectionKey>; /** * 참조 안정 인덱서 — 직전 인덱스를 기억해 **내용이 같은 엔티티는 이전 결과 객체를 재사용**한다. * 캔버스가 computed 안에서 호출한다(모델 변경당 1회). 상태를 갖는 이유는 위 「참조 안정화」 참조. */ export declare function createColumnResolutionIndexer(): (model: LogicalModel, catalog?: EmbeddableCatalog) => ColumnResolutionIndex; /** * 모델 전체의 표시 해소 인덱스를 만든다. `catalog`는 EMBED_PREDEF 서브컬럼의 선언값 출처(호스트 주입) — * 미주입이면 그 축만 원형으로 degrade한다(종전 `editor?.embeddableCatalog` 분기 동형). * `prev`를 주면 내용이 같은 엔티티의 결과 **객체 참조를 재사용**한다(무효화 범위 축소 — 위 문단). */ export declare function buildColumnResolutionIndex(model: LogicalModel, catalog?: EmbeddableCatalog, prev?: ColumnResolutionIndex): ColumnResolutionIndex;