eventmodeler 0.4.7 → 0.6.0
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/dist/api/client-config.js +10 -0
- package/dist/api/generated/client/client.gen.js +235 -0
- package/dist/api/generated/client/index.js +6 -0
- package/dist/api/generated/client/types.gen.js +2 -0
- package/dist/api/generated/client/utils.gen.js +228 -0
- package/dist/api/generated/client.gen.js +4 -0
- package/dist/api/generated/core/auth.gen.js +14 -0
- package/dist/api/generated/core/bodySerializer.gen.js +57 -0
- package/dist/api/generated/core/params.gen.js +100 -0
- package/dist/api/generated/core/pathSerializer.gen.js +106 -0
- package/dist/api/generated/core/queryKeySerializer.gen.js +92 -0
- package/dist/api/generated/core/serverSentEvents.gen.js +133 -0
- package/dist/api/generated/core/types.gen.js +2 -0
- package/dist/api/generated/core/utils.gen.js +87 -0
- package/dist/api/generated/index.js +2 -0
- package/dist/api/generated/sdk.gen.js +4222 -0
- package/dist/api/generated/types.gen.js +2 -0
- package/dist/api/generated/zod.gen.js +7217 -0
- package/dist/commands/add.js +315 -0
- package/dist/commands/auth.js +14 -0
- package/dist/commands/create.js +192 -0
- package/dist/commands/design.js +108 -0
- package/dist/commands/guide.js +15 -0
- package/dist/commands/init.js +21 -0
- package/dist/commands/list-schemas.js +177 -0
- package/dist/commands/list.js +39 -0
- package/dist/commands/loop.js +101 -0
- package/dist/commands/map.js +40 -0
- package/dist/commands/mark.js +27 -0
- package/dist/commands/move.js +35 -0
- package/dist/commands/remove.js +170 -0
- package/dist/commands/rename.js +53 -0
- package/dist/commands/resize.js +30 -0
- package/dist/commands/search.js +14 -0
- package/dist/commands/set.js +199 -0
- package/dist/commands/show-schemas.js +259 -0
- package/dist/commands/show.js +56 -0
- package/dist/commands/summary.js +13 -0
- package/dist/commands/update.js +240 -0
- package/dist/index.js +46 -2379
- package/dist/lib/auth.js +1 -1
- package/dist/lib/config.js +0 -15
- package/dist/lib/excalidraw-schema.js +66 -0
- package/dist/lib/globals.js +8 -0
- package/dist/lib/model.js +11 -0
- package/dist/lib/project-config.js +20 -0
- package/dist/lib/resolve.js +59 -0
- package/dist/lib/scenario.js +15 -0
- package/dist/slices/add-scenario/index.js +2 -206
- package/dist/slices/guide/guides/codegen.js +1 -1
- package/dist/slices/guide/guides/connect-slices.js +12 -37
- package/dist/slices/guide/guides/create-slices.js +110 -140
- package/dist/slices/guide/guides/explore.js +37 -26
- package/dist/slices/guide/guides/information-flow.js +70 -82
- package/dist/slices/guide/guides/scenarios.js +82 -137
- package/dist/slices/guide/index.js +6 -6
- package/dist/slices/help/index.js +96 -0
- package/dist/slices/help/topics/build-codegen.js +109 -0
- package/dist/slices/help/topics/build-slice.js +147 -0
- package/dist/slices/help/topics/check-completeness.js +57 -0
- package/dist/slices/help/topics/connect-slices.js +99 -0
- package/dist/slices/help/topics/explore-model.js +112 -0
- package/dist/slices/help/topics/json-reference.js +188 -0
- package/dist/slices/help/topics/linked-copies.js +89 -0
- package/dist/slices/help/topics/manipulate-canvas.js +150 -0
- package/dist/slices/help/topics/write-scenarios.js +162 -0
- package/dist/slices/init/index.js +10 -4
- package/dist/slices/init/loop.js +60 -0
- package/dist/slices/login/index.js +2 -2
- package/dist/slices/logout/index.js +2 -2
- package/dist/slices/whoami/index.js +11 -36
- package/package.json +8 -3
- package/dist/api/index.d.ts +0 -285
- package/dist/api/index.js +0 -323
- package/dist/cloud/slices/index.d.ts +0 -276
- package/dist/cloud/slices/index.js +0 -406
- package/dist/eventmodeler.js +0 -5646
- package/dist/formatters.d.ts +0 -17
- package/dist/formatters.js +0 -482
- package/dist/index.d.ts +0 -2
- package/dist/lib/auth.d.ts +0 -24
- package/dist/lib/backend.d.ts +0 -43
- package/dist/lib/backend.js +0 -73
- package/dist/lib/chapter-utils.d.ts +0 -13
- package/dist/lib/chapter-utils.js +0 -71
- package/dist/lib/cloud-client.d.ts +0 -69
- package/dist/lib/cloud-client.js +0 -364
- package/dist/lib/config.d.ts +0 -30
- package/dist/lib/diff/merge-rules.d.ts +0 -45
- package/dist/lib/diff/merge-rules.js +0 -210
- package/dist/lib/diff/model-differ.d.ts +0 -8
- package/dist/lib/diff/model-differ.js +0 -568
- package/dist/lib/diff/three-way-merge.d.ts +0 -7
- package/dist/lib/diff/three-way-merge.js +0 -390
- package/dist/lib/diff/types.d.ts +0 -75
- package/dist/lib/diff/types.js +0 -1
- package/dist/lib/element-lookup.d.ts +0 -58
- package/dist/lib/element-lookup.js +0 -126
- package/dist/lib/file-loader.d.ts +0 -8
- package/dist/lib/file-loader.js +0 -108
- package/dist/lib/flow-utils.d.ts +0 -53
- package/dist/lib/flow-utils.js +0 -348
- package/dist/lib/format.d.ts +0 -10
- package/dist/lib/format.js +0 -23
- package/dist/lib/project-config.d.ts +0 -27
- package/dist/lib/slice-utils.d.ts +0 -59
- package/dist/lib/slice-utils.js +0 -140
- package/dist/local/slices/index.d.ts +0 -11
- package/dist/local/slices/index.js +0 -13
- package/dist/projection.d.ts +0 -3
- package/dist/projection.js +0 -828
- package/dist/slices/add-field/index.d.ts +0 -8
- package/dist/slices/add-field/index.js +0 -211
- package/dist/slices/add-scenario/index.d.ts +0 -27
- package/dist/slices/codegen-chapter-events/index.d.ts +0 -2
- package/dist/slices/codegen-chapter-events/index.js +0 -145
- package/dist/slices/codegen-slice/index.d.ts +0 -2
- package/dist/slices/codegen-slice/index.js +0 -448
- package/dist/slices/create-automation-slice/index.d.ts +0 -2
- package/dist/slices/create-automation-slice/index.js +0 -304
- package/dist/slices/create-flow/index.d.ts +0 -2
- package/dist/slices/create-flow/index.js +0 -183
- package/dist/slices/create-state-change-slice/index.d.ts +0 -2
- package/dist/slices/create-state-change-slice/index.js +0 -263
- package/dist/slices/create-state-view-slice/index.d.ts +0 -2
- package/dist/slices/create-state-view-slice/index.js +0 -128
- package/dist/slices/diff/index.d.ts +0 -11
- package/dist/slices/diff/index.js +0 -293
- package/dist/slices/export-eventmodel-to-json/index.d.ts +0 -2
- package/dist/slices/export-eventmodel-to-json/index.js +0 -355
- package/dist/slices/git/index.d.ts +0 -2
- package/dist/slices/git/index.js +0 -125
- package/dist/slices/guide/guides/codegen.d.ts +0 -5
- package/dist/slices/guide/guides/connect-slices.d.ts +0 -5
- package/dist/slices/guide/guides/create-slices.d.ts +0 -5
- package/dist/slices/guide/guides/explore.d.ts +0 -5
- package/dist/slices/guide/guides/information-flow.d.ts +0 -5
- package/dist/slices/guide/guides/scenarios.d.ts +0 -5
- package/dist/slices/guide/index.d.ts +0 -1
- package/dist/slices/import/index.d.ts +0 -8
- package/dist/slices/import/index.js +0 -63
- package/dist/slices/init/index.d.ts +0 -5
- package/dist/slices/list-chapters/index.d.ts +0 -3
- package/dist/slices/list-chapters/index.js +0 -21
- package/dist/slices/list-commands/index.d.ts +0 -3
- package/dist/slices/list-commands/index.js +0 -20
- package/dist/slices/list-events/index.d.ts +0 -3
- package/dist/slices/list-events/index.js +0 -98
- package/dist/slices/list-processors/index.d.ts +0 -3
- package/dist/slices/list-processors/index.js +0 -20
- package/dist/slices/list-readmodels/index.d.ts +0 -3
- package/dist/slices/list-readmodels/index.js +0 -21
- package/dist/slices/list-scenarios/index.d.ts +0 -3
- package/dist/slices/list-scenarios/index.js +0 -35
- package/dist/slices/list-screens/index.d.ts +0 -3
- package/dist/slices/list-screens/index.js +0 -47
- package/dist/slices/list-slices/index.d.ts +0 -3
- package/dist/slices/list-slices/index.js +0 -35
- package/dist/slices/login/index.d.ts +0 -1
- package/dist/slices/logout/index.d.ts +0 -1
- package/dist/slices/map-fields/index.d.ts +0 -2
- package/dist/slices/map-fields/index.js +0 -269
- package/dist/slices/mark-slice-status/index.d.ts +0 -2
- package/dist/slices/mark-slice-status/index.js +0 -31
- package/dist/slices/merge/index.d.ts +0 -19
- package/dist/slices/merge/index.js +0 -147
- package/dist/slices/open-app/index.d.ts +0 -1
- package/dist/slices/remove-field/index.d.ts +0 -8
- package/dist/slices/remove-field/index.js +0 -167
- package/dist/slices/remove-scenario/index.d.ts +0 -2
- package/dist/slices/remove-scenario/index.js +0 -77
- package/dist/slices/search/index.d.ts +0 -3
- package/dist/slices/search/index.js +0 -302
- package/dist/slices/show-actor/index.d.ts +0 -4
- package/dist/slices/show-actor/index.js +0 -115
- package/dist/slices/show-aggregate/index.d.ts +0 -3
- package/dist/slices/show-aggregate/index.js +0 -108
- package/dist/slices/show-aggregate-completeness/index.d.ts +0 -4
- package/dist/slices/show-aggregate-completeness/index.js +0 -181
- package/dist/slices/show-chapter/index.d.ts +0 -3
- package/dist/slices/show-chapter/index.js +0 -195
- package/dist/slices/show-command/index.d.ts +0 -3
- package/dist/slices/show-command/index.js +0 -133
- package/dist/slices/show-completeness/index.d.ts +0 -4
- package/dist/slices/show-completeness/index.js +0 -731
- package/dist/slices/show-event/index.d.ts +0 -3
- package/dist/slices/show-event/index.js +0 -118
- package/dist/slices/show-model-summary/index.d.ts +0 -3
- package/dist/slices/show-model-summary/index.js +0 -31
- package/dist/slices/show-processor/index.d.ts +0 -3
- package/dist/slices/show-processor/index.js +0 -111
- package/dist/slices/show-readmodel/index.d.ts +0 -3
- package/dist/slices/show-readmodel/index.js +0 -158
- package/dist/slices/show-scenario/index.d.ts +0 -3
- package/dist/slices/show-scenario/index.js +0 -196
- package/dist/slices/show-screen/index.d.ts +0 -3
- package/dist/slices/show-screen/index.js +0 -139
- package/dist/slices/show-slice/index.d.ts +0 -3
- package/dist/slices/show-slice/index.js +0 -696
- package/dist/slices/update-field/index.d.ts +0 -15
- package/dist/slices/update-field/index.js +0 -208
- package/dist/slices/whoami/index.d.ts +0 -2
- package/dist/types.d.ts +0 -195
- package/dist/types.js +0 -1
package/dist/lib/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
2
|
import * as crypto from 'node:crypto';
|
|
3
3
|
import { exec } from 'node:child_process';
|
|
4
|
-
import { saveAuthTokens, clearAuthTokens, getAuthTokens, getKeycloakUrl } from './config
|
|
4
|
+
import { saveAuthTokens, clearAuthTokens, getAuthTokens, getKeycloakUrl } from './config';
|
|
5
5
|
const KEYCLOAK_CLIENT_ID = 'eventmodeler-cli';
|
|
6
6
|
const REDIRECT_URI = 'http://localhost:8787/callback';
|
|
7
7
|
/**
|
package/dist/lib/config.js
CHANGED
|
@@ -78,18 +78,3 @@ export function setKeycloakUrl(url) {
|
|
|
78
78
|
config.keycloakUrl = url;
|
|
79
79
|
saveGlobalConfig(config);
|
|
80
80
|
}
|
|
81
|
-
export function getDefaultFormat() {
|
|
82
|
-
// 1. Check environment variable
|
|
83
|
-
const envFormat = process.env.EVENTMODELER_FORMAT;
|
|
84
|
-
if (envFormat === 'json')
|
|
85
|
-
return 'json';
|
|
86
|
-
if (envFormat === 'xml')
|
|
87
|
-
return 'xml';
|
|
88
|
-
// 2. Check config file
|
|
89
|
-
const config = loadGlobalConfig();
|
|
90
|
-
if (config.format === 'json' || config.format === 'xml') {
|
|
91
|
-
return config.format;
|
|
92
|
-
}
|
|
93
|
-
// 3. Default to XML (better for AI agents)
|
|
94
|
-
return 'xml';
|
|
95
|
-
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const FillStyle = z.enum(['solid', 'hachure', 'cross-hatch', 'zigzag']);
|
|
3
|
+
const StrokeStyle = z.enum(['solid', 'dashed', 'dotted']);
|
|
4
|
+
const TextAlign = z.enum(['left', 'center', 'right']);
|
|
5
|
+
const VerticalAlign = z.enum(['top', 'middle', 'bottom']);
|
|
6
|
+
const StyleFields = {
|
|
7
|
+
strokeColor: z.string().optional(),
|
|
8
|
+
backgroundColor: z.string().optional(),
|
|
9
|
+
fillStyle: FillStyle.optional(),
|
|
10
|
+
strokeStyle: StrokeStyle.optional(),
|
|
11
|
+
strokeWidth: z.number().nonnegative().optional(),
|
|
12
|
+
roughness: z.number().nonnegative().optional(),
|
|
13
|
+
opacity: z.number().min(0).max(100).optional(),
|
|
14
|
+
angle: z.number().optional(),
|
|
15
|
+
};
|
|
16
|
+
const BoxFields = {
|
|
17
|
+
id: z.string().min(1),
|
|
18
|
+
x: z.number(),
|
|
19
|
+
y: z.number(),
|
|
20
|
+
width: z.number(),
|
|
21
|
+
height: z.number(),
|
|
22
|
+
...StyleFields,
|
|
23
|
+
};
|
|
24
|
+
const ShapeElement = z.object({
|
|
25
|
+
type: z.enum(['rectangle', 'ellipse', 'diamond', 'frame', 'magicframe', 'iframe', 'embeddable', 'image', 'freedraw']),
|
|
26
|
+
...BoxFields,
|
|
27
|
+
}).passthrough();
|
|
28
|
+
const LineElement = z.object({
|
|
29
|
+
type: z.enum(['line', 'arrow']),
|
|
30
|
+
...BoxFields,
|
|
31
|
+
points: z.array(z.tuple([z.number(), z.number()])).optional(),
|
|
32
|
+
}).passthrough();
|
|
33
|
+
const TextElement = z.object({
|
|
34
|
+
type: z.literal('text'),
|
|
35
|
+
id: z.string().min(1),
|
|
36
|
+
x: z.number(),
|
|
37
|
+
y: z.number(),
|
|
38
|
+
width: z.number().optional(),
|
|
39
|
+
height: z.number().optional(),
|
|
40
|
+
text: z.string(),
|
|
41
|
+
fontSize: z.number().positive(),
|
|
42
|
+
fontFamily: z.number().int().positive().optional(),
|
|
43
|
+
textAlign: TextAlign.optional(),
|
|
44
|
+
verticalAlign: VerticalAlign.optional(),
|
|
45
|
+
...StyleFields,
|
|
46
|
+
}).passthrough();
|
|
47
|
+
const Element = z.discriminatedUnion('type', [ShapeElement, LineElement, TextElement]);
|
|
48
|
+
export const ExcalidrawDesign = z.array(Element).superRefine((elements, ctx) => {
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
elements.forEach((el, i) => {
|
|
51
|
+
if (seen.has(el.id)) {
|
|
52
|
+
ctx.addIssue({
|
|
53
|
+
code: 'custom',
|
|
54
|
+
path: [i, 'id'],
|
|
55
|
+
message: `duplicate id "${el.id}"`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
seen.add(el.id);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
export function formatZodError(error) {
|
|
62
|
+
return error.issues.map(issue => {
|
|
63
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
64
|
+
return `${path}: ${issue.message}`;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { loadProjectConfig } from './project-config';
|
|
2
|
+
/** Load modelId from .eventmodeler.json or exit with error. */
|
|
3
|
+
export function requireModelId() {
|
|
4
|
+
const config = loadProjectConfig();
|
|
5
|
+
if (!config || config.type !== 'cloud') {
|
|
6
|
+
console.error('Error: No project configured.');
|
|
7
|
+
console.error('Run "eventmodeler init" to connect this directory to a model.');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
return config.modelId;
|
|
11
|
+
}
|
|
@@ -81,3 +81,23 @@ export function getProjectRoot(startDir = process.cwd()) {
|
|
|
81
81
|
export function isInProject(startDir) {
|
|
82
82
|
return findProjectConfigPath(startDir) !== null;
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Load the `loop` block from .eventmodeler.json, if present.
|
|
86
|
+
*/
|
|
87
|
+
export function loadLoopConfig(startDir) {
|
|
88
|
+
const configPath = findProjectConfigPath(startDir);
|
|
89
|
+
if (!configPath)
|
|
90
|
+
return null;
|
|
91
|
+
try {
|
|
92
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
93
|
+
const config = JSON.parse(content);
|
|
94
|
+
const loop = config.loop;
|
|
95
|
+
if (loop && typeof loop.command === 'string' && typeof loop.interval === 'number') {
|
|
96
|
+
return { command: loop.command, interval: loop.interval };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as sdk from '../api/generated/sdk.gen';
|
|
2
|
+
const FIELD_BEARING_TYPES = ['command', 'event', 'readmodel', 'screen', 'processor', 'external-event'];
|
|
3
|
+
/** Map element type to the body key name used by the SDK. */
|
|
4
|
+
export function elementIdKey(type) {
|
|
5
|
+
switch (type) {
|
|
6
|
+
case 'command': return 'commandStickyId';
|
|
7
|
+
case 'event': return 'eventStickyId';
|
|
8
|
+
case 'readmodel': return 'readModelStickyId';
|
|
9
|
+
case 'external-event': return 'externalEventId';
|
|
10
|
+
case 'swimlane': return 'swimLaneId';
|
|
11
|
+
default: return `${type}Id`;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Unwrap SDK response — throw on error, return data on success. */
|
|
15
|
+
export function unwrap(result) {
|
|
16
|
+
if (result.error) {
|
|
17
|
+
const err = result.error;
|
|
18
|
+
const message = err?.error ?? err?.message ?? err?.detail ?? JSON.stringify(err);
|
|
19
|
+
throw new Error(String(message));
|
|
20
|
+
}
|
|
21
|
+
if (result.data === undefined) {
|
|
22
|
+
throw new Error(`Empty response (HTTP ${result.response.status})`);
|
|
23
|
+
}
|
|
24
|
+
return result.data;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve an element name to its UUID via the CLI resolver.
|
|
28
|
+
* If idOverride is provided, skip resolution and return it directly.
|
|
29
|
+
*/
|
|
30
|
+
export async function resolve(modelId, elementType, name, idOverride) {
|
|
31
|
+
if (idOverride)
|
|
32
|
+
return idOverride;
|
|
33
|
+
const result = unwrap(await sdk.resolveElement({
|
|
34
|
+
query: { modelId, elementType, name }
|
|
35
|
+
}));
|
|
36
|
+
const elementId = result.elementId;
|
|
37
|
+
if (!elementId) {
|
|
38
|
+
throw new Error(`${elementType} "${name}" not found`);
|
|
39
|
+
}
|
|
40
|
+
return String(elementId);
|
|
41
|
+
}
|
|
42
|
+
/** Resolve an element name to its UUID, trying all field-bearing types. */
|
|
43
|
+
export async function resolveAnyElement(modelId, name, idOverride) {
|
|
44
|
+
if (idOverride) {
|
|
45
|
+
return { elementId: idOverride, elementType: 'unknown' };
|
|
46
|
+
}
|
|
47
|
+
for (const t of FIELD_BEARING_TYPES) {
|
|
48
|
+
try {
|
|
49
|
+
const id = await resolve(modelId, t, name);
|
|
50
|
+
return { elementId: id, elementType: t };
|
|
51
|
+
}
|
|
52
|
+
catch { /* try next */ }
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Element "${name}" not found`);
|
|
55
|
+
}
|
|
56
|
+
/** Pretty-print JSON to stdout. */
|
|
57
|
+
export function out(data) {
|
|
58
|
+
console.log(JSON.stringify(data, null, 2));
|
|
59
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as sdk from '../api/generated/sdk.gen';
|
|
2
|
+
import { unwrap } from './resolve';
|
|
3
|
+
/** Resolve a scenario name to its UUID. If idOverride is provided, skip resolution. */
|
|
4
|
+
export async function resolveScenarioId(modelId, name, idOverride) {
|
|
5
|
+
if (idOverride)
|
|
6
|
+
return idOverride;
|
|
7
|
+
const result = unwrap(await sdk.resolveScenario({
|
|
8
|
+
query: { modelId, name }
|
|
9
|
+
}));
|
|
10
|
+
const scenarioId = result.scenarioId;
|
|
11
|
+
if (!scenarioId) {
|
|
12
|
+
throw new Error(`Scenario "${name}" not found`);
|
|
13
|
+
}
|
|
14
|
+
return String(scenarioId);
|
|
15
|
+
}
|
|
@@ -1,101 +1,9 @@
|
|
|
1
|
-
import { XMLParser } from 'fast-xml-parser';
|
|
2
|
-
const xmlParser = new XMLParser({
|
|
3
|
-
ignoreAttributes: false,
|
|
4
|
-
attributeNamePrefix: '@_',
|
|
5
|
-
allowBooleanAttributes: false,
|
|
6
|
-
parseAttributeValue: false,
|
|
7
|
-
isArray: (name) => name === 'event' || name === 'field',
|
|
8
|
-
trimValues: true,
|
|
9
|
-
});
|
|
10
|
-
function asArray(value) {
|
|
11
|
-
if (value === undefined || value === null)
|
|
12
|
-
return [];
|
|
13
|
-
return Array.isArray(value) ? value : [value];
|
|
14
|
-
}
|
|
15
1
|
function asRecord(value, context) {
|
|
16
2
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
17
3
|
throw new Error(`${context} must be an object`);
|
|
18
4
|
}
|
|
19
5
|
return value;
|
|
20
6
|
}
|
|
21
|
-
function parseFieldValue(value) {
|
|
22
|
-
const trimmed = value.trim();
|
|
23
|
-
if (trimmed === '')
|
|
24
|
-
return '';
|
|
25
|
-
if (trimmed === 'true')
|
|
26
|
-
return true;
|
|
27
|
-
if (trimmed === 'false')
|
|
28
|
-
return false;
|
|
29
|
-
if (trimmed === 'null')
|
|
30
|
-
return null;
|
|
31
|
-
if (/^-?\d+$/.test(trimmed))
|
|
32
|
-
return parseInt(trimmed, 10);
|
|
33
|
-
if (/^-?\d+\.\d+$/.test(trimmed))
|
|
34
|
-
return parseFloat(trimmed);
|
|
35
|
-
try {
|
|
36
|
-
return JSON.parse(trimmed);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return trimmed;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function parseFieldValuesFromNodes(nodes) {
|
|
43
|
-
if (nodes.length === 0)
|
|
44
|
-
return undefined;
|
|
45
|
-
const result = {};
|
|
46
|
-
for (const node of nodes) {
|
|
47
|
-
const n = node;
|
|
48
|
-
const name = n['@_name'];
|
|
49
|
-
if (!name)
|
|
50
|
-
continue;
|
|
51
|
-
const childFields = asArray(n['field']);
|
|
52
|
-
let value;
|
|
53
|
-
if (childFields.length > 0) {
|
|
54
|
-
value = parseFieldValuesFromNodes(childFields);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
// Get text content (fast-xml-parser stores it as #text)
|
|
58
|
-
const text = n['#text'];
|
|
59
|
-
if (text === undefined || text === '') {
|
|
60
|
-
value = null;
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
value = parseFieldValue(String(text));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
// Handle duplicate keys (multiple values → array)
|
|
67
|
-
if (name in result) {
|
|
68
|
-
const current = result[name];
|
|
69
|
-
if (Array.isArray(current)) {
|
|
70
|
-
current.push(value);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
result[name] = [current, value];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
result[name] = value;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return Object.keys(result).length > 0 ? result : undefined;
|
|
81
|
-
}
|
|
82
|
-
function parseEventInputsFromNodes(nodes) {
|
|
83
|
-
const result = [];
|
|
84
|
-
for (const node of nodes) {
|
|
85
|
-
const n = node;
|
|
86
|
-
const nameFromAttr = n['@_name'];
|
|
87
|
-
const textContent = n['#text'];
|
|
88
|
-
const eventName = nameFromAttr ?? textContent;
|
|
89
|
-
if (!eventName)
|
|
90
|
-
continue;
|
|
91
|
-
const childFields = asArray(n['field']);
|
|
92
|
-
result.push({
|
|
93
|
-
event: eventName,
|
|
94
|
-
fieldValues: parseFieldValuesFromNodes(childFields),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
7
|
function parseEventInput(value, context) {
|
|
100
8
|
if (typeof value === 'string') {
|
|
101
9
|
return { event: value };
|
|
@@ -142,8 +50,8 @@ function parseObjectValue(value, context) {
|
|
|
142
50
|
}
|
|
143
51
|
return value;
|
|
144
52
|
}
|
|
145
|
-
function
|
|
146
|
-
const raw = asRecord(
|
|
53
|
+
export function parseScenarioInput(input) {
|
|
54
|
+
const raw = asRecord(JSON.parse(input.trim()), 'scenario');
|
|
147
55
|
const name = raw.name;
|
|
148
56
|
if (typeof name !== 'string' || name.trim() === '') {
|
|
149
57
|
throw new Error('scenario.name is required');
|
|
@@ -193,115 +101,3 @@ function normalizeScenarioInput(rawValue) {
|
|
|
193
101
|
then,
|
|
194
102
|
};
|
|
195
103
|
}
|
|
196
|
-
function parseJsonInput(input) {
|
|
197
|
-
return normalizeScenarioInput(JSON.parse(input));
|
|
198
|
-
}
|
|
199
|
-
function parseXmlInput(input) {
|
|
200
|
-
const parsed = xmlParser.parse(input);
|
|
201
|
-
const scenario = parsed['scenario'];
|
|
202
|
-
if (!scenario) {
|
|
203
|
-
throw new Error('Invalid XML: missing <scenario> tag');
|
|
204
|
-
}
|
|
205
|
-
const name = scenario['@_name'];
|
|
206
|
-
if (!name) {
|
|
207
|
-
throw new Error('Invalid XML: scenario must have a name attribute');
|
|
208
|
-
}
|
|
209
|
-
const description = scenario['@_description'];
|
|
210
|
-
// Parse given events
|
|
211
|
-
const givenNode = scenario['given'];
|
|
212
|
-
const given = givenNode ? parseEventInputsFromNodes(asArray(givenNode['event'])) : [];
|
|
213
|
-
// Parse when
|
|
214
|
-
let when;
|
|
215
|
-
const whenNode = scenario['when'];
|
|
216
|
-
if (whenNode) {
|
|
217
|
-
const commandNodes = asArray(whenNode['command']);
|
|
218
|
-
const commandNode = commandNodes[0];
|
|
219
|
-
const whenEvents = parseEventInputsFromNodes(asArray(whenNode['event']));
|
|
220
|
-
if (commandNode || whenEvents.length > 0) {
|
|
221
|
-
let commandName;
|
|
222
|
-
let commandFieldValues;
|
|
223
|
-
if (commandNode) {
|
|
224
|
-
const nameFromAttr = commandNode['@_name'];
|
|
225
|
-
const textContent = commandNode['#text'];
|
|
226
|
-
commandName = nameFromAttr ?? textContent ?? undefined;
|
|
227
|
-
commandFieldValues = parseFieldValuesFromNodes(asArray(commandNode['field']));
|
|
228
|
-
}
|
|
229
|
-
when = {
|
|
230
|
-
command: commandName || undefined,
|
|
231
|
-
commandFieldValues,
|
|
232
|
-
events: whenEvents.length > 0 ? whenEvents : undefined,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
// Parse then
|
|
237
|
-
const thenNode = scenario['then'];
|
|
238
|
-
if (!thenNode) {
|
|
239
|
-
throw new Error('Invalid XML: missing <then> tag');
|
|
240
|
-
}
|
|
241
|
-
const thenType = thenNode['@_type'];
|
|
242
|
-
if (thenType !== 'error' &&
|
|
243
|
-
thenType !== 'events' &&
|
|
244
|
-
thenType !== 'readModelAssertion' &&
|
|
245
|
-
thenType !== 'command' &&
|
|
246
|
-
thenType !== 'noCommand') {
|
|
247
|
-
throw new Error('Invalid XML: <then> must have a type attribute');
|
|
248
|
-
}
|
|
249
|
-
const then = { type: thenType };
|
|
250
|
-
if (thenType === 'error') {
|
|
251
|
-
then.errorType = thenNode['@_errorType'];
|
|
252
|
-
const errorTypeNode = thenNode['errorType'];
|
|
253
|
-
if (!then.errorType && errorTypeNode) {
|
|
254
|
-
then.errorType = typeof errorTypeNode === 'string' ? errorTypeNode : errorTypeNode['#text'];
|
|
255
|
-
}
|
|
256
|
-
const messageNode = thenNode['message'];
|
|
257
|
-
if (messageNode) {
|
|
258
|
-
then.errorMessage = typeof messageNode === 'string' ? messageNode : messageNode['#text'];
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else if (thenType === 'events') {
|
|
262
|
-
then.events = parseEventInputsFromNodes(asArray(thenNode['event']));
|
|
263
|
-
}
|
|
264
|
-
else if (thenType === 'command') {
|
|
265
|
-
const commandNodes = asArray(thenNode['command']);
|
|
266
|
-
const commandNode = commandNodes[0];
|
|
267
|
-
if (commandNode) {
|
|
268
|
-
const nameFromAttr = commandNode['@_name'];
|
|
269
|
-
const textContent = commandNode['#text'];
|
|
270
|
-
then.command = nameFromAttr ?? textContent ?? undefined;
|
|
271
|
-
then.commandFieldValues = parseFieldValuesFromNodes(asArray(commandNode['field']));
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
else if (thenType === 'readModelAssertion') {
|
|
275
|
-
const readModelNode = thenNode['read-model'];
|
|
276
|
-
if (readModelNode) {
|
|
277
|
-
const nameFromAttr = readModelNode['@_name'];
|
|
278
|
-
const textContent = readModelNode['#text'];
|
|
279
|
-
then.readModel = nameFromAttr ?? textContent ?? undefined;
|
|
280
|
-
const expectedNode = readModelNode['expected'];
|
|
281
|
-
const directFields = parseFieldValuesFromNodes(asArray(readModelNode['field']));
|
|
282
|
-
const expectedFields = expectedNode ? parseFieldValuesFromNodes(asArray(expectedNode['field'])) : undefined;
|
|
283
|
-
then.expected = {
|
|
284
|
-
...(directFields ?? {}),
|
|
285
|
-
...(expectedFields ?? {}),
|
|
286
|
-
};
|
|
287
|
-
const givenEventsNode = (readModelNode['given'] ?? readModelNode['given-events']);
|
|
288
|
-
if (givenEventsNode) {
|
|
289
|
-
then.givenEvents = parseEventInputsFromNodes(asArray(givenEventsNode['event']));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return {
|
|
294
|
-
name,
|
|
295
|
-
description,
|
|
296
|
-
given: given.length > 0 ? given : undefined,
|
|
297
|
-
when,
|
|
298
|
-
then,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
export function parseScenarioInput(input) {
|
|
302
|
-
const trimmed = input.trim();
|
|
303
|
-
if (trimmed.startsWith('<')) {
|
|
304
|
-
return parseXmlInput(trimmed);
|
|
305
|
-
}
|
|
306
|
-
return parseJsonInput(trimmed);
|
|
307
|
-
}
|
|
@@ -316,7 +316,7 @@ export class \${aggregate}Aggregate {
|
|
|
316
316
|
eventmodeler codegen slice "Place Order" | node generator.js
|
|
317
317
|
|
|
318
318
|
# All slices (bash loop)
|
|
319
|
-
for slice in $(eventmodeler list slices
|
|
319
|
+
for slice in $(eventmodeler list slices | node -e 'const fs=require("fs");const d=JSON.parse(fs.readFileSync(0,"utf8"));for(const s of (d.slices||[])) console.log(s.name)'); do
|
|
320
320
|
eventmodeler codegen slice "$slice" | node generator.js
|
|
321
321
|
done
|
|
322
322
|
|
|
@@ -50,30 +50,7 @@ eventmodeler create flow --from "<source>" --to "<target>"
|
|
|
50
50
|
|
|
51
51
|
### 1. Create Your Slices First
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
# User places an order
|
|
55
|
-
eventmodeler create state-change-slice --xml '<state-change-slice name="Place Order">
|
|
56
|
-
<screen name="Checkout">...</screen>
|
|
57
|
-
<command name="PlaceOrder">...</command>
|
|
58
|
-
<event name="OrderPlaced">...</event>
|
|
59
|
-
</state-change-slice>'
|
|
60
|
-
|
|
61
|
-
# View for order status
|
|
62
|
-
eventmodeler create state-view-slice --xml '<state-view-slice name="View Order Status" after="Place Order">
|
|
63
|
-
<read-model name="OrderStatus">...</read-model>
|
|
64
|
-
</state-view-slice>'
|
|
65
|
-
|
|
66
|
-
# System automatically fulfills order (includes its own read model)
|
|
67
|
-
eventmodeler create automation-slice --xml '<automation-slice name="Auto Fulfill" after="View Order Status">
|
|
68
|
-
<read-model name="OrderReadyToFulfill">
|
|
69
|
-
<field name="orderId" type="UUID"/>
|
|
70
|
-
<field name="isPaid" type="Boolean"/>
|
|
71
|
-
</read-model>
|
|
72
|
-
<processor name="Fulfillment Processor"/>
|
|
73
|
-
<command name="FulfillOrder">...</command>
|
|
74
|
-
<event name="OrderFulfilled">...</event>
|
|
75
|
-
</automation-slice>'
|
|
76
|
-
\`\`\`
|
|
53
|
+
Build each slice by adding fields to its elements and connecting them with internal flows.
|
|
77
54
|
|
|
78
55
|
### 2. Connect Events to Read Models
|
|
79
56
|
|
|
@@ -115,11 +92,11 @@ eventmodeler create flow --from "OrderStatus" --to "Order Details Screen"
|
|
|
115
92
|
After creating flows, map the fields:
|
|
116
93
|
|
|
117
94
|
\`\`\`bash
|
|
118
|
-
eventmodeler map fields --
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'
|
|
95
|
+
eventmodeler map fields --from "OrderPlaced" --to "OrderStatus" '[
|
|
96
|
+
{"from": "orderId", "to": "orderId"},
|
|
97
|
+
{"from": "customerId", "to": "customerId"},
|
|
98
|
+
{"from": "placedAt", "to": "placedAt"}
|
|
99
|
+
]'
|
|
123
100
|
\`\`\`
|
|
124
101
|
|
|
125
102
|
### 6. Verify Completeness
|
|
@@ -140,11 +117,9 @@ When multiple elements share the same name (e.g., linked copies of events or rea
|
|
|
140
117
|
\`\`\`bash
|
|
141
118
|
# List events with their IDs
|
|
142
119
|
eventmodeler list events
|
|
143
|
-
# Output
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
# <event id="def67890-..." name="OrderPlaced" fields="4"/> <!-- linked copy -->
|
|
147
|
-
# </events>
|
|
120
|
+
# Output includes id and name for each event, e.g.:
|
|
121
|
+
# [{"id": "abc12345-...", "name": "OrderPlaced", "fields": 4},
|
|
122
|
+
# {"id": "def67890-...", "name": "OrderPlaced", "fields": 4}]
|
|
148
123
|
\`\`\`
|
|
149
124
|
|
|
150
125
|
### Using IDs in Commands
|
|
@@ -156,9 +131,9 @@ Prefix the ID (or ID prefix) with \`id:\`:
|
|
|
156
131
|
eventmodeler create flow --from "id:abc12345" --to "OrderStatus"
|
|
157
132
|
|
|
158
133
|
# Map fields using ID prefix
|
|
159
|
-
eventmodeler map fields --
|
|
160
|
-
|
|
161
|
-
'
|
|
134
|
+
eventmodeler map fields --from "id:abc12345" --to "OrderStatus" '[
|
|
135
|
+
{"from": "orderId", "to": "orderId"}
|
|
136
|
+
]'
|
|
162
137
|
\`\`\`
|
|
163
138
|
|
|
164
139
|
### When the CLI Finds Duplicates
|