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.
Files changed (73) hide show
  1. package/dist/index.js +7133 -34
  2. package/package.json +5 -4
  3. package/dist/api/client-config.js +0 -10
  4. package/dist/api/generated/client/client.gen.js +0 -235
  5. package/dist/api/generated/client/index.js +0 -6
  6. package/dist/api/generated/client/types.gen.js +0 -2
  7. package/dist/api/generated/client/utils.gen.js +0 -228
  8. package/dist/api/generated/client.gen.js +0 -4
  9. package/dist/api/generated/core/auth.gen.js +0 -14
  10. package/dist/api/generated/core/bodySerializer.gen.js +0 -57
  11. package/dist/api/generated/core/params.gen.js +0 -100
  12. package/dist/api/generated/core/pathSerializer.gen.js +0 -106
  13. package/dist/api/generated/core/queryKeySerializer.gen.js +0 -92
  14. package/dist/api/generated/core/serverSentEvents.gen.js +0 -133
  15. package/dist/api/generated/core/types.gen.js +0 -2
  16. package/dist/api/generated/core/utils.gen.js +0 -87
  17. package/dist/api/generated/index.js +0 -2
  18. package/dist/api/generated/sdk.gen.js +0 -4222
  19. package/dist/api/generated/types.gen.js +0 -2
  20. package/dist/api/generated/zod.gen.js +0 -7217
  21. package/dist/commands/add.js +0 -315
  22. package/dist/commands/auth.js +0 -14
  23. package/dist/commands/create.js +0 -192
  24. package/dist/commands/design.js +0 -108
  25. package/dist/commands/guide.js +0 -15
  26. package/dist/commands/init.js +0 -21
  27. package/dist/commands/list-schemas.js +0 -177
  28. package/dist/commands/list.js +0 -39
  29. package/dist/commands/loop.js +0 -101
  30. package/dist/commands/map.js +0 -40
  31. package/dist/commands/mark.js +0 -27
  32. package/dist/commands/move.js +0 -35
  33. package/dist/commands/remove.js +0 -170
  34. package/dist/commands/rename.js +0 -53
  35. package/dist/commands/resize.js +0 -30
  36. package/dist/commands/search.js +0 -14
  37. package/dist/commands/set.js +0 -199
  38. package/dist/commands/show-schemas.js +0 -259
  39. package/dist/commands/show.js +0 -56
  40. package/dist/commands/summary.js +0 -13
  41. package/dist/commands/update.js +0 -240
  42. package/dist/lib/auth.js +0 -331
  43. package/dist/lib/config.js +0 -80
  44. package/dist/lib/excalidraw-schema.js +0 -66
  45. package/dist/lib/globals.js +0 -8
  46. package/dist/lib/model.js +0 -11
  47. package/dist/lib/project-config.js +0 -103
  48. package/dist/lib/resolve.js +0 -59
  49. package/dist/lib/scenario.js +0 -15
  50. package/dist/slices/add-scenario/index.js +0 -103
  51. package/dist/slices/guide/guides/codegen.js +0 -339
  52. package/dist/slices/guide/guides/connect-slices.js +0 -202
  53. package/dist/slices/guide/guides/create-slices.js +0 -273
  54. package/dist/slices/guide/guides/explore.js +0 -238
  55. package/dist/slices/guide/guides/information-flow.js +0 -304
  56. package/dist/slices/guide/guides/scenarios.js +0 -214
  57. package/dist/slices/guide/index.js +0 -40
  58. package/dist/slices/help/index.js +0 -96
  59. package/dist/slices/help/topics/build-codegen.js +0 -109
  60. package/dist/slices/help/topics/build-slice.js +0 -147
  61. package/dist/slices/help/topics/check-completeness.js +0 -57
  62. package/dist/slices/help/topics/connect-slices.js +0 -99
  63. package/dist/slices/help/topics/explore-model.js +0 -112
  64. package/dist/slices/help/topics/json-reference.js +0 -188
  65. package/dist/slices/help/topics/linked-copies.js +0 -89
  66. package/dist/slices/help/topics/manipulate-canvas.js +0 -150
  67. package/dist/slices/help/topics/write-scenarios.js +0 -162
  68. package/dist/slices/init/index.js +0 -86
  69. package/dist/slices/init/loop.js +0 -60
  70. package/dist/slices/login/index.js +0 -20
  71. package/dist/slices/logout/index.js +0 -14
  72. package/dist/slices/open-app/index.js +0 -36
  73. 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
- `;