import { Attribute, DbColumn, EmbeddableCatalog, Entity, LogicalModel, ModelId, MultiLangText } from './types'; /** * 임베더블이 **선언하는 슬롯** 하나 — 배열 인덱스가 곧 사용처(owner) 슬롯 위치다. * * ★이 목록이 「임베더블이 선언한 컬럼」의 **단일 출처**다. 종전엔 같은 개념이 다섯 곳에 흩어져 있었고 * 그중 코드젠만 다른 정의를 써서(DIVIDER·빈 물리명 제외) 위치 페어링이 밀렸다 — 실측 손채움 157건. * 기준을 **전 `dbAttrs`** 로 잡는 이유: 소비처 5 중 4가 이미 그것이고, 특히 **상속 해소가 인덱스 * 대응**이라 이 축을 바꾸면 표시·DDL·코드젠·검증이 동시에 흔들린다. DIVIDER 는 `dbAttrs` 가 없어 * 정의상 0 기여이므로 «DIVIDER 를 제외한다» 는 조작이 애초에 불필요하다. * * 배치: `embedSlots.ts` 가 아니라 여기 두는 이유는 **순환 회피**다(`embedSlots → columnResolve` 의존이 * 이미 있다) — 그리고 인덱스 대응 시맨틱의 소유자가 이 모듈이다(`inheritedSlotOf`). */ export interface DeclaredEmbedSlot { /** 선언 컬럼(임베더블 소유). */ col: DbColumn; /** * 임베더블 루트에서 이 슬롯의 **basic 속성까지의 JPA 프로퍼티 경로** — 코드젠 `@AttributeOverride(name=…)` 의 값. * * 단일 컬럼 속성이면 속성명 하나(`buyerName`)지만, 그 속성이 **자기도 임베더블**이면 JPA 는 basic 까지의 * 경로를 요구하므로 dotted 가 된다(`totalBaseSellingAmt.amount`). 실물 `Cancellation.before` 는 40/40 이 * dotted 다. ★종전엔 속성명만 실어서 **Money 를 품은 임베더블의 사용처가 매핑되지 않았다** — 게다가 * 한 속성의 두 컬럼이 **같은 이름**을 받아 `@AttributeOverrides` 안에 name 중복이 생겼다(실측 5블록·46라인). * * **해소 못 하면 빈 문자열**이다(모르는 값 규약 — 발명하지 않는다). 소비처는 빈값을 손채움으로 표면화한다. */ propertyPath: string; /** 선언 기본 물리명. 공유 투영·미물질화면 빈 문자열. */ defaultPhysical: string; /** 선언이 지정한 공유 타깃 — 사용처 바인딩과 **비교**하는 기준. */ defaultSharedRef?: ModelId; /** * 이 자리에 **자체 물리 컬럼**이 생기는가 — 공유 투영(`sharedColumnRef`)과 미물질화(빈 이름)는 false. * ⚠️**방출 게이트가 아니다** — 코드젠 방출은 「사용처 실효 바인딩이 선언 기본과 다른가」가 정한다. * 이 플래그는 규칙의 규모 분해(`CHK-JPA-21` 부족분 중 실제 컬럼 수)와 형상 판정에 쓴다. */ ownColumn: boolean; /** * 이 슬롯을 **선언한 속성**과 그 속성 «안»에서의 인덱스 — 소비처(EMBED_OWN)가 상속을 **한 단계 더** * 해소할 때 필요하다. 선언 슬롯 자신이 EMBED_PREDEF 면 그 `col` 은 «카탈로그를 물려받아야 하는» * 빈 골격이라, raw 로 물려받으면 **2단 연쇄에서 상속이 끊긴다**. * * ⚠️★**`col` 의 raw 계약은 그대로 둔다** — `commandPlans` 가 그것으로 새 슬롯을 만들고(빈 골격이 * 정답이다) `embedSlots` 가 «선언과 같은가» 비교에 쓴다. 해소는 **호출자**가 이 두 필드로 한다. */ ownerAttr: Attribute; ownerIndex: number; } /** * 선언 슬롯의 **프로퍼티 경로 해소 컨텍스트** — 경로 축에만 쓰인다(`col`·`defaultPhysical`·`ownColumn` 은 * 컨텍스트 없이도 종전과 동일). 미주입이면 중첩 축만 graceful degrade 하고 나머지는 그대로다. */ export interface DeclaredSlotContext { /** 중첩 임베더블(`EMBED_OWN`) 해소용 — 모델 엔티티 목록. */ entities?: readonly Entity[]; /** `EMBED_PREDEF` 서브필드명 해소용(호스트 주입). */ embeddableCatalog?: EmbeddableCatalog; } /** 임베더블 선언 슬롯 목록 — 연결·규칙·보충·상속·코드젠의 단일 출처(`DeclaredEmbedSlot`). */ export declare function declaredEmbedSlots(embeddable: Entity | undefined, ctx?: DeclaredSlotContext): DeclaredEmbedSlot[]; /** 상속 해소 컨텍스트 — `ValidationContext`·`DdlContext`와 동형(카탈로그는 호스트 주입). */ export interface ColumnResolveContext { /** EMBED_PREDEF 서브컬럼의 선언값 해석용. 미주입 시 그 축만 graceful degrade(빈 값). */ embeddableCatalog?: EmbeddableCatalog; } /** * 컬럼의 **실효 값** — 자기 값이 비어 있으면 선언원에서 상속. 문자열은 빈값 폴백(`||`), 수치는 * null/undefined 폴백(`??`)이다(저장 라운드트립이 미설정 수치를 `null`로 바꾸므로 — propagation `nn()` 동형). * * 축별 폴백(값 단위)이지 슬롯 통째 대체가 아니다: 사용처가 물리명만 override하고 타입은 상속하는 형태 * (@AttributeOverride의 실제 용법)가 지배적이라, 한 축을 채웠다고 나머지까지 자기 값으로 강제하면 * 대다수 정상 데이터가 도로 빈 값이 된다. 캔버스 `EntityNode.effectiveAttr`가 쓰던 시맨틱과 동일. */ export declare function resolveEffectiveColumn(attr: Attribute, col: DbColumn, index: number, entity: Entity | undefined, model: LogicalModel, ctx?: ColumnResolveContext): DbColumn; /** * **실효 논리명** — 컬럼 논리명이 있으면 그것, 없으면 «필드» 논리명(`Attribute.logicalName`). * * ★두 저장 자리를 한 값으로 접는 **단일 출처**다. 논리명은 원래 `DbColumn` 에만 있었는데 컬럼이 없는 * 자리(직렬화 타입)에는 넣을 데가 없었고, 그래서 `Attribute.fieldLogicalName` 이 신설됐다(그 필드 주석 참조). * 소비처가 넷이라(캔버스 행 · 탐색기 라벨·검색 · 코드젠 javadoc) 각자 `?? ` 로 재기술하면 어긋난다 — * 이 리포가 `excludeDDLGeneration` 에서 정확히 그렇게 당했다. * * ★**컬럼이 이긴다.** 컬럼 논리명이 더 구체적인 자리이고(임베드 슬롯 상속을 이미 지난 값일 수 있다), * 필드 논리명은 「컬럼이 말해 주지 않을 때」의 값이다. 그래서 직렬화 → 일반 전환 후에도 표시가 유지되고 * (컬럼이 없거나 논리명이 비어 필드 값이 나온다), 사용자가 컬럼 논리명을 입력하면 그쪽으로 넘어간다. * * ⚠️판정은 **내용 유무**다(`??` 아님) — 빈 객체 `{}` 는 편집 중간 상태로 실재하고, 그것을 「값 있음」으로 * 읽으면 폴백이 조용히 막힌다(`resolveEffectiveColumn` 의 임베드 상속이 같은 이유로 같은 판정을 쓴다). */ /** * **실효 엔티티 논리명** — 테이블 논리명이 있으면 그것, 없으면 «클래스» 논리명(`Entity.classLogicalName`). * * ★`effectiveLogicalName`(속성/컬럼 축)의 **엔티티 판**이고 같은 이유로 단일 출처다 — 소비처가 다섯이다 * (캔버스 노드 헤더 · 탐색기 라벨·검색 · 명령 팔레트 · 코드젠 클래스 javadoc). 각자 `?? ` 로 재기술하면 * 어긋난다. * * ★**테이블이 이긴다** — 더 구체적인 자리이고, 클래스 논리명은 「테이블이 말해 주지 않을 때」의 값이다 * (직렬화 타입은 테이블이 아예 없다). 그래서 직렬화 → 일반 전환 후에도 표시가 유지된다. * * ⚠️판정은 **내용 유무**다(`??` 아님) — 빈 객체 `{}` 가 폴백을 조용히 막는 것을 피한다. */ /** * **실효 NOT NULL** — 컬럼 축(`DbColumn.notNull`)이 설정돼 있으면 그것, 아니면 속성 축(`Attribute.notNull`). * * ★`effectiveLogicalName` 과 **같은 형상**이다 — 두 저장 자리를 한 값으로 접는 단일 출처이고 **컬럼이 * 이긴다**(더 구체적인 자리). 다른 점은 폴백 방향의 «원천»뿐이다: 논리명·타입·길이는 *선언원*(카탈로그· * 임베더블·부모 PK)에서 상속하지만(`resolveEffectiveColumn`), NOT NULL 은 **자기 속성**에서 내려온다 * — 카탈로그 필드에 그 축이 없고(실물 `@Embeddable` 이 `nullable` 을 대개 지정하지 않는다) 제약의 * 소유자는 «사용처»이기 때문이다(같은 임베더블이 어떤 테이블에선 NOT NULL, 다른 곳에선 nullable). * * ⚠️판정은 `?? `(nullish)다 — 저장 라운드트립이 미설정을 `null` 로 바꾸므로 `!== undefined` 로 읽으면 * `null` 을 «false 설정»으로 오독한다(이 리포의 wire clear 규약: *「저장값 null 을 엄격 비교로 읽지 말 것」*). * * ★`identifier` 는 여기서 보지 않는다 — PK 는 소비처마다 다르게 다룬다(DDL 은 `NOT NULL` 을 생략하고 * PK 제약으로 표현, 코드젠은 `@Id` 를 낸다) ⇒ 그 판단을 이 함수로 끌어오면 두 소비처가 어긋난다. */ export declare function effectiveNotNull(attr: { notNull?: boolean; }, col: { notNull?: boolean; } | undefined): boolean; export declare function effectiveEntityLogicalName(entity: { classLogicalName?: MultiLangText; table?: { logicalName?: MultiLangText; }; }): MultiLangText | undefined; export declare function effectiveLogicalName(attr: Pick, col: Pick | undefined): MultiLangText | undefined; /** * 실효 물리명 — override(비어있지 않은 physicalName)가 있으면 그 값, 없으면 상속 기본값. * 컬럼 충돌 판정(CHK-NAME-2)은 raw `''`가 아니라 이 값으로 해야 한다: 한 테이블에 같은 임베더블 * (예: Money)이 여러 번 쓰이고 서브컬럼을 비워두면 전부 같은 기본명을 상속해 실제 컬럼 충돌이 나고, * FK도 물리명 미지정 시 부모 PK 컬럼명을 그대로 쓰므로 자식 자기 컬럼과 충돌할 수 있다. */ export declare function resolveColumnPhysicalName(attr: Attribute, col: DbColumn, index: number, entity: Entity | undefined, model: LogicalModel, ctx?: ColumnResolveContext): string; /** * 서브컬럼의 **선언 필드명** — 다중 컬럼 속성을 컬럼 단위로 표시할 때 각 슬롯이 선언원의 어느 필드인지. * * 축을 EMBED 두 종으로 한정하는 이유: 이 이름은 장식이 아니라 **`embeddableOverrides`의 키와 같은 축**이다 * (AI 경로가 `{field:"currency", …}`로 서브컬럼을 지목한다 — `agent/resolver.ts`). 그 op이 EMBED_PREDEF * 에만 적용되므로 카탈로그 필드명이 곧 계약이고, EMBED_OWN 은 임베더블 소스 속성명(JPA 필드명)이 그에 * 대응한다. FK 등 나머지는 슬롯을 지목하는 어휘가 없고 **물리명이 곧 식별자**라 undefined 를 돌려준다 * (호출자가 물리명으로 폴백한다 — 없는 이름을 지어내지 않는다). * * ⚠️평탄화 순서는 `inheritedSlotOf`의 EMBED_OWN 분기와 **반드시 같아야** 한다(선언순 dbAttrs 이어붙이기). * 어긋나면 라벨과 값이 서로 다른 슬롯을 가리키는 조용한 오답이 된다. 소스 속성이 다중 컬럼이면 그 이름이 * 슬롯 여럿에 반복되는데, 그것이 실제 구조(한 필드가 여러 컬럼)라 구분자를 덧붙이지 않는다. */ export declare function slotFieldName(attr: Attribute, index: number, model: LogicalModel, ctx?: ColumnResolveContext): string | undefined; /** * 상속된 «필드»를 지목한다 — 표시(`text.ts`)와 쓰기 안내(`agent/resolver.ts`)가 **같은 술어**를 쓴다. * * ★배치 근거: 이 모듈이 *「빈 슬롯이 어디서 값을 물려받는지」*의 단일 출처다. 술어를 소비처에 두면 * 표시와 안내가 갈려 *「/text 는 상속이라 하는데 op 응답은 아니라 한다」* 가 난다(이 리포가 반복해 * 겪은 실패 — `excludeDDLGeneration` 4회·상속 해소 5곳). * * ⚠️★**단일 「상속됨」 불리언이 아니다** — 상속은 필드마다 판정되고 전수 실측에서 상속 컬럼 **1,787 중 * 77%가 부분 상속**이다(지배 형상 = 논리명«만» 1,273 = 71%). 뭉뚱그리면 「물리명도 상속」으로 읽혀 * **무신호를 오신호로 바꾼다**. */ /** * 「ctx 자리가 아닌 곳에 들어온 ctx」 구제 — 값이 «객체»면 ctx 로 본다(`null` 은 ctx 부재 표기라 제외). * * ★★**왜 필요한가**: 산출 함수들이 ctx 를 **다른 자리**에 둔다 — `validateModel(model, ctx)`· * `entityModelToText(logical, ctx)` 는 2번째인데 `entityModelToDdl(logical, dialect, ctx)`· * `entityModelToMermaid(logical, dialect, ctx)` 는 3번째다. 한 파일에서 나란히 쓰면 ctx 가 dialect * 자리로 들어가 **조용히 사라지고**, 그 degrade 는 «미주입»과 바이트 동일해서 관측으로 구분되지 않는다 * (실측: 조사 하니스 14파일 23자리가 그렇게 물렸다). * ⚠️★**선언 타입은 좁힌 채로 둔다** — 파라미터 타입을 넓히면 TS 호출자가 받던 컴파일 에러가 사라지고 * 그 에러가 이 함정의 **가장 강한 신호**다. 이 구제는 타입 검사가 없는 호출자(`.mjs` 조사 하니스)에만 * 닿게 하는 것이 의도다. * ★배치 근거: 이 모듈이 **ctx(`ColumnResolveContext`) 개념의 소유자**이고 `ddl`·`mermaid` 둘이 이미 * 여기에 의존한다 — 한쪽 산출 모듈에 두면 다른 산출이 그쪽을 import 하는 어긋난 방향이 된다. */ export declare function misplacedCtx(slot: unknown): T | undefined; export declare const INHERITABLE_COLUMN_FIELDS: readonly ["physicalName", "dataType", "length", "scale", "logicalName"]; export type InheritableColumnField = (typeof INHERITABLE_COLUMN_FIELDS)[number]; /** * `raw` 가 비어 `eff` 가 선언원에서 물려받은 필드 목록. * * ⚠️★**SHARED_REF 자리에서 `physicalName` 은 «상속»이 아니다** — 그 컬럼은 물리명을 저장하지 않고 * **타깃 컬럼에서 파생**하고(`types.ts` 의 `sharedColumnRef` 주석), 표시되는 이름도 선언값이 아니라 * 타깃 물리명이다 ⇒ 상속으로 지목하면 *「보이는 그 이름이 선언에서 왔다」* 는 **틀린 안내**가 된다. */ export declare function inheritedColumnFields(raw: DbColumn | undefined, eff: DbColumn): InheritableColumnField[];