@zoneflow/core 0.0.2 → 0.0.3
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 +133 -0
- package/dist/document.d.ts +22 -0
- package/dist/document.js +83 -0
- package/dist/ids.d.ts +2 -1
- package/dist/ids.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +3 -1
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# @zoneflow/core
|
|
2
|
+
|
|
3
|
+
`@zoneflow/core`는 Zoneflow의 도메인 레이어입니다.
|
|
4
|
+
|
|
5
|
+
이 패키지는 다음 역할을 담당합니다.
|
|
6
|
+
|
|
7
|
+
- `UniverseModel`, `UniverseLayoutModel` 타입
|
|
8
|
+
- zone / path 생성, 수정, 삭제 mutation
|
|
9
|
+
- lookup / validation 유틸
|
|
10
|
+
- layout 유틸
|
|
11
|
+
- zoneflow 문서 import / export
|
|
12
|
+
|
|
13
|
+
일반적으로 `@zoneflow/react`와 함께 사용합니다.
|
|
14
|
+
|
|
15
|
+
## 설치
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @zoneflow/core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 포함 기능
|
|
22
|
+
|
|
23
|
+
- 모델 타입
|
|
24
|
+
- `UniverseModel`
|
|
25
|
+
- `UniverseLayoutModel`
|
|
26
|
+
- `Zone`
|
|
27
|
+
- `Path`
|
|
28
|
+
- mutation
|
|
29
|
+
- `createZone`
|
|
30
|
+
- `updateZone`
|
|
31
|
+
- `removeZone`
|
|
32
|
+
- `addPath`
|
|
33
|
+
- `updatePath`
|
|
34
|
+
- `removePath`
|
|
35
|
+
- layout
|
|
36
|
+
- `createUniverseLayoutModel`
|
|
37
|
+
- `createZoneLayout`
|
|
38
|
+
- `getZoneLayout`
|
|
39
|
+
- `updateZoneLayout`
|
|
40
|
+
- 문서 포맷
|
|
41
|
+
- `createZoneflowDocument`
|
|
42
|
+
- `serializeZoneflowDocument`
|
|
43
|
+
- `parseZoneflowDocument`
|
|
44
|
+
|
|
45
|
+
## 최소 예제
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import {
|
|
49
|
+
createUniverseId,
|
|
50
|
+
createUniverseLayoutModel,
|
|
51
|
+
createZone,
|
|
52
|
+
createZoneLayout,
|
|
53
|
+
serializeZoneflowDocument,
|
|
54
|
+
} from "@zoneflow/core";
|
|
55
|
+
|
|
56
|
+
const universeId = createUniverseId();
|
|
57
|
+
|
|
58
|
+
let model = {
|
|
59
|
+
version: "1.0.0",
|
|
60
|
+
universeId,
|
|
61
|
+
rootZoneIds: [],
|
|
62
|
+
zonesById: {},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let layoutModel = createUniverseLayoutModel({
|
|
66
|
+
universeId,
|
|
67
|
+
version: "1.0.0",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
model = createZone(model, {
|
|
71
|
+
name: "Start",
|
|
72
|
+
zoneType: "action",
|
|
73
|
+
parentZoneId: null,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const rootZoneId = model.rootZoneIds[0];
|
|
77
|
+
|
|
78
|
+
layoutModel = {
|
|
79
|
+
...layoutModel,
|
|
80
|
+
zoneLayoutsById: {
|
|
81
|
+
...layoutModel.zoneLayoutsById,
|
|
82
|
+
[rootZoneId]: createZoneLayout({
|
|
83
|
+
x: 120,
|
|
84
|
+
y: 100,
|
|
85
|
+
width: 220,
|
|
86
|
+
height: 140,
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const json = serializeZoneflowDocument({
|
|
92
|
+
model,
|
|
93
|
+
layoutModel,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 문서 저장 / 불러오기
|
|
98
|
+
|
|
99
|
+
Zoneflow 문서 포맷은 다음 함수를 사용합니다.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import {
|
|
103
|
+
serializeZoneflowDocument,
|
|
104
|
+
parseZoneflowDocument,
|
|
105
|
+
} from "@zoneflow/core";
|
|
106
|
+
|
|
107
|
+
const json = serializeZoneflowDocument({
|
|
108
|
+
model,
|
|
109
|
+
layoutModel,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const documentBundle = parseZoneflowDocument(json);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
문서 형식:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"kind": "zoneflow/universe",
|
|
120
|
+
"formatVersion": 1,
|
|
121
|
+
"exportedAt": "2026-03-31T12:00:00.000Z",
|
|
122
|
+
"model": {},
|
|
123
|
+
"layoutModel": {}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
레거시 호환을 위해 raw `{ "model": ..., "layoutModel": ... }` 형태도 읽을 수 있습니다.
|
|
128
|
+
|
|
129
|
+
## 함께 쓰는 패키지
|
|
130
|
+
|
|
131
|
+
- 렌더링/에디팅 UI까지 필요하면 `@zoneflow/react`
|
|
132
|
+
|
|
133
|
+
레포지토리: [github.com/groobee/zoneflow](https://github.com/groobee/zoneflow)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { UniverseLayoutModel, UniverseModel } from "./types";
|
|
2
|
+
export declare const ZONEFLOW_DOCUMENT_KIND = "zoneflow/universe";
|
|
3
|
+
export declare const ZONEFLOW_DOCUMENT_VERSION = 1;
|
|
4
|
+
export type ZoneflowDocument = {
|
|
5
|
+
kind: typeof ZONEFLOW_DOCUMENT_KIND;
|
|
6
|
+
formatVersion: typeof ZONEFLOW_DOCUMENT_VERSION;
|
|
7
|
+
exportedAt: string;
|
|
8
|
+
model: UniverseModel;
|
|
9
|
+
layoutModel: UniverseLayoutModel;
|
|
10
|
+
};
|
|
11
|
+
export declare function createZoneflowDocument(params: {
|
|
12
|
+
model: UniverseModel;
|
|
13
|
+
layoutModel: UniverseLayoutModel;
|
|
14
|
+
exportedAt?: string;
|
|
15
|
+
}): ZoneflowDocument;
|
|
16
|
+
export declare function serializeZoneflowDocument(params: {
|
|
17
|
+
model: UniverseModel;
|
|
18
|
+
layoutModel: UniverseLayoutModel;
|
|
19
|
+
exportedAt?: string;
|
|
20
|
+
}, space?: number): string;
|
|
21
|
+
export declare function readZoneflowDocument(input: unknown): ZoneflowDocument;
|
|
22
|
+
export declare function parseZoneflowDocument(json: string): ZoneflowDocument;
|
package/dist/document.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { validateUniverseModel } from "./validation";
|
|
2
|
+
export const ZONEFLOW_DOCUMENT_KIND = "zoneflow/universe";
|
|
3
|
+
export const ZONEFLOW_DOCUMENT_VERSION = 1;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function assertUniverseShape(value, key) {
|
|
8
|
+
if (!isRecord(value)) {
|
|
9
|
+
throw new Error(`Invalid zoneflow document: "${key}" must be an object.`);
|
|
10
|
+
}
|
|
11
|
+
if (typeof value.universeId !== "string" || typeof value.version !== "string") {
|
|
12
|
+
throw new Error(`Invalid zoneflow document: "${key}" must include string "universeId" and "version".`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function validateLayoutModel(layoutModel) {
|
|
16
|
+
const errors = [];
|
|
17
|
+
if (!isRecord(layoutModel.zoneLayoutsById)) {
|
|
18
|
+
errors.push(`layoutModel.zoneLayoutsById must be an object`);
|
|
19
|
+
}
|
|
20
|
+
if (!isRecord(layoutModel.pathLayoutsById)) {
|
|
21
|
+
errors.push(`layoutModel.pathLayoutsById must be an object`);
|
|
22
|
+
}
|
|
23
|
+
return errors;
|
|
24
|
+
}
|
|
25
|
+
export function createZoneflowDocument(params) {
|
|
26
|
+
return {
|
|
27
|
+
kind: ZONEFLOW_DOCUMENT_KIND,
|
|
28
|
+
formatVersion: ZONEFLOW_DOCUMENT_VERSION,
|
|
29
|
+
exportedAt: params.exportedAt ?? new Date().toISOString(),
|
|
30
|
+
model: structuredClone(params.model),
|
|
31
|
+
layoutModel: structuredClone(params.layoutModel),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function serializeZoneflowDocument(params, space = 2) {
|
|
35
|
+
return JSON.stringify(createZoneflowDocument(params), null, space);
|
|
36
|
+
}
|
|
37
|
+
export function readZoneflowDocument(input) {
|
|
38
|
+
if (!isRecord(input)) {
|
|
39
|
+
throw new Error("Invalid zoneflow document: root must be an object.");
|
|
40
|
+
}
|
|
41
|
+
const isLegacyBundle = !("kind" in input) &&
|
|
42
|
+
"model" in input &&
|
|
43
|
+
"layoutModel" in input;
|
|
44
|
+
if (!isLegacyBundle) {
|
|
45
|
+
if (input.kind !== ZONEFLOW_DOCUMENT_KIND) {
|
|
46
|
+
throw new Error(`Invalid zoneflow document: "kind" must be "${ZONEFLOW_DOCUMENT_KIND}".`);
|
|
47
|
+
}
|
|
48
|
+
if (input.formatVersion !== ZONEFLOW_DOCUMENT_VERSION) {
|
|
49
|
+
throw new Error(`Unsupported zoneflow document version: "${String(input.formatVersion)}".`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
assertUniverseShape(input.model, "model");
|
|
53
|
+
assertUniverseShape(input.layoutModel, "layoutModel");
|
|
54
|
+
const model = structuredClone(input.model);
|
|
55
|
+
const layoutModel = structuredClone(input.layoutModel);
|
|
56
|
+
if (layoutModel.universeId !== model.universeId) {
|
|
57
|
+
throw new Error("Invalid zoneflow document: model.universeId and layoutModel.universeId must match.");
|
|
58
|
+
}
|
|
59
|
+
const modelErrors = validateUniverseModel(model);
|
|
60
|
+
const layoutErrors = validateLayoutModel(layoutModel);
|
|
61
|
+
const errors = [...modelErrors, ...layoutErrors];
|
|
62
|
+
if (errors.length > 0) {
|
|
63
|
+
throw new Error(`Invalid zoneflow document:\\n- ${errors.join("\\n- ")}`);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
kind: ZONEFLOW_DOCUMENT_KIND,
|
|
67
|
+
formatVersion: ZONEFLOW_DOCUMENT_VERSION,
|
|
68
|
+
exportedAt: typeof input.exportedAt === "string" ? input.exportedAt : new Date().toISOString(),
|
|
69
|
+
model,
|
|
70
|
+
layoutModel,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function parseZoneflowDocument(json) {
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(json);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : "Unknown JSON parse error";
|
|
80
|
+
throw new Error(`Failed to parse zoneflow document JSON: ${message}`);
|
|
81
|
+
}
|
|
82
|
+
return readZoneflowDocument(parsed);
|
|
83
|
+
}
|
package/dist/ids.d.ts
CHANGED
package/dist/ids.js
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoneflow/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Zoneflow core model, layout, mutation, validation, and document utilities.",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"homepage": "https://github.com/groobee/zoneflow",
|
|
7
9
|
"repository": {
|
|
8
10
|
"type": "git",
|
|
9
11
|
"url": "https://github.com/groobee/zoneflow.git",
|