eventmodeler 0.6.0 → 0.6.2
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/index.js +7133 -34
- package/package.json +5 -4
- package/dist/api/client-config.js +0 -10
- package/dist/api/generated/client/client.gen.js +0 -235
- package/dist/api/generated/client/index.js +0 -6
- package/dist/api/generated/client/types.gen.js +0 -2
- package/dist/api/generated/client/utils.gen.js +0 -228
- package/dist/api/generated/client.gen.js +0 -4
- package/dist/api/generated/core/auth.gen.js +0 -14
- package/dist/api/generated/core/bodySerializer.gen.js +0 -57
- package/dist/api/generated/core/params.gen.js +0 -100
- package/dist/api/generated/core/pathSerializer.gen.js +0 -106
- package/dist/api/generated/core/queryKeySerializer.gen.js +0 -92
- package/dist/api/generated/core/serverSentEvents.gen.js +0 -133
- package/dist/api/generated/core/types.gen.js +0 -2
- package/dist/api/generated/core/utils.gen.js +0 -87
- package/dist/api/generated/index.js +0 -2
- package/dist/api/generated/sdk.gen.js +0 -4222
- package/dist/api/generated/types.gen.js +0 -2
- package/dist/api/generated/zod.gen.js +0 -7217
- package/dist/commands/add.js +0 -315
- package/dist/commands/auth.js +0 -14
- package/dist/commands/create.js +0 -192
- package/dist/commands/design.js +0 -108
- package/dist/commands/guide.js +0 -15
- package/dist/commands/init.js +0 -21
- package/dist/commands/list-schemas.js +0 -177
- package/dist/commands/list.js +0 -39
- package/dist/commands/loop.js +0 -101
- package/dist/commands/map.js +0 -40
- package/dist/commands/mark.js +0 -27
- package/dist/commands/move.js +0 -35
- package/dist/commands/remove.js +0 -170
- package/dist/commands/rename.js +0 -53
- package/dist/commands/resize.js +0 -30
- package/dist/commands/search.js +0 -14
- package/dist/commands/set.js +0 -199
- package/dist/commands/show-schemas.js +0 -259
- package/dist/commands/show.js +0 -56
- package/dist/commands/summary.js +0 -13
- package/dist/commands/update.js +0 -240
- package/dist/lib/auth.js +0 -331
- package/dist/lib/config.js +0 -80
- package/dist/lib/excalidraw-schema.js +0 -66
- package/dist/lib/globals.js +0 -8
- package/dist/lib/model.js +0 -11
- package/dist/lib/project-config.js +0 -103
- package/dist/lib/resolve.js +0 -59
- package/dist/lib/scenario.js +0 -15
- package/dist/slices/add-scenario/index.js +0 -103
- package/dist/slices/guide/guides/codegen.js +0 -339
- package/dist/slices/guide/guides/connect-slices.js +0 -202
- package/dist/slices/guide/guides/create-slices.js +0 -273
- package/dist/slices/guide/guides/explore.js +0 -238
- package/dist/slices/guide/guides/information-flow.js +0 -304
- package/dist/slices/guide/guides/scenarios.js +0 -214
- package/dist/slices/guide/index.js +0 -40
- package/dist/slices/help/index.js +0 -96
- package/dist/slices/help/topics/build-codegen.js +0 -109
- package/dist/slices/help/topics/build-slice.js +0 -147
- package/dist/slices/help/topics/check-completeness.js +0 -57
- package/dist/slices/help/topics/connect-slices.js +0 -99
- package/dist/slices/help/topics/explore-model.js +0 -112
- package/dist/slices/help/topics/json-reference.js +0 -188
- package/dist/slices/help/topics/linked-copies.js +0 -89
- package/dist/slices/help/topics/manipulate-canvas.js +0 -150
- package/dist/slices/help/topics/write-scenarios.js +0 -162
- package/dist/slices/init/index.js +0 -86
- package/dist/slices/init/loop.js +0 -60
- package/dist/slices/login/index.js +0 -20
- package/dist/slices/logout/index.js +0 -14
- package/dist/slices/open-app/index.js +0 -36
- package/dist/slices/whoami/index.js +0 -19
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
export const meta = {
|
|
2
|
-
name: 'codegen',
|
|
3
|
-
description: 'Build code generators that consume eventmodeler codegen output',
|
|
4
|
-
};
|
|
5
|
-
export const content = `
|
|
6
|
-
# Building Code Generators for Event Models
|
|
7
|
-
|
|
8
|
-
The \`eventmodeler codegen\` commands output structured JSON that code generators can consume:
|
|
9
|
-
- \`eventmodeler codegen slice "<slice-name>"\` for per-slice generation
|
|
10
|
-
- \`eventmodeler codegen events [--chapter "<chapter-name>"]\` for deduplicated event scaffolding across slices
|
|
11
|
-
|
|
12
|
-
## The Codegen Pipeline
|
|
13
|
-
|
|
14
|
-
Per-slice pipeline:
|
|
15
|
-
|
|
16
|
-
\`\`\`
|
|
17
|
-
eventmodeler codegen slice "Place Order" | your-generator > output-files
|
|
18
|
-
\`\`\`
|
|
19
|
-
|
|
20
|
-
Events-only pipeline:
|
|
21
|
-
|
|
22
|
-
\`\`\`
|
|
23
|
-
eventmodeler codegen events --chapter "Register Products" | your-events-generator > events.kt
|
|
24
|
-
\`\`\`
|
|
25
|
-
|
|
26
|
-
Your generator receives JSON on stdin and produces code files.
|
|
27
|
-
|
|
28
|
-
## Understanding the Input
|
|
29
|
-
|
|
30
|
-
Run codegen to see what you'll receive:
|
|
31
|
-
|
|
32
|
-
\`\`\`bash
|
|
33
|
-
eventmodeler codegen slice "Place Order"
|
|
34
|
-
\`\`\`
|
|
35
|
-
|
|
36
|
-
### Top-Level Structure
|
|
37
|
-
|
|
38
|
-
\`\`\`json
|
|
39
|
-
{
|
|
40
|
-
"sliceType": "STATE_CHANGE | STATE_VIEW | AUTOMATION",
|
|
41
|
-
"slice": { "id": "...", "name": "Place Order" },
|
|
42
|
-
"chapter": { "name": "...", "hierarchy": ["Parent", "Child"] },
|
|
43
|
-
"elements": { ... },
|
|
44
|
-
"inboundDependencies": [ ... ],
|
|
45
|
-
"internalFlows": [ ... ],
|
|
46
|
-
"scenarios": [ ... ]
|
|
47
|
-
}
|
|
48
|
-
\`\`\`
|
|
49
|
-
|
|
50
|
-
### Events-Only Structure (\`codegen events\`)
|
|
51
|
-
|
|
52
|
-
\`\`\`json
|
|
53
|
-
{
|
|
54
|
-
"chapter": { "name": "Register Products", "parent": { "name": "Configuration" } },
|
|
55
|
-
"sliceCount": 4,
|
|
56
|
-
"eventCount": 9,
|
|
57
|
-
"slices": [
|
|
58
|
-
{ "id": "...", "name": "Place Product", "sliceType": "STATE_CHANGE" }
|
|
59
|
-
],
|
|
60
|
-
"events": [
|
|
61
|
-
{
|
|
62
|
-
"id": "...",
|
|
63
|
-
"name": "ProductRegistered",
|
|
64
|
-
"fields": [ ... ],
|
|
65
|
-
"aggregate": "Product",
|
|
66
|
-
"sourceSlices": ["Place Product", "Import Product"]
|
|
67
|
-
}
|
|
68
|
-
]
|
|
69
|
-
}
|
|
70
|
-
\`\`\`
|
|
71
|
-
|
|
72
|
-
Use \`codegen events\` when generating shared event artifacts in one pass (for a chapter or entire model).
|
|
73
|
-
|
|
74
|
-
### Slice Types and What to Generate
|
|
75
|
-
|
|
76
|
-
| sliceType | Components | Generate |
|
|
77
|
-
|-----------|------------|----------|
|
|
78
|
-
| \`STATE_CHANGE\` | Screen, Command, Event | API endpoint, command handler, event, tests |
|
|
79
|
-
| \`STATE_VIEW\` | ReadModel | Projection/denormalizer, query endpoint |
|
|
80
|
-
| \`AUTOMATION\` | Processor, ReadModel, Command, Event | Event handler, saga/process manager |
|
|
81
|
-
|
|
82
|
-
## Building a Generator: Step by Step
|
|
83
|
-
|
|
84
|
-
### 1. Parse the Input
|
|
85
|
-
|
|
86
|
-
\`\`\`typescript
|
|
87
|
-
// generator.ts
|
|
88
|
-
import { stdin } from 'process';
|
|
89
|
-
|
|
90
|
-
interface CodegenInput {
|
|
91
|
-
sliceType: 'STATE_CHANGE' | 'STATE_VIEW' | 'AUTOMATION';
|
|
92
|
-
slice: { id: string; name: string };
|
|
93
|
-
elements: {
|
|
94
|
-
commands: Array<{ name: string; fields: Field[] }>;
|
|
95
|
-
events: Array<{ name: string; fields: Field[]; aggregate?: string }>;
|
|
96
|
-
readModels: Array<{ name: string; fields: Field[] }>;
|
|
97
|
-
screens: Array<{ name: string; fields: Field[] }>;
|
|
98
|
-
processors: Array<{ name: string }>;
|
|
99
|
-
};
|
|
100
|
-
inboundDependencies: InboundDependency[];
|
|
101
|
-
internalFlows: Flow[];
|
|
102
|
-
scenarios: Scenario[];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function readInput(): Promise<CodegenInput> {
|
|
106
|
-
const chunks: Buffer[] = [];
|
|
107
|
-
for await (const chunk of stdin) {
|
|
108
|
-
chunks.push(chunk);
|
|
109
|
-
}
|
|
110
|
-
return JSON.parse(Buffer.concat(chunks).toString());
|
|
111
|
-
}
|
|
112
|
-
\`\`\`
|
|
113
|
-
|
|
114
|
-
### 2. Route by Slice Type
|
|
115
|
-
|
|
116
|
-
\`\`\`typescript
|
|
117
|
-
async function main() {
|
|
118
|
-
const input = await readInput();
|
|
119
|
-
|
|
120
|
-
switch (input.sliceType) {
|
|
121
|
-
case 'STATE_CHANGE':
|
|
122
|
-
generateStateChangeSlice(input);
|
|
123
|
-
break;
|
|
124
|
-
case 'STATE_VIEW':
|
|
125
|
-
generateStateViewSlice(input);
|
|
126
|
-
break;
|
|
127
|
-
case 'AUTOMATION':
|
|
128
|
-
generateAutomationSlice(input);
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
\`\`\`
|
|
133
|
-
|
|
134
|
-
### 3. Generate from Elements
|
|
135
|
-
|
|
136
|
-
**Commands → DTOs/Request types:**
|
|
137
|
-
\`\`\`typescript
|
|
138
|
-
function generateCommand(cmd: Command): string {
|
|
139
|
-
const fields = cmd.fields
|
|
140
|
-
.map(f => \` \${f.name}: \${mapType(f.type)}\${f.optional ? ' | null' : ''};\`)
|
|
141
|
-
.join('\\n');
|
|
142
|
-
|
|
143
|
-
return \`export interface \${cmd.name}Command {\\n\${fields}\\n}\`;
|
|
144
|
-
}
|
|
145
|
-
\`\`\`
|
|
146
|
-
|
|
147
|
-
**Events → Event types with metadata:**
|
|
148
|
-
\`\`\`typescript
|
|
149
|
-
function generateEvent(evt: Event): string {
|
|
150
|
-
const fields = evt.fields.map(f => {
|
|
151
|
-
const generated = f.generated ? ' // generated' : '';
|
|
152
|
-
return \` \${f.name}: \${mapType(f.type)};\${generated}\`;
|
|
153
|
-
}).join('\\n');
|
|
154
|
-
|
|
155
|
-
return \`export interface \${evt.name}Event {\\n type: '\${evt.name}';\\n\${fields}\\n}\`;
|
|
156
|
-
}
|
|
157
|
-
\`\`\`
|
|
158
|
-
|
|
159
|
-
**ReadModels → Projection types:**
|
|
160
|
-
\`\`\`typescript
|
|
161
|
-
function generateReadModel(rm: ReadModel): string {
|
|
162
|
-
const fields = rm.fields
|
|
163
|
-
.map(f => \` \${f.name}: \${mapType(f.type)}\${f.optional ? ' | null' : ''};\`)
|
|
164
|
-
.join('\\n');
|
|
165
|
-
|
|
166
|
-
return \`export interface \${rm.name} {\\n\${fields}\\n}\`;
|
|
167
|
-
}
|
|
168
|
-
\`\`\`
|
|
169
|
-
|
|
170
|
-
### 4. Use Field Mappings for Projections
|
|
171
|
-
|
|
172
|
-
The \`inboundDependencies\` show how external events map to read model fields:
|
|
173
|
-
|
|
174
|
-
\`\`\`typescript
|
|
175
|
-
function generateProjection(input: CodegenInput): string {
|
|
176
|
-
const rm = input.elements.readModels[0];
|
|
177
|
-
const handlers: string[] = [];
|
|
178
|
-
|
|
179
|
-
for (const dep of input.inboundDependencies) {
|
|
180
|
-
if (dep.target.name === rm.name) {
|
|
181
|
-
const mappings = dep.fieldMappings
|
|
182
|
-
.map(m => \` state.\${m.targetFieldName} = event.\${m.sourceFieldName};\`)
|
|
183
|
-
.join('\\n');
|
|
184
|
-
|
|
185
|
-
handlers.push(\`
|
|
186
|
-
on\${dep.source.name}(state: \${rm.name}, event: \${dep.source.name}Event) {
|
|
187
|
-
\${mappings}
|
|
188
|
-
}\`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return \`export class \${rm.name}Projection {\\n\${handlers.join('\\n')}\\n}\`;
|
|
193
|
-
}
|
|
194
|
-
\`\`\`
|
|
195
|
-
|
|
196
|
-
### 5. Generate Tests from Scenarios
|
|
197
|
-
|
|
198
|
-
Scenarios are BDD specs that become tests. There are two scenario patterns:
|
|
199
|
-
|
|
200
|
-
**State-Change Scenarios** (whenCommand → then events/error):
|
|
201
|
-
- \`whenCommand\`: The command being tested
|
|
202
|
-
- \`then.type: 'events'\`: Expected events produced
|
|
203
|
-
- \`then.type: 'error'\`: Expected error
|
|
204
|
-
|
|
205
|
-
**Automation Scenarios** (whenEvents → then command):
|
|
206
|
-
- \`whenEvents\`: The event(s) that trigger the processor
|
|
207
|
-
- \`then.type: 'command'\`: The command the processor should issue
|
|
208
|
-
|
|
209
|
-
\`\`\`typescript
|
|
210
|
-
function generateTests(input: CodegenInput): string {
|
|
211
|
-
return input.scenarios.map(scenario => {
|
|
212
|
-
const givenSetup = scenario.given
|
|
213
|
-
.map(g => \` await given(new \${g.eventName}(\${JSON.stringify(g.fieldValues)}));\`)
|
|
214
|
-
.join('\\n');
|
|
215
|
-
|
|
216
|
-
// State-change scenarios: when is a command
|
|
217
|
-
let whenAction = '';
|
|
218
|
-
if (scenario.whenCommand) {
|
|
219
|
-
whenAction = \` await when(new \${scenario.whenCommand.commandName}(\${JSON.stringify(scenario.whenCommand.fieldValues)}));\`;
|
|
220
|
-
}
|
|
221
|
-
// Automation scenarios: when is event(s)
|
|
222
|
-
if (scenario.whenEvents?.length > 0) {
|
|
223
|
-
whenAction = scenario.whenEvents
|
|
224
|
-
.map(e => \` await whenEvent(new \${e.eventName}(\${JSON.stringify(e.fieldValues)}));\`)
|
|
225
|
-
.join('\\n');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
let thenAssertion: string;
|
|
229
|
-
if (scenario.then.type === 'events') {
|
|
230
|
-
const expected = scenario.then.expectedEvents!
|
|
231
|
-
.map(e => \`new \${e.eventName}(\${JSON.stringify(e.fieldValues)})\`)
|
|
232
|
-
.join(', ');
|
|
233
|
-
thenAssertion = \` await thenExpect([\${expected}]);\`;
|
|
234
|
-
} else if (scenario.then.type === 'command') {
|
|
235
|
-
const cmd = scenario.then.expectedCommand!;
|
|
236
|
-
thenAssertion = \` await thenExpectCommand(new \${cmd.commandName}(\${JSON.stringify(cmd.fieldValues)}));\`;
|
|
237
|
-
} else if (scenario.then.type === 'error') {
|
|
238
|
-
thenAssertion = \` await thenExpectError('\${scenario.then.errorType}', '\${scenario.then.errorMessage}');\`;
|
|
239
|
-
} else {
|
|
240
|
-
thenAssertion = \` await thenExpectState(\${JSON.stringify(scenario.then.expectedFieldValues)});\`;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return \`
|
|
244
|
-
test('\${scenario.name}', async () => {
|
|
245
|
-
\${givenSetup}
|
|
246
|
-
\${whenAction}
|
|
247
|
-
\${thenAssertion}
|
|
248
|
-
});\`;
|
|
249
|
-
}).join('\\n');
|
|
250
|
-
}
|
|
251
|
-
\`\`\`
|
|
252
|
-
|
|
253
|
-
## Type Mapping
|
|
254
|
-
|
|
255
|
-
Map event model types to your target language:
|
|
256
|
-
|
|
257
|
-
\`\`\`typescript
|
|
258
|
-
function mapType(fieldType: string, isList?: boolean): string {
|
|
259
|
-
const baseType = {
|
|
260
|
-
'UUID': 'string',
|
|
261
|
-
'String': 'string',
|
|
262
|
-
'Int': 'number',
|
|
263
|
-
'Long': 'number',
|
|
264
|
-
'Double': 'number',
|
|
265
|
-
'Decimal': 'Decimal', // or 'number' or 'BigNumber'
|
|
266
|
-
'Boolean': 'boolean',
|
|
267
|
-
'Date': 'Date',
|
|
268
|
-
'DateTime': 'Date',
|
|
269
|
-
'Custom': 'object', // handle subfields separately
|
|
270
|
-
}[fieldType] ?? 'unknown';
|
|
271
|
-
|
|
272
|
-
return isList ? \`\${baseType}[]\` : baseType;
|
|
273
|
-
}
|
|
274
|
-
\`\`\`
|
|
275
|
-
|
|
276
|
-
For Custom types with subfields, generate inline or separate types:
|
|
277
|
-
|
|
278
|
-
\`\`\`typescript
|
|
279
|
-
function generateFieldType(field: Field): string {
|
|
280
|
-
if (field.type === 'Custom' && field.subfields) {
|
|
281
|
-
const nested = field.subfields
|
|
282
|
-
.map(sf => \`\${sf.name}: \${mapType(sf.type)}\`)
|
|
283
|
-
.join('; ');
|
|
284
|
-
const type = \`{ \${nested} }\`;
|
|
285
|
-
return field.list ? \`Array<\${type}>\` : type;
|
|
286
|
-
}
|
|
287
|
-
return mapType(field.type, field.list);
|
|
288
|
-
}
|
|
289
|
-
\`\`\`
|
|
290
|
-
|
|
291
|
-
## Handling Aggregates
|
|
292
|
-
|
|
293
|
-
Events include their aggregate name when inside one:
|
|
294
|
-
|
|
295
|
-
\`\`\`typescript
|
|
296
|
-
function generateEventHandler(input: CodegenInput): string {
|
|
297
|
-
const event = input.elements.events[0];
|
|
298
|
-
const aggregate = event.aggregate;
|
|
299
|
-
|
|
300
|
-
if (aggregate) {
|
|
301
|
-
return \`
|
|
302
|
-
export class \${aggregate}Aggregate {
|
|
303
|
-
apply(event: \${event.name}Event) {
|
|
304
|
-
// Update aggregate state from event
|
|
305
|
-
}
|
|
306
|
-
}\`;
|
|
307
|
-
}
|
|
308
|
-
// ...
|
|
309
|
-
}
|
|
310
|
-
\`\`\`
|
|
311
|
-
|
|
312
|
-
## Running Your Generator
|
|
313
|
-
|
|
314
|
-
\`\`\`bash
|
|
315
|
-
# Single slice
|
|
316
|
-
eventmodeler codegen slice "Place Order" | node generator.js
|
|
317
|
-
|
|
318
|
-
# All slices (bash loop)
|
|
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
|
-
eventmodeler codegen slice "$slice" | node generator.js
|
|
321
|
-
done
|
|
322
|
-
|
|
323
|
-
# Chapter-scoped events scaffolding
|
|
324
|
-
eventmodeler codegen events --chapter "Register Products" | node generate-events.js
|
|
325
|
-
|
|
326
|
-
# Whole-model events scaffolding
|
|
327
|
-
eventmodeler codegen events | node generate-events.js
|
|
328
|
-
\`\`\`
|
|
329
|
-
|
|
330
|
-
## Best Practices
|
|
331
|
-
|
|
332
|
-
1. **Handle all slice types** - Your generator should handle STATE_CHANGE, STATE_VIEW, and AUTOMATION
|
|
333
|
-
2. **Use field mappings** - They define the projection logic between events and read models
|
|
334
|
-
3. **Generate tests from scenarios** - Scenarios are executable specifications
|
|
335
|
-
4. **Respect field attributes** - \`generated\` fields don't need input, \`optional\` fields can be null
|
|
336
|
-
5. **Use aggregate info** - Events in aggregates should be handled by that aggregate
|
|
337
|
-
6. **Handle Custom types recursively** - Subfields can themselves be Custom types
|
|
338
|
-
7. **Make it idempotent** - Running twice should produce the same output
|
|
339
|
-
`;
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
export const meta = {
|
|
2
|
-
name: 'connect-slices',
|
|
3
|
-
description: 'Connect slices with flows to show how data moves through the system',
|
|
4
|
-
};
|
|
5
|
-
export const content = `
|
|
6
|
-
# Connecting Slices
|
|
7
|
-
|
|
8
|
-
After creating slices, you need to connect them with flows to show how data moves through the system. This creates the complete picture of your event model.
|
|
9
|
-
|
|
10
|
-
## The Data Flow Pattern
|
|
11
|
-
|
|
12
|
-
In event modeling, data flows in a specific pattern:
|
|
13
|
-
|
|
14
|
-
\`\`\`
|
|
15
|
-
[State-Change Slice] [State-View Slice] [Next Slice]
|
|
16
|
-
|
|
17
|
-
Screen ReadModel Screen
|
|
18
|
-
| ^ | ^
|
|
19
|
-
v | v |
|
|
20
|
-
Command | - - - - - - - - - - - - - +
|
|
21
|
-
| | OR
|
|
22
|
-
v | Processor
|
|
23
|
-
Event - - - - - - - - - - - - - - - + ^
|
|
24
|
-
|
|
|
25
|
-
+ - - - - - - - - - - -+
|
|
26
|
-
\`\`\`
|
|
27
|
-
|
|
28
|
-
**Key flows:**
|
|
29
|
-
1. **Event → ReadModel**: Events project their data into read models
|
|
30
|
-
2. **ReadModel → Screen**: Read models provide data to screens (for user viewing)
|
|
31
|
-
3. **ReadModel → Processor**: Read models provide data to processors (for automation)
|
|
32
|
-
|
|
33
|
-
Within-slice flows (Screen→Command, Command→Event, Processor→Command) are created automatically when you create a slice.
|
|
34
|
-
|
|
35
|
-
## Command Syntax
|
|
36
|
-
|
|
37
|
-
\`\`\`bash
|
|
38
|
-
eventmodeler create flow --from "<source>" --to "<target>"
|
|
39
|
-
\`\`\`
|
|
40
|
-
|
|
41
|
-
## Valid Flow Combinations
|
|
42
|
-
|
|
43
|
-
| Source | Target | Use Case |
|
|
44
|
-
|--------|--------|----------|
|
|
45
|
-
| Event | ReadModel | Project event data into a view |
|
|
46
|
-
| ReadModel | Screen | Provide data for user display |
|
|
47
|
-
| ReadModel | Processor | Provide data for automation logic |
|
|
48
|
-
|
|
49
|
-
## Workflow: Connecting a Complete Model
|
|
50
|
-
|
|
51
|
-
### 1. Create Your Slices First
|
|
52
|
-
|
|
53
|
-
Build each slice by adding fields to its elements and connecting them with internal flows.
|
|
54
|
-
|
|
55
|
-
### 2. Connect Events to Read Models
|
|
56
|
-
|
|
57
|
-
Events project their data into read models:
|
|
58
|
-
|
|
59
|
-
\`\`\`bash
|
|
60
|
-
# OrderPlaced feeds into OrderStatus view
|
|
61
|
-
eventmodeler create flow --from "OrderPlaced" --to "OrderStatus"
|
|
62
|
-
|
|
63
|
-
# OrderFulfilled also updates OrderStatus view
|
|
64
|
-
eventmodeler create flow --from "OrderFulfilled" --to "OrderStatus"
|
|
65
|
-
\`\`\`
|
|
66
|
-
|
|
67
|
-
### 3. Connect Events to Automation Read Models
|
|
68
|
-
|
|
69
|
-
Automation slices have their own internal read model. Connect external events to feed it:
|
|
70
|
-
|
|
71
|
-
\`\`\`bash
|
|
72
|
-
# OrderPlaced feeds the automation's read model
|
|
73
|
-
eventmodeler create flow --from "OrderPlaced" --to "OrderReadyToFulfill"
|
|
74
|
-
|
|
75
|
-
# PaymentReceived also feeds the automation's read model
|
|
76
|
-
eventmodeler create flow --from "PaymentReceived" --to "OrderReadyToFulfill"
|
|
77
|
-
\`\`\`
|
|
78
|
-
|
|
79
|
-
Note: The flow from ReadModel → Processor is internal to the automation slice and created automatically.
|
|
80
|
-
|
|
81
|
-
### 4. Connect Read Models to Screens
|
|
82
|
-
|
|
83
|
-
State-view read models provide data to screens:
|
|
84
|
-
|
|
85
|
-
\`\`\`bash
|
|
86
|
-
# OrderStatus provides data to a screen in another slice
|
|
87
|
-
eventmodeler create flow --from "OrderStatus" --to "Order Details Screen"
|
|
88
|
-
\`\`\`
|
|
89
|
-
|
|
90
|
-
### 5. Set Up Field Mappings
|
|
91
|
-
|
|
92
|
-
After creating flows, map the fields:
|
|
93
|
-
|
|
94
|
-
\`\`\`bash
|
|
95
|
-
eventmodeler map fields --from "OrderPlaced" --to "OrderStatus" '[
|
|
96
|
-
{"from": "orderId", "to": "orderId"},
|
|
97
|
-
{"from": "customerId", "to": "customerId"},
|
|
98
|
-
{"from": "placedAt", "to": "placedAt"}
|
|
99
|
-
]'
|
|
100
|
-
\`\`\`
|
|
101
|
-
|
|
102
|
-
### 6. Verify Completeness
|
|
103
|
-
|
|
104
|
-
Check that all flows have proper field mappings:
|
|
105
|
-
|
|
106
|
-
\`\`\`bash
|
|
107
|
-
eventmodeler show model-completeness
|
|
108
|
-
eventmodeler show completeness "OrderStatus"
|
|
109
|
-
\`\`\`
|
|
110
|
-
|
|
111
|
-
## Handling Duplicate Names with IDs
|
|
112
|
-
|
|
113
|
-
When multiple elements share the same name (e.g., linked copies of events or read models), you'll need to use element IDs instead of names.
|
|
114
|
-
|
|
115
|
-
### Finding Element IDs
|
|
116
|
-
|
|
117
|
-
\`\`\`bash
|
|
118
|
-
# List events with their IDs
|
|
119
|
-
eventmodeler list 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}]
|
|
123
|
-
\`\`\`
|
|
124
|
-
|
|
125
|
-
### Using IDs in Commands
|
|
126
|
-
|
|
127
|
-
Prefix the ID (or ID prefix) with \`id:\`:
|
|
128
|
-
|
|
129
|
-
\`\`\`bash
|
|
130
|
-
# Create flow using ID prefix (first 8 characters is usually enough)
|
|
131
|
-
eventmodeler create flow --from "id:abc12345" --to "OrderStatus"
|
|
132
|
-
|
|
133
|
-
# Map fields using ID prefix
|
|
134
|
-
eventmodeler map fields --from "id:abc12345" --to "OrderStatus" '[
|
|
135
|
-
{"from": "orderId", "to": "orderId"}
|
|
136
|
-
]'
|
|
137
|
-
\`\`\`
|
|
138
|
-
|
|
139
|
-
### When the CLI Finds Duplicates
|
|
140
|
-
|
|
141
|
-
If you use a name that matches multiple elements, the CLI will show you the options:
|
|
142
|
-
|
|
143
|
-
\`\`\`bash
|
|
144
|
-
eventmodeler create flow --from "OrderPlaced" --to "OrderStatus"
|
|
145
|
-
# Error: Multiple events found with name "OrderPlaced"
|
|
146
|
-
# Please specify using the element ID:
|
|
147
|
-
# - "OrderPlaced" (id: abc12345-1234-5678-9abc-def012345678)
|
|
148
|
-
# - "OrderPlaced" (id: def67890-1234-5678-9abc-def012345678)
|
|
149
|
-
#
|
|
150
|
-
# Usage: eventmodeler create flow --from "id:abc12345" --to "OrderStatus"
|
|
151
|
-
\`\`\`
|
|
152
|
-
|
|
153
|
-
## Common Patterns
|
|
154
|
-
|
|
155
|
-
### Pattern 1: Event Sourced View
|
|
156
|
-
Multiple events feed into one read model:
|
|
157
|
-
|
|
158
|
-
\`\`\`bash
|
|
159
|
-
eventmodeler create flow --from "OrderPlaced" --to "OrderSummary"
|
|
160
|
-
eventmodeler create flow --from "OrderShipped" --to "OrderSummary"
|
|
161
|
-
eventmodeler create flow --from "OrderDelivered" --to "OrderSummary"
|
|
162
|
-
\`\`\`
|
|
163
|
-
|
|
164
|
-
### Pattern 2: Automation Triggered by Events
|
|
165
|
-
Events feed an automation slice's internal read model:
|
|
166
|
-
|
|
167
|
-
\`\`\`bash
|
|
168
|
-
eventmodeler create flow --from "OrderPlaced" --to "OrderReadyForShipment"
|
|
169
|
-
eventmodeler create flow --from "PaymentReceived" --to "OrderReadyForShipment"
|
|
170
|
-
\`\`\`
|
|
171
|
-
|
|
172
|
-
### Pattern 3: User Dashboard
|
|
173
|
-
A read model feeds a screen for user viewing:
|
|
174
|
-
|
|
175
|
-
\`\`\`bash
|
|
176
|
-
eventmodeler create flow --from "UserDashboard" --to "Dashboard Screen"
|
|
177
|
-
\`\`\`
|
|
178
|
-
|
|
179
|
-
## Error Handling
|
|
180
|
-
|
|
181
|
-
The CLI prevents invalid flows:
|
|
182
|
-
|
|
183
|
-
\`\`\`bash
|
|
184
|
-
# This will error - can't go directly from Event to Screen
|
|
185
|
-
eventmodeler create flow --from "OrderPlaced" --to "Checkout Screen"
|
|
186
|
-
# Error: Cannot create flow directly from Event to Screen.
|
|
187
|
-
# Events flow to ReadModels, which then flow to Screens.
|
|
188
|
-
|
|
189
|
-
# This will error - can't go from ReadModel to ReadModel
|
|
190
|
-
eventmodeler create flow --from "OrderStatus" --to "CustomerProfile"
|
|
191
|
-
# Error: Cannot create flow from ReadModel to ReadModel.
|
|
192
|
-
\`\`\`
|
|
193
|
-
|
|
194
|
-
## Best Practices
|
|
195
|
-
|
|
196
|
-
1. **Create all slices first** - Easier to see the full picture before connecting
|
|
197
|
-
2. **Connect events to read models** - Every event should feed at least one read model
|
|
198
|
-
3. **Think about what triggers what** - Automation slices need data from read models
|
|
199
|
-
4. **Map fields after creating flows** - Use \`map fields\` to complete the data flow
|
|
200
|
-
5. **Verify completeness** - Run \`show model-completeness\` to find missing mappings
|
|
201
|
-
6. **Use IDs for duplicates** - When names aren't unique, use \`id:\` prefix with element IDs
|
|
202
|
-
`;
|