@g1cloud/ui-modeler-next 5.0.0-alpha.1
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.
- package/README.md +140 -0
- package/dist/adapter/attrCodec.d.ts +12 -0
- package/dist/adapter/interpretFlat.d.ts +20 -0
- package/dist/adapter/legacyGridSort.d.ts +11 -0
- package/dist/adapter/legacyText.d.ts +31 -0
- package/dist/adapter/legacyValidator.d.ts +2 -0
- package/dist/adapter/migrate.d.ts +47 -0
- package/dist/adapter/persisted.d.ts +75 -0
- package/dist/adapter/uiModelAdapter.d.ts +80 -0
- package/dist/agent/buildAgentBatch.d.ts +22 -0
- package/dist/agent/describeScreen.d.ts +21 -0
- package/dist/agent/resolver.d.ts +72 -0
- package/dist/agent/schema.d.ts +15 -0
- package/dist/agent/strictSubset.d.ts +30 -0
- package/dist/agent/symbolicOp.d.ts +97 -0
- package/dist/catalog/builtin/basic.d.ts +3 -0
- package/dist/catalog/builtin/chart.d.ts +3 -0
- package/dist/catalog/builtin/composite.d.ts +3 -0
- package/dist/catalog/builtin/etc.d.ts +3 -0
- package/dist/catalog/builtin/filter.d.ts +3 -0
- package/dist/catalog/builtin/grid.d.ts +3 -0
- package/dist/catalog/builtin/index.d.ts +3 -0
- package/dist/catalog/builtin/input.d.ts +3 -0
- package/dist/catalog/builtin/layout.d.ts +3 -0
- package/dist/catalog/builtin/listField.d.ts +3 -0
- package/dist/catalog/builtin/multilang.d.ts +3 -0
- package/dist/catalog/builtin/shared.d.ts +69 -0
- package/dist/catalog/builtin/tree.d.ts +3 -0
- package/dist/catalog/catalog.d.ts +13 -0
- package/dist/catalog/containment.d.ts +26 -0
- package/dist/catalog/index.d.ts +6 -0
- package/dist/catalog/propertyEditor.d.ts +24 -0
- package/dist/catalog/styleTokens.d.ts +54 -0
- package/dist/catalog/toAgentCatalog.d.ts +57 -0
- package/dist/catalog/types.d.ts +117 -0
- package/dist/command/commands.d.ts +25 -0
- package/dist/command/op.d.ts +83 -0
- package/dist/command/opSync.d.ts +84 -0
- package/dist/command/stack.d.ts +47 -0
- package/dist/command/types.d.ts +23 -0
- package/dist/core/attrValue.d.ts +50 -0
- package/dist/core/caseMessage.d.ts +13 -0
- package/dist/core/gridDefaultSort.d.ts +15 -0
- package/dist/core/id.d.ts +2 -0
- package/dist/core/lang.d.ts +25 -0
- package/dist/core/partType.d.ts +30 -0
- package/dist/core/traverse.d.ts +24 -0
- package/dist/core/types.d.ts +40 -0
- package/dist/core/validation.d.ts +21 -0
- package/dist/core/validatorConfig.d.ts +48 -0
- package/dist/editor/controller.d.ts +125 -0
- package/dist/index.d.ts +42 -0
- package/dist/ui-modeler-next.css +1 -0
- package/dist/ui-modeler.js +3965 -0
- package/dist/ui-modeler.umd.cjs +1 -0
- package/dist/view/InlineIssueBadge.vue.d.ts +7 -0
- package/dist/view/MultiLangTextField.vue.d.ts +19 -0
- package/dist/view/PartCanvas.vue.d.ts +12 -0
- package/dist/view/PartPalette.vue.d.ts +2 -0
- package/dist/view/PartTree.vue.d.ts +7 -0
- package/dist/view/PropertyPanel.vue.d.ts +2 -0
- package/dist/view/RendererRegistry.d.ts +28 -0
- package/dist/view/SchematicPart.vue.d.ts +29 -0
- package/dist/view/dnd.d.ts +19 -0
- package/dist/view/preview/IframePreview.vue.d.ts +22 -0
- package/dist/view/preview/protocol.d.ts +40 -0
- package/dist/view/validationDisplay.d.ts +8 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @g1cloud/ui-modeler-next
|
|
2
|
+
|
|
3
|
+
> **비주얼 UI 모델러 / 화면 설계 에디터** Vue 3 라이브러리. 컴포넌트 기반 화면을 시각적으로 저작하고, **AI 에이전트로 화면 디자인을 생성**하는 것을 목표로 한다.
|
|
4
|
+
|
|
5
|
+
- **패키지**: `@g1cloud/ui-modeler-next`
|
|
6
|
+
- **스택**: TypeScript · Vue 3 · Vite(library mode) · Vitest
|
|
7
|
+
- **상태**: `0.1.0-alpha.0` (활발한 개발 중)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 개요
|
|
12
|
+
|
|
13
|
+
~85종의 UI 파트를 다루는 **디자인시스템 중립** 라이브러리. 렌더링·데이터·명령·동시성·에이전트 계층이 분리되어 있고, 뷰는 교체 가능한 `RendererRegistry`를 통해 컴포넌트 셋에 바인딩된다.
|
|
14
|
+
|
|
15
|
+
두 종류의 소비자를 지원한다.
|
|
16
|
+
|
|
17
|
+
- **에디터 통합자** — Vue 앱(호스트)에 비주얼 모델러를 임베드. `createUiEditorController` + `view/` 컴포넌트 + op 전송층.
|
|
18
|
+
- **에이전트/서버** — 사람 개입 없이 화면 구조를 읽고(`describeScreen`) 쓰는(`buildAgentBatch` → op 배치) 순수 함수 표면. 저장소 중립.
|
|
19
|
+
|
|
20
|
+
## 핵심 설계
|
|
21
|
+
|
|
22
|
+
- **2단계 컴파일러.** 라이브러리에 LLM SDK는 없다. 외부 LLM이 구조화된 symbolic op를 생성하고, 결정론적 `resolver`가 이를 모델로 하강(lowering)한다. resolve/assemble은 호스트에서 실행된다.
|
|
23
|
+
- **AI-native clean 모델.** 인메모리 모델은 타입드 prop·`MultiLangText`·구조값을 쓰는 깨끗한 모델이다. 문자열 인코딩이나 attr 태깅 같은 표현이 모델 어휘에 새지 않는다.
|
|
24
|
+
- **디자인시스템 중립.** 모델 어휘는 컴포넌트 셋에 독립. 디자인시스템 종속(스타일 클래스 문자열 등)은 `catalog/`에 *데이터*로만 존재하며, 컴포넌트 셋 교체 = `view/RendererRegistry` 대체 주입.
|
|
25
|
+
- **단일 CAS 축.** 동시성 단위는 파트(`casUnit:'part'`) 하나. 레이아웃이 트리에 내재하므로 `RevCache`는 `partId`로 키잉한다.
|
|
26
|
+
|
|
27
|
+
### 데이터 표현 3계층
|
|
28
|
+
|
|
29
|
+
| 계층 | 형태 | 용도 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| 인메모리 `EditorState` | 재귀 트리 `PartModel{children[]}` | command/resolver/view의 단일 소스 |
|
|
32
|
+
| 영속 (op-write) | 평면 인접리스트 `partList` | op-set·동시성·에이전트 + 유일 write 포맷 |
|
|
33
|
+
|
|
34
|
+
## 설치
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @g1cloud/ui-modeler-next
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
peer dependency:
|
|
41
|
+
|
|
42
|
+
```jsonc
|
|
43
|
+
{
|
|
44
|
+
"@g1cloud/open-bluesea-core": "1.0.0-alpha.9",
|
|
45
|
+
"vue": "^3.5.0"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
스타일시트가 필요하면 함께 임포트한다.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import '@g1cloud/ui-modeler-next/style.css'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 빠른 시작 — 에디터 임베드
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { provide } from 'vue'
|
|
59
|
+
import {
|
|
60
|
+
createUiEditorController,
|
|
61
|
+
EDITOR,
|
|
62
|
+
createCatalog,
|
|
63
|
+
fromPersistedV2,
|
|
64
|
+
} from '@g1cloud/ui-modeler-next'
|
|
65
|
+
|
|
66
|
+
// 호스트에서 v2 문서를 로드해 트리로 복원
|
|
67
|
+
const root = fromPersistedV2(persistedV2Doc)
|
|
68
|
+
|
|
69
|
+
const controller = createUiEditorController(root, {
|
|
70
|
+
catalog: createCatalog(),
|
|
71
|
+
modelId: docId,
|
|
72
|
+
// 트랙 B 마커 — 호스트가 주입. v2 + opWriteEnabled=true 여야 op-mode(편집) 활성.
|
|
73
|
+
meta: { schemaVersion: 2, opWriteEnabled: true },
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// 뷰 컴포넌트가 inject로 소비
|
|
77
|
+
provide(EDITOR, controller)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<template>
|
|
82
|
+
<PartPalette />
|
|
83
|
+
<PartCanvas />
|
|
84
|
+
<PartTree />
|
|
85
|
+
<PropertyPanel />
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<script setup lang="ts">
|
|
89
|
+
import { PartPalette, PartCanvas, PartTree, PropertyPanel } from '@g1cloud/ui-modeler-next'
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
op 동기화(낙관적 예측·CAS·hard-freeze)는 `OpSyncAdapter`(`command/opSync`)를 통해 호스트 전송층에 배선한다. 전송층은 op 배치를 호스트 엔드포인트로 POST하고 CAS 충돌만 `{ok:false}`로 반환하면 된다.
|
|
94
|
+
|
|
95
|
+
## 에이전트 / 서버 표면
|
|
96
|
+
|
|
97
|
+
사람 개입 없이 화면을 읽고 쓰는 순수 함수들. 저장소·프레임워크에 독립적이다.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import {
|
|
101
|
+
describeScreen, // PartModel → AgentView (id·path·caption·agent-facing attrs 트리)
|
|
102
|
+
resolveOps, // symbolicOp[] → ResolvedOp[] (핸들 해소: id → path → caption)
|
|
103
|
+
buildAgentBatch, // ResolvedOp[] → OpShape 배치 (CAS revs·origin)
|
|
104
|
+
applyOpsToFlat, // 평면 partList에 op 배치 적용 (호스트 mongo interpreter의 의미론 오라클)
|
|
105
|
+
toAgentCatalog, // 카탈로그의 에이전트-정제 뷰 (G2)
|
|
106
|
+
} from '@g1cloud/ui-modeler-next'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
에이전트 접근의 불변 투자는 호스트 HTTP API 3종(`describeScreen` read · op write · `toAgentCatalog`)이며, skill/MCP 등 전송층은 그 위의 얇은 계층이다.
|
|
110
|
+
|
|
111
|
+
## 실 미리보기 (전략 B)
|
|
112
|
+
|
|
113
|
+
캔버스는 경량 스키매틱 렌더러로, 실 컴포넌트 미리보기는 격리된 iframe에서 렌더한다(`IframePreview` + postMessage 브릿지). iframe 경계가 디자인시스템 CSS/inject 충돌을 분리한다.
|
|
114
|
+
|
|
115
|
+
## 공개 API 표면
|
|
116
|
+
|
|
117
|
+
| 영역 | 주요 export |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `core` | `PartModel`·`attrValue`·`MultiLangText`·`traverse`·`validatePartModel` |
|
|
120
|
+
| `catalog` | `createCatalog`·`toAgentCatalog`·`STYLE_TOKENS`·containment 규칙 |
|
|
121
|
+
| `adapter` | `fromPersistedV2`/`toPersistedV2`·`applyOpsToFlat` |
|
|
122
|
+
| `command` | `CommandStack`·트리 4연산·`OpShape`·`RevCache`·`OpSyncAdapter` |
|
|
123
|
+
| `agent` | `symbolicOp`·`resolver`·`schema`·`describeScreen`·`buildAgentBatch` |
|
|
124
|
+
| `editor` | `createUiEditorController`·`EDITOR` inject key |
|
|
125
|
+
| `view` | `RendererRegistry`·`PartCanvas`·`PartPalette`·`PropertyPanel`·`PartTree`·`IframePreview` |
|
|
126
|
+
|
|
127
|
+
## 개발
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pnpm dev # 데모 하네스 (src/dev)
|
|
131
|
+
pnpm test # Vitest
|
|
132
|
+
pnpm typecheck # vue-tsc --noEmit
|
|
133
|
+
pnpm build # 라이브러리 빌드 → dist/
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- Node `>= 24.10.0`
|
|
137
|
+
|
|
138
|
+
## 라이선스
|
|
139
|
+
|
|
140
|
+
`LicenseRef-LICENSE` — 저장소의 `LICENSE` 파일 참조.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AttrKind, AttrVal } from '../core/attrValue';
|
|
2
|
+
import { PAttrValue } from './persisted';
|
|
3
|
+
/**
|
|
4
|
+
* @param valueType catalog가 선언한 kind(authoritative). 미지정 시 v1 태그로 추론.
|
|
5
|
+
* @returns 표현 가능한 clean 값, 또는 표현 불가 시 `undefined`(관찰적 폐기 대상).
|
|
6
|
+
*/
|
|
7
|
+
export declare function attrValFrom(p: PAttrValue, valueType?: AttrKind): AttrVal | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* v1 attrMap → clean attrs. 표현 불가 항목은 결과에서 제외하고 `onDiscard`로 보고한다.
|
|
10
|
+
* @param valueTypeOf 키별 catalog valueType 조회(없으면 태그 추론).
|
|
11
|
+
*/
|
|
12
|
+
export declare function attrMapFrom(m: Record<string, PAttrValue>, valueTypeOf?: (key: string) => AttrKind | undefined, onDiscard?: (key: string, p: PAttrValue) => void): Record<string, AttrVal>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OpShape, OpCas, OpConflict, PartId } from '../command/op';
|
|
2
|
+
import { PFlatPart } from './persisted';
|
|
3
|
+
/** {@link applyOpsToFlat} 결과 — ok면 갱신 평면 리스트+touched 컨테이너 신 rev, 충돌이면 프리즈 트리거. */
|
|
4
|
+
export type FlatApplyResult = {
|
|
5
|
+
ok: true;
|
|
6
|
+
partList: PFlatPart[];
|
|
7
|
+
revs: Record<PartId, number>;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
conflict: OpConflict;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* op 배치를 평면 인접리스트에 적용한다(호스트 mongo interpreter의 의미론 오라클).
|
|
14
|
+
*
|
|
15
|
+
* @param partList pre-batch 평면 상태(입력은 변형하지 않음 — 클론에 적용).
|
|
16
|
+
* @param ops apply 순서의 op 어휘 배치.
|
|
17
|
+
* @param cas 컨테이너 base rev 단언(`revContainersOf` 파생, opSync가 합성).
|
|
18
|
+
* @returns ok+갱신 리스트/신 rev, 또는 conflict(rev 불일치 / 대상 소실). 충돌 시 부분 변형 없음.
|
|
19
|
+
*/
|
|
20
|
+
export declare function applyOpsToFlat(partList: PFlatPart[], ops: OpShape[], cas: OpCas): FlatApplyResult;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GridDefaultSort } from '../core/gridDefaultSort';
|
|
2
|
+
/**
|
|
3
|
+
* 레거시 그리드 정렬 문자열(`propId:ORDER:dataMapping:name`, 리스트는 세미콜론 join)의
|
|
4
|
+
* **v1-read 파서**. → {@link GridDefaultSort}(구조 객체). 역방향 직렬화(write-back)는 폐기(G6).
|
|
5
|
+
*
|
|
6
|
+
* 파생 원본: bluework-uimodeler-model/.../ui/GridDefaultSort.java (parse/toString)
|
|
7
|
+
*/
|
|
8
|
+
/** `propId:ORDER:dataMapping:name` 단일 항목 파싱. */
|
|
9
|
+
export declare function parseGridSort(str: string): GridDefaultSort;
|
|
10
|
+
/** 세미콜론 구분 리스트 파싱(빈 항목 무시). */
|
|
11
|
+
export declare function parseGridSortList(str: string | null | undefined): GridDefaultSort[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MultiLangText, MultiLangString } from '../core/lang';
|
|
2
|
+
/**
|
|
3
|
+
* 레거시 텍스트 형태({@code UiKeyText})의 **v1-read 디코더**. `#{key}` 인코딩·key/text/msgMap
|
|
4
|
+
* 형태는 legacy 저장 관심사이므로 여기(단일 v1-read 경계)에만 두고, clean 모델은 {@link MultiLangText}만
|
|
5
|
+
* 다룬다(G6). 역방향 인코딩(write-back)은 폐기.
|
|
6
|
+
*
|
|
7
|
+
* 파생 원본: bluework-uimodeler-model/.../ui/shared/UiKeyText.java(+ encodeKeyText/decodeKeyText)
|
|
8
|
+
*/
|
|
9
|
+
/** 레거시 v1 텍스트 형태. 저장 시 `key`가 canonical(UiKeyText.java 주석). */
|
|
10
|
+
export interface UiKeyText {
|
|
11
|
+
key?: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
msgMap?: MultiLangString;
|
|
14
|
+
children?: UiKeyText[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `#{key}` 인코딩 문자열을 {@link UiKeyText}로 디코딩. 레거시 {@code decodeKeyText(msg, null)}과 동치:
|
|
18
|
+
* - `#{key}` → { key } (resolver 없으므로 text 미설정)
|
|
19
|
+
* - 그 외 → { text }
|
|
20
|
+
*/
|
|
21
|
+
export declare function decodeKeyText(msg: string | null | undefined): UiKeyText | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* {@link UiKeyText}(v1) → clean {@link MultiLangText}로 브릿지.
|
|
24
|
+
* - `key` 보유 → 메시지 키 참조 `{ key }`(운영 caption의 사실상 전부 · 무손실).
|
|
25
|
+
* - `msgMap` 보유 → locale 맵.
|
|
26
|
+
* - `text`만 → 평문 string.
|
|
27
|
+
* - 비어 있으면 undefined.
|
|
28
|
+
*/
|
|
29
|
+
export declare function uiKeyTextToMultiLang(t: UiKeyText | undefined): MultiLangText | undefined;
|
|
30
|
+
/** v1 저장 문자열(`#{key}` 또는 평문) → clean {@link MultiLangText}. adapter 텍스트 디코드 진입점. */
|
|
31
|
+
export declare function decodeToMultiLang(msg: string | null | undefined): MultiLangText | undefined;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PartModel } from '../core/types';
|
|
2
|
+
import { PartCatalog } from '../catalog/types';
|
|
3
|
+
import { PPartModel, PUiModelV2 } from './persisted';
|
|
4
|
+
import { CoverageReport } from './uiModelAdapter';
|
|
5
|
+
/**
|
|
6
|
+
* v1→v2 마이그레이션 도구(G6 · 불변식 #5). 폐기된 무손실 왕복(write-back)을 대체하는
|
|
7
|
+
* **일방향 이관 파이프**: 레거시 v1 Jackson JSON({@link PPartModel})을 읽어 clean 트리로 디코드하고
|
|
8
|
+
* (레거시 적대성 흡수), 신규 편집기의 유일 write 포맷인 v2 평면 문서({@link PUiModelV2})로 재직렬화한다.
|
|
9
|
+
*
|
|
10
|
+
* 이 도구는 두 순수 함수의 얇은 조립일 뿐이다: `fromPersisted`(v1-read 디코더) → `toPersistedV2`(v2 write).
|
|
11
|
+
* 존재 이유는 **커버리지 리포트의 표면화**다 — v1 디코드가 조용히 버린 것(미지 partType·표현 불가 attr·
|
|
12
|
+
* 신모델 미표현 필드)을 {@link CoverageReport}로 모아 반환해, 운영자가 v2를 영속화하기 전에 폐기 내역을
|
|
13
|
+
* 감사·리뷰할 수 있게 한다(관찰적 폐기 원칙).
|
|
14
|
+
*
|
|
15
|
+
* 호스트 무의존 순수 함수다(파일 I/O·DB 없음). 실제 적재/기록은 호출부(마이그레이션 스크립트·호스트 route)가
|
|
16
|
+
* 맡는다. v1→clean의 구조 동치·`#{key}` 비누출·왕복 대칭은 `_migration-oracle.test.ts`가 실샘플로 실증한다.
|
|
17
|
+
*/
|
|
18
|
+
export interface MigrateV1ToV2Options {
|
|
19
|
+
/** attr 타이핑 authoritative 소스 + unknown partType/attr 판정용. 생략 시 태그 추론. */
|
|
20
|
+
catalog?: PartCatalog;
|
|
21
|
+
/** v2 문서 메타로 전달({@link toPersistedV2}). */
|
|
22
|
+
opWriteEnabled?: boolean;
|
|
23
|
+
/** v2 문서 메타로 전달({@link toPersistedV2}). */
|
|
24
|
+
version?: number;
|
|
25
|
+
/**
|
|
26
|
+
* 배치 마이그레이션 시 여러 문서의 폐기를 하나의 리포트에 누적하고 싶을 때 주입.
|
|
27
|
+
* 생략 시 문서마다 fresh 리포트를 생성한다({@link FromPersistedOptions.report}와 동일 규약).
|
|
28
|
+
*/
|
|
29
|
+
report?: CoverageReport;
|
|
30
|
+
}
|
|
31
|
+
export interface MigrateV1ToV2Result {
|
|
32
|
+
/** 신규 편집기 유일 write 포맷. 호스트가 그대로 영속화한다. */
|
|
33
|
+
v2: PUiModelV2;
|
|
34
|
+
/** 관찰적 폐기 내역. 영속화 전 감사·리뷰 대상(unknownPartTypes/discardedAttrs/droppedFields). */
|
|
35
|
+
report: CoverageReport;
|
|
36
|
+
/**
|
|
37
|
+
* v1-read 중간 산출물 clean 트리. `toPersistedV2`의 입력이자, 호출부가 영속화 전
|
|
38
|
+
* `validatePartModel`(M5)로 카탈로그 규칙을 재검증하고 싶을 때 재계산 없이 재사용하는 seam.
|
|
39
|
+
*/
|
|
40
|
+
model: PartModel;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* v1 문서 하나를 v2로 이관한다. 반환된 {@link MigrateV1ToV2Result.report}가 비어 있으면 무손실 흡수,
|
|
44
|
+
* 항목이 있으면 그 내역이 폐기 결정의 감사 근거다. 배치 이관은 호출부가 문서마다 호출하며,
|
|
45
|
+
* `opts.report`를 공유하면 폐기 내역이 한 리포트에 누적된다.
|
|
46
|
+
*/
|
|
47
|
+
export declare function migrateV1ToV2(doc: PPartModel, opts?: MigrateV1ToV2Options): MigrateV1ToV2Result;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PartInfo } from '../core/types';
|
|
2
|
+
import { AttrVal } from '../core/attrValue';
|
|
3
|
+
import { MultiLangText } from '../core/lang';
|
|
4
|
+
import { CaseMessage } from '../core/caseMessage';
|
|
5
|
+
/**
|
|
6
|
+
* 레거시 영속 형태(v1) — **읽기·마이그레이션 소스 전용**(G6, write-back 폐기).
|
|
7
|
+
* {@code UiModelerUtils.partModelToJson}(Jackson, Include.NON_NULL)의 JSON 구조를 그대로 표현한다.
|
|
8
|
+
* 필드명은 레거시 getter 기준: partId/partType/partInfo/attrMap/comment/caseMsgList/children/
|
|
9
|
+
* layoutAttrMap/extraAttrMap. AttrValue → {type,val}.
|
|
10
|
+
*
|
|
11
|
+
* 파생 원본: bluework-uimodeler-model/.../ui/shared/{PartModel,AttrValue,PartInfo,CaseMessage}.java
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 레거시 `AttrType` enum 이름(v1 저장 태그). clean 모델엔 존재하지 않고 v1-read 디코더에만 산다.
|
|
15
|
+
* `M_REF`/`DS_REF`/`DM_REF`(원문 보존용 왕복)·`ETC`(미지 태그)는 write-back 폐기로 clean kind로
|
|
16
|
+
* 매핑되지 않는다 → 관찰적 폐기(커버리지 리포트).
|
|
17
|
+
*/
|
|
18
|
+
export type AttrTypeTag = 'S' | 'I' | 'B' | 'M' | 'M_REF' | 'DS' | 'DS_REF' | 'DM' | 'DM_REF' | 'ETC';
|
|
19
|
+
export interface PAttrValue {
|
|
20
|
+
type: AttrTypeTag | string;
|
|
21
|
+
val: string;
|
|
22
|
+
}
|
|
23
|
+
export interface PPartInfo {
|
|
24
|
+
draggable: boolean;
|
|
25
|
+
droppable: boolean;
|
|
26
|
+
selectable: boolean;
|
|
27
|
+
deletable: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface PCaseMessage {
|
|
30
|
+
caseText?: Record<string, string>;
|
|
31
|
+
msgType?: string;
|
|
32
|
+
msgText?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface PPartModel {
|
|
35
|
+
partId: string;
|
|
36
|
+
partType: string;
|
|
37
|
+
partInfo?: PPartInfo;
|
|
38
|
+
attrMap?: Record<string, PAttrValue>;
|
|
39
|
+
comment?: Record<string, string>;
|
|
40
|
+
caseMsgList?: PCaseMessage[];
|
|
41
|
+
children?: PPartModel[];
|
|
42
|
+
layoutAttrMap?: Record<string, PAttrValue>;
|
|
43
|
+
extraAttrMap?: Record<string, string>;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/** 평면 인접리스트의 한 노드 — clean `PartModel`에서 children을 걷어내고 parentId/order/rev를 부여. */
|
|
47
|
+
export interface PFlatPart {
|
|
48
|
+
partId: string;
|
|
49
|
+
/** 부모 partId. root는 null. */
|
|
50
|
+
parentId: string | null;
|
|
51
|
+
/** 형제 내 정렬 키(0-base). 트리 children[] 순서를 평면에서 복원하는 근거. */
|
|
52
|
+
order: number;
|
|
53
|
+
/** CAS 축 rev(불변식 #2). 신규 write/서브트리 seed 시 0. */
|
|
54
|
+
rev: number;
|
|
55
|
+
partType: string;
|
|
56
|
+
info?: PartInfo;
|
|
57
|
+
/** 컴포넌트 속성(타입드 clean 값). */
|
|
58
|
+
attrs?: Record<string, AttrVal>;
|
|
59
|
+
/** 부모 레이아웃 부과 속성(정렬/expandRatio 등). */
|
|
60
|
+
layoutAttrs?: Record<string, AttrVal>;
|
|
61
|
+
extraAttrs?: Record<string, string>;
|
|
62
|
+
comment?: MultiLangText;
|
|
63
|
+
caseMessages?: CaseMessage[];
|
|
64
|
+
}
|
|
65
|
+
/** v2 문서 루트 — 평면 partList + 메타. */
|
|
66
|
+
export interface PUiModelV2 {
|
|
67
|
+
schemaVersion: 2;
|
|
68
|
+
/** op-write 활성 플래그(호스트가 낙관 write 경로 게이팅). */
|
|
69
|
+
opWriteEnabled?: boolean;
|
|
70
|
+
/** 문서 리비전(전체 로드 낙관 락 보조 — part rev와 독립). */
|
|
71
|
+
version?: number;
|
|
72
|
+
/** 트리 재구성 진입점(parentId=null 노드와 일치). */
|
|
73
|
+
rootId: string;
|
|
74
|
+
partList: PFlatPart[];
|
|
75
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PartModel } from '../core/types';
|
|
2
|
+
import { PartCatalog } from '../catalog/types';
|
|
3
|
+
import { PPartModel, PFlatPart, PUiModelV2 } from './persisted';
|
|
4
|
+
/**
|
|
5
|
+
* 레거시 v1 Jackson JSON({@link PPartModel}) → 인메모리 clean 트리({@link PartModel})의
|
|
6
|
+
* **단방향 lossy-observable 디코더**(G6 — write-back·무손실 왕복 폐기).
|
|
7
|
+
*
|
|
8
|
+
* "손실"은 조용히 버리지 않고 {@link CoverageReport}로 표면화한다(관찰적 폐기):
|
|
9
|
+
* - `unknownPartTypes`: catalog에 없는 partType.
|
|
10
|
+
* - `discardedAttrs`: clean 모델이 표현할 수 없는 attr(`M_REF`/`ETC`/미지 태그 등).
|
|
11
|
+
* - `droppedFields`: 신모델이 표현하지 않는 최상위 필드(구 `legacyRaw`).
|
|
12
|
+
*
|
|
13
|
+
* catalog를 넘기면 attr 타이핑이 catalog {@code valueType}로 authoritative해지고(`validate`→구조값 등),
|
|
14
|
+
* unknown partType/attr이 리포트에 잡힌다. catalog 없이도 태그 추론으로 동작한다.
|
|
15
|
+
*/
|
|
16
|
+
export interface DiscardedAttr {
|
|
17
|
+
partId: string;
|
|
18
|
+
partType: string;
|
|
19
|
+
key: string;
|
|
20
|
+
tag: string;
|
|
21
|
+
layout: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface CoverageReport {
|
|
24
|
+
unknownPartTypes: {
|
|
25
|
+
partId: string;
|
|
26
|
+
partType: string;
|
|
27
|
+
}[];
|
|
28
|
+
discardedAttrs: DiscardedAttr[];
|
|
29
|
+
droppedFields: {
|
|
30
|
+
partId: string;
|
|
31
|
+
partType: string;
|
|
32
|
+
field: string;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
export declare function emptyCoverageReport(): CoverageReport;
|
|
36
|
+
export interface FromPersistedOptions {
|
|
37
|
+
/** attr 타이핑 authoritative 소스 + unknown partType/attr 판정용. */
|
|
38
|
+
catalog?: PartCatalog;
|
|
39
|
+
/** 관찰적 폐기 집계 리포트(호출부가 넘겨 누적). */
|
|
40
|
+
report?: CoverageReport;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* v1 문서를 clean 트리로 읽어 들인다. `opts.report`를 넘기면 관찰적 폐기가 집계된다.
|
|
44
|
+
* write-back(toPersisted)은 존재하지 않는다 — v1→v2 마이그레이션은 별도 도구(v2 write 경로).
|
|
45
|
+
*/
|
|
46
|
+
export declare function fromPersisted(doc: PPartModel, opts?: FromPersistedOptions): PartModel;
|
|
47
|
+
/**
|
|
48
|
+
* 서브트리를 평면 레코드 배열로 펼친다(pre-order). 전 노드에 **동일 `rev`를 stamp**한다(기본 0) —
|
|
49
|
+
* part.add inline-fold 서브트리 seed(불변식 #3)와 초기 v2 write의 rev 재시드에 공용으로 쓰인다.
|
|
50
|
+
* `order`는 형제 내 인덱스로, root(또는 서브트리 최상위)는 `startOrder`.
|
|
51
|
+
* opSync(낙관 캐시 seed)와 호스트 interpreter가 같은 평탄화 규칙을 공유하도록 여기 한 곳에 둔다.
|
|
52
|
+
*/
|
|
53
|
+
export declare function flattenSubtree(part: PartModel, parentId?: string | null, startOrder?: number, rev?: number): PFlatPart[];
|
|
54
|
+
/**
|
|
55
|
+
* 인메모리 clean 트리 → v2 평면 문서. **초기 write/마이그레이션 시 전 노드 rev를 0으로 재시드**한다
|
|
56
|
+
* (이후 rev는 op-write CAS가 파트별로 $inc). 이 문서를 다시 트리로 읽는 v2-read(flat→tree)는
|
|
57
|
+
* 호스트가 실제 영속 문서를 적재하는 M10에서 대칭 구현한다(§G6). opSync는 write + RevCache seed만 소비.
|
|
58
|
+
*/
|
|
59
|
+
export declare function toPersistedV2(root: PartModel, opts?: {
|
|
60
|
+
opWriteEnabled?: boolean;
|
|
61
|
+
version?: number;
|
|
62
|
+
}): PUiModelV2;
|
|
63
|
+
/** {@link fromPersistedV2} 결과 — clean 트리 + RevCache seed(controller `UiModelMeta.revSeed`로 주입). */
|
|
64
|
+
export interface FromPersistedV2Result {
|
|
65
|
+
root: PartModel;
|
|
66
|
+
/** partId→rev. 호스트가 op-mode 진입 시 opSync `seed`로 주입(불변식 #2 단일 'part' 축). */
|
|
67
|
+
revSeed: {
|
|
68
|
+
revs: Record<string, number>;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* v2 평면 문서를 clean 트리로 재구성한다({@link toPersistedV2}의 역, round-trip deep-equal).
|
|
73
|
+
* `parentId`/`order`로 부모-자식·형제 순서를 복원하고 각 파트 `rev`를 `revSeed`로 산출한다.
|
|
74
|
+
*
|
|
75
|
+
* v2는 v1과 달리 우리 자신의 clean write 포맷이므로 **lossy-observable이 아니라 구조적 역함수**다 —
|
|
76
|
+
* 손상(중복 partId·부모 부재·root 부재/비-null·순환·도달 불가 파트)은 관찰적 폐기가 아니라 데이터 무결성
|
|
77
|
+
* 위반이므로 조용히 넘기지 않고 즉시 throw한다(호스트 저장/interpreter 버그를 조기 노출; M5 검증은 정상
|
|
78
|
+
* 트리의 카탈로그 규칙 위반을 다루는 별개 계층).
|
|
79
|
+
*/
|
|
80
|
+
export declare function fromPersistedV2(doc: PUiModelV2): FromPersistedV2Result;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { OpOrigin, OpShape, PartId, PersistedOpBatch } from '../command/op';
|
|
2
|
+
import { ResolvedOp } from './resolver';
|
|
3
|
+
/**
|
|
4
|
+
* resolver `ResolvedOp` → M6 wire `OpShape`. 필드명 브릿지 + part.update 두 축→patch 합성.
|
|
5
|
+
* part.update patch는 M6 `updatePart` 관례를 미러 — **비어있지 않은 축만** 키로 포함(빈 patch 허용).
|
|
6
|
+
*/
|
|
7
|
+
export declare function toOpShape(op: ResolvedOp): OpShape;
|
|
8
|
+
export interface BuildAgentBatchOptions {
|
|
9
|
+
/** clientOpId(멱등 키) 발급기 — 주입 시 테스트 결정성. 기본 newPartId(crypto.randomUUID). */
|
|
10
|
+
mkClientOpId?: () => string;
|
|
11
|
+
/** op 출처 — 기본 'agent'(감사·aggregation 트리거 분기). REST 브리지면 'rest'. */
|
|
12
|
+
origin?: OpOrigin;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 에이전트/REST 경로의 stateless 배치 조립.
|
|
16
|
+
*
|
|
17
|
+
* @param modelId 대상 모델 doc id(호스트 라우트가 bizModule→DBRef로 해소).
|
|
18
|
+
* @param revs base rev 스냅샷(컨테이너 partId→rev). 호스트가 M10에서 v2 문서→revSeed로 공급,
|
|
19
|
+
* 테스트는 손수 주입. touched 컨테이너에 대해서만 `cas.revs`에 반영.
|
|
20
|
+
* @param ops resolver 산출 `ResolvedOp` 배치(한 원자 단위). 내부에서 `toOpShape`로 브릿지.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildAgentBatch(modelId: string, revs: Record<PartId, number>, ops: ResolvedOp[], options?: BuildAgentBatchOptions): PersistedOpBatch;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PartType } from '../core/partType';
|
|
2
|
+
import { PartModel } from '../core/types';
|
|
3
|
+
import { PartCatalog } from '../catalog/types';
|
|
4
|
+
/**
|
|
5
|
+
* 화면 트리의 agent-facing 노드. write `PartSpec`의 read 대칭.
|
|
6
|
+
* - id: 1차 핸들(항상 포함 — "id 선호" 강제).
|
|
7
|
+
* - path: 2차 핸들(`FormLayout[0]/TextField[2]`). resolver `pathOf`·`byPath`와 동일 문법.
|
|
8
|
+
* - caption: 3차 핸들 — captionProp가 가리키는 attr 값(string/text 리터럴만).
|
|
9
|
+
* - attrs: agent 노출 subset. 노출 prop 값 + G1 css→token 역매핑한 스타일 토큰(dim→token)을 평면 병합.
|
|
10
|
+
*/
|
|
11
|
+
export interface AgentNode {
|
|
12
|
+
id: string;
|
|
13
|
+
partType: PartType;
|
|
14
|
+
path: string;
|
|
15
|
+
caption?: string;
|
|
16
|
+
attrs: Record<string, unknown>;
|
|
17
|
+
children: AgentNode[];
|
|
18
|
+
}
|
|
19
|
+
export type AgentView = AgentNode;
|
|
20
|
+
/** PartModel 트리 → AgentView(정제 read 뷰). 호스트 무관 — 순수 함수. */
|
|
21
|
+
export declare function describeScreen(root: PartModel, catalog: PartCatalog): AgentView;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { AttrVal } from '../core/attrValue';
|
|
2
|
+
import { PartModel } from '../core/types';
|
|
3
|
+
import { PartCatalog } from '../catalog/types';
|
|
4
|
+
import { Handle, SymbolicOp } from './symbolicOp';
|
|
5
|
+
/** 핸들 후보(모호성 escalation·describeScreen seed). */
|
|
6
|
+
export interface HandleCandidate {
|
|
7
|
+
partId: string;
|
|
8
|
+
path: string;
|
|
9
|
+
caption?: string;
|
|
10
|
+
}
|
|
11
|
+
export type ResolveIssue = {
|
|
12
|
+
kind: 'handle-not-found';
|
|
13
|
+
ref: string;
|
|
14
|
+
by?: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'handle-ambiguous';
|
|
17
|
+
ref: string;
|
|
18
|
+
candidates: HandleCandidate[];
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'unknown-style-dim';
|
|
21
|
+
dim: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'unknown-style-token';
|
|
24
|
+
dim: string;
|
|
25
|
+
token: string;
|
|
26
|
+
};
|
|
27
|
+
/** 하강된 op(M6 OpShape seed). */
|
|
28
|
+
export type ResolvedOp = {
|
|
29
|
+
kind: 'part.add';
|
|
30
|
+
parentId: string | null;
|
|
31
|
+
index?: number;
|
|
32
|
+
part: PartModel;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'part.update';
|
|
35
|
+
targetId: string;
|
|
36
|
+
attrs: Record<string, AttrVal>;
|
|
37
|
+
layoutAttrs: Record<string, AttrVal>;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'part.remove';
|
|
40
|
+
targetId: string;
|
|
41
|
+
};
|
|
42
|
+
export interface ResolveResult {
|
|
43
|
+
ops: ResolvedOp[];
|
|
44
|
+
issues: ResolveIssue[];
|
|
45
|
+
}
|
|
46
|
+
export interface ResolveContext {
|
|
47
|
+
/** 핸들 해석 대상 기존 트리(루트). part.add parent=null이면 무관. */
|
|
48
|
+
root?: PartModel;
|
|
49
|
+
/** captionProp 조회(caption 핸들·검증). 미지정 시 caption 축 비활성. */
|
|
50
|
+
catalog?: PartCatalog;
|
|
51
|
+
/** partId 생성기(테스트 결정성 주입용). 기본 crypto.randomUUID. */
|
|
52
|
+
newId?: () => string;
|
|
53
|
+
}
|
|
54
|
+
/** root→partId 경로를 `Type[i]/Type[j]` 문자열로. i=형제 중 동일 partType 순번(0-base). */
|
|
55
|
+
export declare function pathOf(root: PartModel, partId: string): string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* captionProp가 가리키는 attr의 비교용 문자열(string/text 리터럴만; 맵/키형은 비교 대상 밖).
|
|
58
|
+
* describeScreen(G3)이 caption 핸들 산출에 **동일 규칙 재사용**(read↔write 대칭) — export.
|
|
59
|
+
*/
|
|
60
|
+
export declare function captionText(part: PartModel, catalog: PartCatalog | undefined): string | undefined;
|
|
61
|
+
type HandleResolution = {
|
|
62
|
+
ok: true;
|
|
63
|
+
partId: string;
|
|
64
|
+
} | {
|
|
65
|
+
ok: false;
|
|
66
|
+
issue: ResolveIssue;
|
|
67
|
+
};
|
|
68
|
+
/** 핸들 하나를 partId로. by 미지정 시 id → path → caption 순. 모호하면 후보 반환. */
|
|
69
|
+
export declare function resolveHandle(h: Handle, ctx: ResolveContext): HandleResolution;
|
|
70
|
+
/** SymbolicOp 배치를 ResolvedOp[]로 하강. 실패는 issues로 수집(해당 op 생략). */
|
|
71
|
+
export declare function resolveOps(ops: SymbolicOp[], ctx?: ResolveContext): ResolveResult;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type JsonSchema = Record<string, unknown>;
|
|
2
|
+
/**
|
|
3
|
+
* inline-fold unroll 최대 깊이. self-`$ref` 재귀를 대체하는 고정 깊이(§R-schema 판정).
|
|
4
|
+
* L0(루트 spec)부터 L{MAX}(children 없는 leaf)까지 (MAX+1)개 레벨. 이보다 깊은 중첩은 별도 배치.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MAX_INLINE_DEPTH = 4;
|
|
7
|
+
/** 단일 SymbolicOp의 JSON Schema(kind로 판별되는 anyOf). $defs는 최상위에서 정의. */
|
|
8
|
+
export declare const SYMBOLIC_OP_SCHEMA: JsonSchema;
|
|
9
|
+
/**
|
|
10
|
+
* 호스트 `applyOps` 도구의 input_schema. 한 배치 = 한 원자 단위(resolver가 전부 해소 or 전부 실패).
|
|
11
|
+
* description은 "언제 호출하는지"를 명시 — 최신 모델은 도구를 보수적으로 집으므로 트리거 조건이 호출률을 좌우.
|
|
12
|
+
* 모든 $defs는 여기 최상위에 모은다(엄격 서브셋: nested $defs 금지).
|
|
13
|
+
*/
|
|
14
|
+
export declare const APPLY_UI_OPS_INPUT_SCHEMA: JsonSchema;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ── Anthropic structured-outputs 엄격 서브셋 적합성 검증기 ──
|
|
3
|
+
*
|
|
4
|
+
* `agent/schema.ts`가 방출하는 JSON Schema가 Anthropic structured-outputs / strict tool use의
|
|
5
|
+
* **엄격 서브셋**을 지키는지 기계적으로 검사한다. 이 검증기는 두 목적을 겸한다:
|
|
6
|
+
* 1. 스키마 drift 게이트 — 미지원 키워드·additionalProperties 누락을 CI에서 잡는다.
|
|
7
|
+
* 2. **R-schema 실증(exit #3)** — inline-fold `partSpec.children`의 self-`$ref`(재귀)가 이 서브셋에서
|
|
8
|
+
* 거부됨을 코드로 증명한다(재귀 탐지가 self-loop/사이클을 위반으로 보고). 라이브러리엔 LLM이
|
|
9
|
+
* 없으므로(불변식) 라이브 API 프로브 대신 문서-권위 제약을 검증기로 코드화한다.
|
|
10
|
+
*
|
|
11
|
+
* 엄격 서브셋 규칙(claude-api: Structured Outputs → JSON Schema Limitations):
|
|
12
|
+
* - 지원: object/array/string/integer/number/boolean/null, enum, const, anyOf, allOf, $ref/$defs,
|
|
13
|
+
* string formats, 모든 object에 `additionalProperties: false`.
|
|
14
|
+
* - 미지원: **재귀 스키마**, 수치 제약(minimum/maximum/multipleOf), 문자열 제약(minLength/maxLength/pattern),
|
|
15
|
+
* 복합 배열 제약(minItems/maxItems/uniqueItems), `additionalProperties`가 false 아닌 값.
|
|
16
|
+
*/
|
|
17
|
+
export interface StrictSubsetViolation {
|
|
18
|
+
/** 위반 규칙 슬러그. */
|
|
19
|
+
rule: 'missing-additional-properties-false' | 'unsupported-keyword' | 'nested-$defs' | 'recursion' | 'dangling-$ref';
|
|
20
|
+
/** 스키마 내 위치(디버그용 경로). */
|
|
21
|
+
path: string;
|
|
22
|
+
detail: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 스키마가 Anthropic 엄격 서브셋을 지키는지 검사. 위반 배열 반환(빈 배열 = 적합).
|
|
26
|
+
* @param root 루트 스키마(도구 input_schema — `$defs`는 여기 최상위).
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkStrictSubset(root: unknown): StrictSubsetViolation[];
|
|
29
|
+
/** 적합 여부 단축 판정. */
|
|
30
|
+
export declare function isStrictSubset(root: unknown): boolean;
|