import { Association, Attribute, Entity, LogicalModel, ModelId, SuperClassCatalog, EmbeddableCatalog } from './types'; import { JavaTypeDef } from './javaTypes'; /** * `AttributeConverter` FQN 기본값 — **레거시 생성기 상수 미러**(`bluework-im` `EntityJavaFile.java:77~78` * Y/N 2종 · Money `@Convert`). 프로젝트 소유 FQN 을 emitter 상수로 두는 것은 이 파일의 확립된 관례다 * (위 `FQN` 표의 `net.g1project.*` 8개 · `PREDEF_CUSTOM_TYPE_FQN` 동형 — 카탈로그는 FQN 을 갖지 않는다). * * ★**여기가 단일 출처다** — 종전 Money FQN 은 `resolveJavaType` 안에 박혀 있어서, 컨버터 축이 하나 늘 때마다 * 방출 지점이 흩어질 구조였다(Y/N 축이 세 번째 자리가 될 자리). 축이 늘면 이 표에만 추가한다. * 갈아 끼울 필요가 생기면 `ScaffoldOptions.converterCatalog` 로 축별 override 한다. */ declare const CONVERTER_FQN: { /** Y/N 문자 플래그 — `dataType=VARCHAR` × `PrimitiveBoolean`. */ readonly booleanYnPrimitive: "net.g1project.ecp.common.jpa.converter.PrimitiveBooleanToYNStringConverter"; /** Y/N 문자 플래그 — `dataType=VARCHAR` × `Boolean`(박싱). */ readonly booleanYnBoxed: "net.g1project.ecp.common.jpa.converter.BooleanToYNStringConverter"; /** 레거시 ③ 단일 컬럼 Money(`CustomMoneyType`). forward Money ②(2컬럼)는 `@CompositeType` 경로로 별개. */ readonly money: "net.g1project.bluecomm.money.MoneyAttributeConverter"; /** 비율(`Rate`) — Money 와 **같은 패키지·같은 관용구**. 실물 전수 12/12 이 `@Convert`(예외 0). */ readonly rate: "net.g1project.bluecomm.money.RateAttributeConverter"; }; /** 컨버터 축 — `CONVERTER_FQN` 키. */ export type ConverterAxis = keyof typeof CONVERTER_FQN; /** * 컨버터 FQN override 카탈로그(`ScaffoldOptions.converterCatalog`). 축마다 3-상태다 — * **미주입**=lib 내장 기본값 / **문자열**=그 FQN / **`null`**=명시 비활성(방출 생략 + `fillIn`). * * ★**방출 조건은 주입 대상이 아니다** — 어느 형상에 컨버터를 붙이는지는 레거시 산식으로 고정하고 * (예: Y/N 은 `VARCHAR` × boolean) **FQN 만** 프로젝트가 갈아 끼운다. 조건까지 열면 주입 규약이 곧 * 두 번째 코드젠이 된다. * * ★**다중값(단일 컬럼 Set) 컨버터 축은 여기 없다** — 그 컨버터는 도메인 타입별로 다르므로 프로젝트 단위 * FQN 하나로 대표할 수 없다(기본값을 두면 발명 = 「모르는 값 규약」 위반) ⇒ 현행 `fillIn` 손채움을 유지한다. * 즉 이 표에 들어오는 것은 **종류가 정해진 축**뿐이다. */ export type ConverterCatalog = { readonly [K in ConverterAxis]?: string | null; }; /** * ④ `CustomType` 해소 카탈로그 항목 — `Attribute.customType`(자유 입력 타입명)을 **필드 타입 표현 + * `@Convert` 컨버터 + import** 로 푼다. ③ `PredefinedCustomTypeEntry`(호스트 주입, 단일 컬럼)와 같은 * 관용구지만 그쪽은 **구조 없는 프레임워크 공통 타입**(StoredFile·MultiLangString)이고 이쪽은 * **프로젝트 로컬 POJO**다 — 실물은 구조체 요소 컬렉션을 `@Convert(AttributeConverter, String>)` * 로 **한 컬럼에 JSON 직렬화**한다(실측 21자리 / POJO 9종). * * ★**컬렉션성을 왜 속성이 아니라 타입에 두는가**: 실측에서 **요소 타입당 컨테이너가 하나로 고정**됐다 * (`OrderBrand`→List 3자리 · `OrderSeries`→List 6자리 · `PromoTargets`→`Map` 1자리 — * 같은 요소 타입이 자리마다 다른 컨테이너를 쓰는 사례 **0**). per-usage 축을 두면 그 자유도가 근거 없는 * 발명이 된다(Money currency 의 per-usage 판단과 반대 결론이고, 근거는 양쪽 다 실측이다). * * ★미주입·미등록이면 **종전 동작 그대로**(verbatim 타입명 + `CustomType 확인 필요` fillIn) — inert. */ export interface CustomTypeEntry { /** 필드 타입 표현. 컬렉션이면 컨테이너 포함(`List`). 생략 시 `customType` 값 그대로. */ fieldType?: string; /** * `AttributeConverter` FQN. `null`·공백은 *"이 프로젝트는 이 타입의 컨버터를 쓰지 않는다"* 를 침묵이 아니라 * 신호로 남기는 표기다(리포 규약: `null` ≡ 부재) ⇒ `@Convert` 를 생략하고 `fillIn` 으로 표면화한다. * 미지정(undefined)도 방출할 FQN 이 없으므로 같은 처리이고 **문구로 둘을 구분**한다. */ converterFqn?: string | null; /** 방출할 import FQN — 요소 타입·`Map` 키 타입 등. 컨테이너(java.util 3종)는 `fieldType` 에서 자동 유도. */ imports?: readonly string[]; } /** ④ CustomType 카탈로그 — 키는 `Attribute.customType` 값(단순명 또는 FQN). */ export type CustomTypeCatalog = Readonly>; export interface ScaffoldOptions { /** * **산출 불가 대상 자동 제외**(기본 `true`) — 엔티티명 미지정 / `@Id` 없는 JPA 엔티티는 파일을 만들지 * 않는다. 레거시 배치 선택 계층 패리티(`EntityJavaSrcGen.generateImplModel:105·108`이 각각 * `isBlank(entityName)`·`JPA_ENTITY && !hasIdField`를 `logger.warn + continue`로 스킵). * * ★레거시는 이 판정을 **선택 계층**(generateImplModel)에 두고 렌더러(EntityJavaFile)와 분리한다. * lib은 두 역할이 `scaffoldEntityJava` 한 함수에 겹쳐 있어 옵션으로 seam을 만든다 — 모듈 산출 * (호스트 오라클)은 기본값을 쓰고, **렌더러로 직접 쓰는 경우**(단위 테스트·부분 렌더 프리뷰)는 * `false`로 꺼서 PK 없는 축약 모델도 렌더한다. */ skipUngeneratable?: boolean; /** 저장값→소스 타입 역해소 카탈로그. 미주입 시 lib 내장 `JAVA_TYPES` 폴백(호스트 javaTypeCatalog 미러). */ javaTypeCatalog?: readonly JavaTypeDef[]; /** 임베더블 타입 해소용(EMBED_PREDEF 필드 타입·클래스명). 미주입 시 모델 type 폴백 + fillIn. */ embeddableCatalog?: EmbeddableCatalog; /** * `AttributeConverter` FQN override. 미주입 시 lib 내장 `CONVERTER_FQN` 폴백(레거시 생성기 상수 미러) — * `javaTypeCatalog` 와 같은 관용구다. 축별 3-상태·조건 비주입 근거는 {@link ConverterCatalog}. */ converterCatalog?: ConverterCatalog; /** * ④ `CustomType`(자유 입력 `Attribute.customType`) 해소 카탈로그 — 직렬화 컬렉션/프로젝트 로컬 직렬화 타입 축. * `javaTypeCatalog`·`converterCatalog` 와 같은 **호스트 주입** 관용구이고 미주입 시 종전 폴백(inert). * 계약·근거는 {@link CustomTypeEntry}. */ customTypeCatalog?: CustomTypeCatalog; /** * `extends ` + 상속 컬럼 인덱스 해소용. 핸드오프 O3 — `SuperClassDef.packageName`이 * "코드젠 forward 슬롯"으로 예약돼 있고 이 emitter가 그 첫 소비처다. 미주입 시 상속 컬럼/extends 미방출. */ superClassCatalog?: SuperClassCatalog; /** * 패키지 조합 설정(레거시 codegen 이식 — 프로젝트/모듈 설정 + 그룹 조합). 모델 엔티티엔 packageName 이 * 없고(모델링 도구 특성), 코드젠이 `{basePackage}.{그룹 packageName}.{layerSuffix}` 로 조합한다. * - `basePackage`: 모듈 base(호스트 `srcGenConfig.bizModuleConfigs[moduleId].basePackage`, 예 `net.g1project.ecp.sales.order`). * - `layerSuffix`: DDD 레이어 접미(기본 `command.domain` — 실물 골든 관례). 그룹 packageName 이 base 마지막 * 세그먼트와 중복돼도 정상(대형 모듈은 base 안에 order/claim 서브패키지 존재). 그룹 미소속/무 packageName 이면 * 그룹 세그먼트 생략. 미주입 시 placeholder 폴백(reconcile 몫). */ packageConfig?: { basePackage: string; layerSuffix?: string; }; /** * GroupCodeEnum 속성의 enum 클래스가 사는 패키지(레거시 codegen 컨벤션 = const 프로젝트 basePackage + * `.code`, 예 `net.g1project.ecp.common.code`). 주입 시 `@Enumerated` 필드에 `{pkg}.{groupCode}` import 를 * 방출한다. 미주입 시 import 생략 + fillIn(패키지 확인). 호스트가 srcGenConfig.constProjectConfig 에서 도출. */ groupCodeEnumPackage?: string; /** * Lombok 방출 모드(기본 `true` — 모델러 사용 프로젝트가 사실상 전부 Lombok). true면 **필드 단위** * `@Getter`(스칼라·단일 임베드·owner 관계, 대형 엔티티 SalesOrch류 관례) + 클래스 `@NoArgsConstructor * (access = AccessLevel.PROTECTED)`를 방출하고 수동 getter·protected 생성자를 생략한다. **컬렉션 관계** * getter는 @Getter가 raw 컬렉션을 반환해 애그리거트 보호가 깨지므로 예외 — 수동 `Collections.unmodifiable…` * 유지(@Getter 미부착). **@Version**은 JPA 관리 내부 필드라 getter 제외(실측 37/38 무 getter). * `{Entity}PK`는 필드 @Getter만, equals/hashCode·생성자는 수동. `false` = 수동 전량(생성기/코퍼스 패리티, * 바이트 골든 앵커 모드 — @Version getter 포함). */ lombok?: boolean; } export interface ScaffoldedEntity { modelId: ModelId; /** `{ClassName}.java` */ fileName: string; /** 조합·결정된 패키지(소비자 UI의 패키지 트리 그룹핑용). placeholder 폴백 시 `com.example`. */ packageName: string; /** 컴파일 가능 최소 골격 자바 소스(`// TODO [손채움]` 포함). */ source: string; /** 사람이 채울 지점 목록(라벨) — reconcile 보고용. */ fillIns: string[]; /** 신규 엔티티 경로에서만 write(기존엔 재추가 금지, §4.4) — reconcile 판단. */ repository?: { fileName: string; source: string; }; } /** * 대상 엔티티들을 JPA 자바 소스로 렌더한다. `logical`은 참조 폐포(FK 대상 등) 해소를 위해 전체를 받고, * `targetIds`가 실제 산출(파일 생성) 스코프다. 스코프 밖 엔티티는 읽기(폐포)에만 쓰이고 산출되지 않는다. */ /** * 산출 대상 판정 — 레거시 배치 선택 계층(`EntityJavaSrcGen.generateImplModel:105·108`) 패리티. * * - 엔티티명 미지정: 방출하면 `undefined.java` / `public class undefined {`가 나간다(실측 module-order). * - `@Id` 없는 JPA 엔티티: JPA는 식별자가 필수라 방출해도 부트스트랩 불가. * * 판정은 레거시 `hasIdField`→`countIdAttributes`(자기 속성 중 identifier, attrType 무관)와 동형. * 레거시의 `countReferredIdAttributes` 보정분은 v2에선 식별 전파로 자식 attributes에 FK가 * `identifier=true`로 물질화되므로 여기에 이미 포함된다. * * ⚠️ 레거시는 abstractClass도 예외 없이 스킵한다(:108에 예외 조항 없음). lib `CHK-JPA-1`은 추상 베이스를 * 면제하므로 "모델은 정당한데 산출은 제외"되는 divergence가 이론상 가능하다 — 다만 abstract이면서 PK 없는 * `@Entity`는 `@MappedSuperclass` 없이 JPA 자체가 불가하고, 실 데이터(로컬 BNKR_SALES) PK 없는 JPA 엔티티 * 17건 중 abstract는 0건이라 레거시 시맨틱을 그대로 따른다. * * 모델 레벨 신호는 검증이 담당한다 — 엔티티명 빈값=`CHK-NAME-4`, PK 부재=`CHK-JPA-1`. */ export declare function isGeneratable(entity: Entity): boolean; export declare function scaffoldEntityJava(logical: LogicalModel, targetIds: readonly ModelId[], opts?: ScaffoldOptions): ScaffoldedEntity[]; /** * 식별 FK가 `@MapsId`(관용구4)인지 판정 — **단일 컬럼 @OneToOne 식별 FK**는 대상 PK를 공유하는 파생 * 식별이다(실물 OrderShipping: `@Id String orderNo` + `@MapsId @OneToOne order`가 order_no 컬럼 공유). * 이 경우 FK는 스칼라 @Id에 매핑될 뿐 별도 PK 멤버가 아니므로 PK 카디널리티 카운트에서 제외한다. * 걸러지는 쪽 = 직접 @Id 멤버(관용구3 @Id@ManyToOne[단수 아님]·관용구5 대상 복합키[컬럼 2+]). * * ★★**종전엔 조건이 하나 더 있었다** — *"스칼라 @Id와 같은 물리 컬럼을 공유"*. 그 조건은 모델에 스칼라 * PK가 **실재할 것**을 요구했는데 FK 전파는 FK 속성만 만들고 스칼라를 만들지 않으므로(`propagation.ts`) * **프로덕션 발동이 0**이었고, 실물이 `@MapsId` 21/21인데 모델은 22건 전부 관용구5로 방출되는 **전면 * 드리프트**가 났다. 스칼라 필드는 이제 **방출 시점에 합성**하므로(`derivedIdFieldName` ?? 부모 PK * 물리명 camelCase — 실물 관행 19/19) 그 조건이 불요해졌고, 모델은 **속성 하나**를 유지한다 * (⇒ 물리명 중복이 생기지 않아 `CHK-NAME-2`·DDL 이 무변경). 근거=`.claude/docs/MapsIdIdiom-entry_2026-08-26.md` §3.A. * ★관용구5(`@Id`를 연관 필드에 직접)는 JPA 정식이지만 **이 코드베이스 실물에 0건**이라 선택지를 두지 * 않았다(§3.A (가) 확정 — 요구가 관측되면 관용구 선택 축을 신설). */ export declare function isMapsIdFk(fk: Attribute, assoc: Association | undefined): boolean; /** * `@MapsId` FK가 매핑되는 **스칼라 @Id의 필드명**. 모델 지정(`derivedIdFieldName`)이 우선이고, 없으면 * **부모 PK 물리명의 camelCase**로 파생한다(실물 21건 대조 19/19 — 모델 속성명 폴백은 18/19로 이름 결손 * 1건에서 어긋나므로 물리명 쪽을 택했다). 부모 컬럼도 미해소면 FK 자신의 물리명, 그마저 없으면 빈 문자열 * (호출부가 fillIn으로 표면화). */ export declare function mapsIdScalarName(fk: Attribute, assoc: Association, byId: Map): string; /** * 이 속성이 **방출 시점에 합성되는** `@MapsId` 스칼라 `@Id` 를 낳으면 그 **필드명**, 아니면 빈 문자열. * * ★**「모델 속성 하나가 Java 필드 «둘» 을 낸다」의 단일 출처**다 — 판정(`isMapsIdFk`)·관계 조달 * (`findFkOwningAssociation`)·이름 파생(`mapsIdScalarName`) 셋을 한 자리에 묶어, 소비처가 조건을 * 재기술하지 않게 한다. 재기술하면 축이 갈린다(이 리포가 반복해서 대가를 치른 형태). * * ⚠️**소비처는 같은 술어를 쓰되 처방이 다르다**: 코드젠은 **방출**하고(`buildClassFieldPlan`), * 캔버스는 코드명으로 **표시**하며(`view/columnResolution`), `/text` 는 `derivedIdField=` 로 * **자기기술**하고(`core/text`), AI 쓰기 경로는 같은 이름의 속성 생성을 **거부**한다(`agent/resolver`). * 넷이 한 술어를 공유하는 것이 이 함수의 목적이다. * * 근거=`.claude/docs/MapsIdIdiom-entry_2026-08-26.md` §3.A */ export declare function derivedIdScalarNameOf(attr: Attribute, entity: Entity, logical: LogicalModel, byId?: Map): string; /** * `@MapsId` 스칼라 @Id의 **Java 타입** — 이름과 **같은 경로**(`derivedFrom.sourceAttributeRef`)로 조달한다. * * ★`pkMemberFieldType(fk, …)`을 직접 쓰면 안 된다 — 그 함수는 부모를 **`member.type`(엔티티명)으로 조회** * 하는데 **실 데이터의 FK 속성은 `type`이 빈 문자열**이다(전파가 채우지 않는다 — 실측 `PaymentMethodPolicy` * `{name:'paymentMethod', type:''}`). 그러면 부모 미해소로 `Object`가 나간다(실물은 `String`). 반면 * `derivedFrom`은 부모 PK 속성을 **modelId로 직접** 가리키므로 해소율이 19/19다. * ⚠️이것은 이 변경이 만든 결함이 아니라 **선재 결함의 노출**이다 — 같은 경로를 `{Entity}PK` 필드 타입과 * 단일키 Repository 타입도 쓰고 있어 그쪽도 `Object`가 나가고 있었다. */ export declare function mapsIdScalarType(fk: Attribute, assoc: Association, byId: Map, logical: LogicalModel, opts: ScaffoldOptions): string; /** 이 엔티티가 내는 Java 필드 한 줄의 역할. */ export type ClassFieldRole = 'scalar' | 'embed' | 'relation-owner' | 'relation-inverse'; /** 방출되지 않는 모델 속성의 사유 — `no-anchor` 만 신호 대상(나머지는 정상 스킵). */ export type ClassFieldOmitReason = 'join-column-only' | 'no-anchor' | 'nav-not-materialized'; export interface ClassField { /** 표시·방출 대상. 합성 스칼라(`synthetic`)는 **모델에 없는 가상 속성**이다. */ attr: Attribute; role: ClassFieldRole; /** 모델에 없는 합성 행(@MapsId 스칼라 @Id) — 편집·삭제·재정렬 대상이 아니다. */ synthetic: boolean; /** 이 행을 만든 모델 속성(합성 스칼라면 그 FK, 아니면 `attr` 자신). 행 → 모델 역참조. */ sourceAttr: Attribute; assoc?: Association; /** relation-* 의 대상 엔티티 — **CLASS 타입 칸의 출처**(JPA 필드 타입이 곧 이 클래스다). */ target?: Entity; /** relation-inverse 의 컬렉션 래핑(to-many). */ collection?: 'LIST' | 'SET'; } export interface ClassFieldPlan { /** 방출 순서대로의 필드 목록 — **CLASS 뷰의 행 목록이 이것과 같아야 한다**. */ fields: ClassField[]; /** 방출되지 않는 모델 속성 + 사유. `no-anchor` 는 컬럼 누락이라 호출부가 신호한다. */ omitted: { attr: Attribute; reason: ClassFieldOmitReason; }[]; /** @MapsId 스칼라 필드명 미해소 — 코드젠이 `fillIn` 으로 표면화한다. */ unresolvedDerivedIds: Attribute[]; } /** * **이 엔티티가 방출하는 Java 필드 목록** — 코드젠·캔버스·탐색기의 단일 출처. * * 모델 속성과 **1:1이 아니다**. 세 축에서 갈라진다: * 1. **@MapsId**: 속성 하나가 **두 필드**를 낸다 — 합성 스칼라 `@Id` + `@MapsId` 연관 필드. * (모델이 속성 하나를 유지하는 것은 *모델 결정*[물리명 중복·전파·검증·DDL 무변경]이지 표시 결정이 * 아니다. 상세=`.claude/docs/MapsIdIdiom-entry_2026-08-26.md` §3.A) * 2. **복합 FK 형제**: 앵커가 `@ManyToOne` + `@JoinColumns` 를 소유하고 형제는 컬럼만 보태므로 * **필드를 내지 않는다**(`fkDerived.isJoinColumnOnlyFk`). * 3. **navigable off 인 `RELATION_REF`**: 앵커에 안 잡혀 `emitInverseRelation` 이 호출되지 않는다. * * ⇒ CLASS 뷰가 모델 속성을 1:1로 그리면 **방출되지 않는 행을 보여주고 방출되는 필드를 감춘다**. * 실측(프로덕션 v2 29문서, 2026-08-27): ①+22행 ②−1행 ③−18행 = **41행 어긋남**. * * ★**타입 해소는 여기 없다** — 소비처마다 답이 다르기 때문이다. 코드젠은 카탈로그·패키지를 타고 * Java 타입 FQN을 뽑고(`syntheticType` 주입), 뷰는 `javaTypes.attrTypeCell` 로 표시 라벨을 만든다. * 이 함수가 소유하는 것은 **행 정체성**(무엇이 몇 줄로 나가는가)이고 그것만 단일 출처다. * * @param syntheticType 합성 스칼라의 `type` 해소기. 미주입 시 **부모 PK 속성의 `type`을 그대로** 쓴다 * (모델 값 — 뷰의 표시 라벨엔 그것으로 충분하다). */ export declare function classFields(entity: Entity, logical: LogicalModel, syntheticType?: (fk: Attribute, assoc: Association) => string): ClassFieldPlan; export {};