import { ClassStereotype, Entity, MultiLangText, Multiplicity, OperationVisibility } from '../core/types'; /** * 요소 지정 핸들 — modelId 대신 이름/물리명. * 엔티티: 물리명(`table.physicalName`) 우선, 논리명(`name`) 보조 — 논리명은 모델 내 중복 허용(CHK-NAME-3)이라 모호 가능. * 속성: 소속 엔티티 내 속성명(`name`) 또는 컬럼 물리명(`dbAttrs[].physicalName`) — 둘 다 엔티티 내 유일(CHK-NAME-1/2). */ export interface Handle { /** 매칭할 이름/물리명. `by:'modelId'`면 modelId 원문. */ ref: string; /** * 매칭 키 고정. 미지정 시 대상별 기본 순서로 시도(엔티티=물리명→논리명, 속성=논리명→물리명). * * ★`'modelId'`는 **모호 해소 탈출구**다 — 이름·물리명이 둘 다 겹쳐 주소지정이 불가능한 대상이 실재한다 * (임베더블은 `table`이 없어 물리명 차원 자체가 없고, 같은 테이블에 매핑된 동명 엔티티도 실 데이터에 * 있다). resolver가 모호를 보고할 때 후보를 판별 맥락과 함께 돌려주므로, 사람이 그중 하나를 골라 * 이 키로 확정한다. 평시엔 쓰지 않는다(핸들 설계 취지는 "LLM은 사람 용어로만 말한다"). */ by?: 'physicalName' | 'name' | 'modelId'; } /** * 관계 지정 — 관계는 무명이라 양 끝 엔티티로 주소한다. * `from`=부모(end1, PK 원천 "1"측), `to`=자식(end2, FK 보유 "N"측). * 같은 (from,to) 쌍에 복수 관계가 있으면 모호 → resolver가 escalation. */ export interface AssocHandle { from: Handle; to: Handle; } /** * entity.add 페이로드 — resolver가 modelId 발급 + 골격 조립. * * **inline fold**: 신규 엔티티의 자식(속성·인덱스·operation)은 별도 `attribute.add` 등으로 보내지 않고 * 여기에 inline 배열로 담는다. resolver가 완성형 엔티티를 한 `entity.add`로 조립해 단일 `$push`로 들어간다. * (별도 child op는 mongo arrayFilter 선이미지 한계로 *같은 배치 신규 엔티티*에 닿지 못해 silent no-op이 된다 — * 그래서 신규 엔티티 자식은 op 분리가 아니라 inline이 정공법. 분리 emit은 resolver가 `pending-entity-ref`로 거부.) * `attribute.add`/`index.add`/`operation.add` op은 *기존* 엔티티에 자식을 추가하는 용도로 남는다. */ export interface EntitySpec { /** * **클래스** 논리명(다국어) — `Entity.classLogicalName`. 테이블이 없는 자리(직렬화 타입 * `SERIALIZED_TYPE`)의 논리명이 여기 산다(그 타입은 `table` 자체를 갖지 않는다). * ⚠️테이블이 있는 엔티티는 `table.logicalName`(dotted-key)이 이기므로 그쪽을 쓴다. */ classLogicalName?: MultiLangText; name: string; /** 기본 'JPA_ENTITY'. */ stereotype?: ClassStereotype; /** 설정 시 `table.physicalName`. */ physicalName?: string; /** * JPA `@Version`(낙관적 락) 사용 여부 → `Entity.jpaAttrs.useVersion`. 코드의 `@Version` 필드는 별도 속성으로 * 모델링하지 않고 이 엔티티 레벨 플래그로 흡수한다(버전 컬럼은 호스트 코드젠 소유). resolver가 true일 때만 * `jpaAttrs.useVersion:true`를 세팅(false/미설정은 생략 — round-trip diff 방지). *기존* 엔티티에 켤 땐 이 스펙이 * 아니라 `entity.update` patch의 **dotted key** `{'jpaAttrs.useVersion': true}`를 써야 형제 jpaAttrs 플래그를 * 통째 $set로 덮어쓰지 않는다(호스트 opInterpreter는 patch를 per-key $set). */ useVersion?: boolean; /** inline 속성 — 배열 순서가 곧 `order`. GroupCodeEnum+groupCode 규칙은 `attribute.add`와 동일하게 강제. */ attributes?: AttrSpec[]; /** inline 인덱스 — 컬럼 핸들은 *이 엔티티가 inline으로 만드는* dbAttr 내에서 해소된다(속성 inline 동반 전제). */ indexes?: IndexSpec[]; /** inline 도메인 메서드 — 배열 순서가 곧 `order`. */ operations?: OperationSpec[]; /** * 직렬화 축 — 이 타입이 **소비될 때의 컨테이너**(`stereotype: 'SERIALIZED_TYPE'` 일 때만 의미). * 타입을 SoT 로 두는 근거는 `core/types.ts` `Entity.serialization` 주석(요소 타입당 컨테이너가 하나로 * 고정된다는 실측). 미지정 = 컬렉션이 아닌 단일 값. * * ★**신규 엔티티는 add 시점에 줘야 한다** — 같은 배치의 `entity.update` 는 갓 만든 엔티티에 닿지 못한다 * (pending-entity-ref). `useVersion` 과 같은 이유·같은 처방이다. */ serialization?: Entity['serialization']; } /** attribute.add 페이로드 — resolver가 modelId 발급 + dbAttr 골격 조립. */ export interface AttrSpec { name: string; /** * Java 타입(예: 'Long', 'String'). 카탈로그 저장값. * `'GroupCodeEnum'`은 파생 타입이라 **`groupCode` 동반 필수** — 없으면 resolver가 거부(groupCode 바인딩 * 없는 깨진 파생 타입 방지). groupCode 지정 시 코드성 속성으로 해소된다. */ type: string; /** * 이 **필드**가 컬렉션인가 — **직렬화 타입(`SERIALIZED_TYPE`)의 자기 필드 전용**(`Set`· * `List`). 컬럼 있는 자리는 읽지 않는다 — 그쪽 컨테이너는 «요소 타입»이 소유한다 * (`entity.add/update` 의 `serialization.collectionType`). SoT=`core/types.ts` `Attribute.collectionType`. */ collectionType?: 'LIST' | 'SET'; identifier?: boolean; notNull?: boolean; /** * JPA `@Transient` — 비영속 속성. true면 `Attribute.transient:true`(속성 레벨 플래그). 컬럼을 만들지 않는 * 속성이므로 physicalName/dataType 등 컬럼 필드는 함께 주지 않는다(주면 dbAttr가 생성됨). false/미설정은 * 생략(round-trip diff 방지). *기존* 속성에 켤 땐 `attribute.update` patch `{transient:true}`로도 가능. */ transient?: boolean; /** 설정 시 단일 dbAttr 컬럼 생성(미설정이면 name을 물리명으로). */ physicalName?: string; /** dbAttr 물리 타입(예: 'BIGINT'). physicalName/dataType/length/scale 중 하나라도 있으면 dbAttr 생성. */ dataType?: string; /** dbAttr 컬럼 길이(VARCHAR(n)·DECIMAL precision). 0/미설정은 미지정. */ length?: number; /** dbAttr 소수 자릿수(DECIMAL/NUMERIC scale). length 동반이 일반적. 0/미설정은 미지정. */ scale?: number; /** * dbAttr 채번 시퀀스명(`DbColumn.sequenceName`). autoIncrement 없는 비PK 컬럼이면 «애플리케이션 채번»이고 여러 * 속성이 같은 이름을 공유해도 정상(공유 채번기). 컬럼 생성 트리거 필드(dataType 동반 필수). */ sequenceName?: string; /** * 코드성 속성의 group code 바인딩(레거시 AttributeModel.groupCode). `type:'GroupCodeEnum'`이면 필수. * 모델→모델 복사·코드 동기화에서 GroupCodeEnum 속성을 온전히 재현하는 핵심 필드(없으면 깨진 파생 타입). */ groupCode?: string; /** 속성 기본값(레거시 AttributeModel.defaultValue, 문자열 패스스루). */ defaultValue?: string; /** 속성 분류 그룹(레거시 attributeGroup, 예: 'code'·'amt'). 표시·분류 전용 패스스루. */ attributeGroup?: string; /** 속성 설명(다국어, 레거시 AttributeModel.description). */ description?: MultiLangText; /** * dbAttr 컬럼 논리명(다국어, 예: {ko:'체크아웃유형'}). 한글 업무명이 모델→모델 복사에서 보존되는 위치 — * 속성 자체는 단일 식별자 `name`만 갖고, 다국어 업무명은 컬럼에 붙는다. 컬럼 생성 시에만 의미. */ columnLogicalName?: MultiLangText; /** * **필드** 논리명(다국어) — `Attribute.fieldLogicalName`. 위 `columnLogicalName`(컬럼에 붙는 논리명)과 * **다른 축**이고, 컬럼이 없는 자리(직렬화 타입 `SERIALIZED_TYPE` 의 자기 필드)의 논리명이 여기 산다. * 그 자리는 `dbAttrs` 가 구조적으로 0이라 컬럼 논리명을 줄 데가 없다. * ⚠️컬럼이 있는 자리에서는 컬럼 논리명이 이긴다(`core/columnResolve.effectiveLogicalName`). */ fieldLogicalName?: MultiLangText; /** dbAttr UNIQUE 제약. */ unique?: boolean; /** dbAttr 갱신 가능 여부(JPA @Column(updatable=)). */ updatable?: boolean; /** * predefined 임베더블 서브컬럼 오버라이드 — `type`이 호스트 주입 임베더블 카탈로그 엔트리와 매칭될 때만 * 의미. resolver가 카탈로그 fields를 그대로 펼친 뒤(EMBED_PREDEF, 다중 dbAttr) 이 배열을 **필드명**으로 * 매칭해 서브컬럼을 덮어쓴다(JPA `@AttributeOverride` 대응). 미설정 서브컬럼은 카탈로그 기본을 상속한다. * 카탈로그 미매칭 type에 이 배열을 주면 resolver가 `embeddable-type-unresolved`로 거부(silent drop 방지). */ embeddableOverrides?: EmbeddableColumnOverride[]; } /** * predefined 임베더블(EMBED_PREDEF)의 개별 서브컬럼 오버라이드. 매칭 키는 카탈로그 필드명 * (`EmbeddableCatalogField.name`, 예: 'amount'·'currency') — 순서 독립. 미설정 필드는 오버라이드 없음(카탈로그 상속). */ export interface EmbeddableColumnOverride { /** 대상 카탈로그 필드명(EmbeddableCatalogField.name). 예: 'amount', 'currency'. */ field: string; /** 서브컬럼 물리명 오버라이드(@AttributeOverride column name). 미설정 시 카탈로그 기본 컬럼명 상속. */ physicalName?: string; /** 서브컬럼 길이 오버라이드. */ length?: number; /** 서브컬럼 소수 자릿수 오버라이드. */ scale?: number; /** JPA @Column(insertable=). OWN 컬럼에만 저장(SHARED_REF는 타깃 파생). */ insertable?: boolean; /** JPA @Column(updatable=). OWN 컬럼에만 저장(SHARED_REF는 타깃 파생). */ updatable?: boolean; /** * JPA @Column(nullable=false) — **이 서브컬럼 하나의** NOT NULL. 미설정이면 속성 축(`notNull`)을 따른다. * ★임베더블은 컬럼마다 제약이 갈릴 수 있고(실측: `ChangedBy` 의 type 컬럼만 `NOT NULL`) 속성 축 하나로는 * 그 비대칭을 담을 수 없다 — 그래서 서브컬럼 축이 있다(SoT=`core/types.ts` `DbColumn.notNull`). * OWN 컬럼에만 저장(SHARED_REF는 타깃 파생). */ notNull?: boolean; /** * SHARED_REF — 이 서브컬럼이 자체 물리 컬럼을 갖지 않고 같은 엔터티의 다른 컬럼(**물리명**)을 공유해 * read-only 투영됨(대표: Money의 currency가 별도 스칼라 통화 컬럼 공유). 설정 시 resolver가 그 물리명을 * 소속 엔터티 내 기존 컬럼 modelId로 해소해 `DbColumn.sharedColumnRef`로 바인딩하고 physicalName· * insertable·updatable은 저장하지 않는다(타깃 파생). 타깃 컬럼은 해소 시점 존재해야 한다(부재=거부). * JPA 코드에서 currency 서브컬럼의 `insertable=false && updatable=false`가 이 모드의 시그니처. */ sharedColumnPhysicalName?: string; } /** association.add 페이로드 — from/to 엔티티는 resolver가 해소, modelId 발급 + end 조립. */ export interface AssocSpec { /** 부모(end1, PK 원천). */ from: Handle; /** 자식(end2, FK 보유). */ to: Handle; /** end1 다중성(기본 EXACTLY_ONE_INSTANCE). */ fromMultiplicity?: Multiplicity; /** end2 다중성(기본 ZERO_OR_MORE_INSTANCES). */ toMultiplicity?: Multiplicity; /** 식별 관계 여부(부모 PK가 자식 PK로 전파). self-association은 식별 불가(INV-3) — resolver가 거부. */ identifying?: boolean; /** end2 composition(부모가 자식 생명주기 소유). */ composition?: boolean; } /** * 그룹 지정 핸들 — 그룹엔 테이블(물리명) 차원이 없어 `Handle` 과 `by` 축이 다르다. * * 실측(프로덕션 v2 29문서·그룹 220): **`name` 은 220/220 전량 보유**하고 `packageName` 은 177(80%)만 * 있다. 그래서 기본은 엔티티와 같은 관용(물리 정체성 우선 → 논리 보조)으로 `packageName` → `name` * 순서를 시도하되, packageName 이 없는 43개는 자연히 name 으로 잡힌다. * * ★`name` 은 `MultiLangText` 라 **어느 로케일 값이든 일치하면 매칭**한다(사람이 부르는 이름이 로케일마다 * 다를 수 있고, 어느 하나를 정본으로 고르면 나머지 로케일로 부른 요청이 조용히 not-found 가 된다). * ★문서 내 중복이 실재한다(실측 packageName 4종·ko 이름 3종) ⇒ 모호는 **후보를 동봉해 보고**하고 * `by:'modelId'` 로 확정한다(엔티티 축과 같은 형태). */ export interface GroupHandle { /** 매칭할 패키지명/이름. `by:'modelId'`면 modelId 원문. */ ref: string; /** 매칭 키 고정. 미지정 시 packageName → name 순서. */ by?: 'packageName' | 'name' | 'modelId'; } /** * group.add 페이로드 — 엔티티 멤버를 묶는 논리 그룹(레거시 LogicalGroup). resolver가 modelId 발급 + * 멤버 핸들을 entity modelId(`memberEntityRefs`)로 해소. 그룹 멤버십은 값 포함이 아닌 **id 참조**(엔티티는 * 그룹과 독립 존재). 멤버는 *기존* 엔티티여야 한다 — 같은 배치 신규 엔티티(entity.add)는 닿지 못하므로 * (top-level 그룹은 inline fold 불가) resolver가 `pending-entity-ref`로 거부, 엔티티 배치 확정 후 별도 배치로. * 그룹 박스 좌표(groupLayout)는 호스트가 멤버 엔티티 레이아웃을 감싸 incidental로 첨부(C1 동형). */ export interface GroupSpec { /** 그룹 논리명(다국어, 예: {ko:'주문'}). 패키지 기반 그룹이면 보통 패키지 leaf의 업무명. */ name?: MultiLangText; /** 자바 패키지명 등 그룹의 물리 식별(레거시 LogicalGroup.packageName). */ packageName?: string; /** 그룹 설명(자유 텍스트 — 모듈/도메인 경계 의도 메모). */ description?: string; /** DDL 생성 제외 여부(기본 false). */ excludeDDLGeneration?: boolean; /** 멤버 엔티티 핸들(물리명 우선/논리명 보조). resolver가 entity modelId로 해소. */ members: Handle[]; } /** * index.add 컬럼 지정 — 인덱스 컬럼은 dbAttr(컬럼) modelId(`IndexColumn.columnRef`)를 참조하나, * 핸들은 사람이 지정 가능한 물리명/속성명이다. resolver가 소속 엔티티 내에서 dbAttr modelId로 해소한다. */ export interface IndexColumnSpec { /** * 컬럼 핸들 — 컬럼 물리명(`dbAttrs[].physicalName`) 우선, 속성명(`name`) 보조. * 속성명으로 지정 시 그 속성이 단일 컬럼이어야 한다(다중 dbAttr=임베드/Money는 모호 → resolver 거부). */ column: Handle; /** 내림차순 정렬 컬럼(기본 false=오름차순). */ descending?: boolean; } /** index.add 페이로드 — resolver가 modelId 발급 + 컬럼 핸들을 dbAttr modelId로 해소. */ export interface IndexSpec { name: string; /** UNIQUE 인덱스 여부(기본 false). */ unique?: boolean; /** 인덱스 컬럼(순서 의미 있음). 비어 있으면 컬럼 없는 인덱스(허용 — 값 품질은 검증 엔진). */ columns: IndexColumnSpec[]; /** 인덱스 설명(레거시 IndexModel.description, 평문). */ description?: string; /** 인덱스 파라미터(레거시 IndexModel.parameters, 패스스루). */ parameters?: string; } /** * operation.add 페이로드 — 도메인 메서드(레거시 OperationModel). resolver가 modelId 발급 + order 누적. * 파라미터·반환 타입은 별도 모델 필드가 아니라 `sourceCode`(Java 메서드 본문 텍스트)에 담긴다. * legacyRaw(소스 엔티티 parent back-ref·properties)는 복사 시 stale이 되므로 op로 다루지 않는다. */ export interface OperationSpec { name: string; /** 가시성(기본 미설정 → 호스트/렌더 기본). */ visibility?: OperationVisibility; /** Java 메서드 본문(멀티라인, 파라미터·반환 시그니처 포함). */ sourceCode?: string; /** 메서드 설명(다국어). 레거시 직렬화는 평문이라 어댑터가 평문↔{ko} 변환(types.ts Operation 주석). */ description?: MultiLangText; /** 개인정보 속성 유형(레거시 personalInfoAttributeType 패스스루). */ personalInfoAttributeType?: string; } /** * LLM이 산출하는 단일 심볼릭 op. `kind`는 op.ts 어휘와 1:1(로케이터만 심볼릭). * `patch`는 OpShape.patch와 동형(부분 patch, 그대로 전달 — 값 품질은 검증 엔진이 사후 경고). */ export type SymbolicOp = { kind: 'entity.add'; spec: EntitySpec; } | { kind: 'entity.update'; entity: Handle; patch: Record; } | { kind: 'entity.remove'; entity: Handle; } | { kind: 'attribute.add'; entity: Handle; spec: AttrSpec; } | { kind: 'attribute.update'; entity: Handle; attribute: Handle; patch: Record; embeddableOverrides?: EmbeddableColumnOverride[]; } | { kind: 'attribute.remove'; entity: Handle; attribute: Handle; } | { kind: 'attribute.reorder'; entity: Handle; order: Handle[]; } | { kind: 'association.add'; spec: AssocSpec; } | { kind: 'association.remove'; association: AssocHandle; } | { kind: 'association.update'; association: AssocHandle; patch: Record; } | { kind: 'associationEnd.update'; association: AssocHandle; end: 'from' | 'to'; patch: Record; } | { kind: 'index.add'; entity: Handle; spec: IndexSpec; } | { kind: 'index.update'; entity: Handle; index: Handle; patch: Record; } | { kind: 'index.remove'; entity: Handle; index: Handle; } | { kind: 'operation.add'; entity: Handle; spec: OperationSpec; } | { kind: 'operation.update'; entity: Handle; operation: Handle; patch: Record; } | { kind: 'operation.remove'; entity: Handle; operation: Handle; } | { kind: 'operation.reorder'; entity: Handle; operations: Handle[]; } | { kind: 'group.add'; spec: GroupSpec; } | { kind: 'group.update'; group: GroupHandle; patch: Record; } | { kind: 'group.remove'; group: GroupHandle; } | { kind: 'group.addMember'; group: GroupHandle; entity: Handle; } | { kind: 'group.removeMember'; group: GroupHandle; entity: Handle; }; /** * v1이 다루는 심볼릭 op 종류의 닫힌 집합(단일 출처). resolver·JSON Schema(`schema.ts`)가 공유한다. * 아래 컴파일타임 단언이 이 튜플과 `SymbolicOp['kind']`의 일치를 강제 — 한쪽만 늘리면 타입 에러. */ export declare const SYMBOLIC_OP_KINDS: readonly ["entity.add", "entity.update", "entity.remove", "attribute.add", "attribute.update", "attribute.remove", "association.add", "association.remove", "association.update", "associationEnd.update", "index.add", "index.update", "index.remove", "operation.add", "operation.update", "operation.remove", "attribute.reorder", "operation.reorder", "group.add", "group.update", "group.remove", "group.addMember", "group.removeMember"]; export type SymbolicOpKind = (typeof SYMBOLIC_OP_KINDS)[number]; /** * `AttributeType`(core) 런타임 목록 — JSON Schema enum과 resolver 값 검증이 공유한다. * SoT는 core의 union이고 아래 단언이 양방향 일치를 강제한다(`SYMBOLIC_OP_KINDS`와 같은 형태). * * ★목록이 있다고 patch로 자유 전환이 되는 건 아니다 — `attrType`은 동반 구조에서 파생되는 성격이라 * (RELATION_*=관계 소유, EMBED_*=`embedded`/카탈로그 type, DIVIDER=dbAttrs 0·type '') resolver가 * *전환*은 거부하고 라운드트립 에코만 통과시킨다. 이 목록의 쓸모는 **미지 값 판별**이다. */ export declare const ATTRIBUTE_TYPES: readonly ["NORMAL", "RELATION_OWN", "RELATION_REF", "EMBED_OWN", "EMBED_REF", "EMBED_PREDEF", "DIVIDER"];