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/slices/git/index.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
function isGitRepo() {
|
|
5
|
-
try {
|
|
6
|
-
execSync('git rev-parse --git-dir', { stdio: 'pipe' });
|
|
7
|
-
return true;
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
function getEventmodelerCommand() {
|
|
14
|
-
// Check if running from npm/npx
|
|
15
|
-
try {
|
|
16
|
-
execSync('which eventmodeler', { stdio: 'pipe' });
|
|
17
|
-
return 'eventmodeler';
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
// Fall back to npx
|
|
21
|
-
return 'npx eventmodeler';
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function configureGitDrivers(scope) {
|
|
25
|
-
const cmd = getEventmodelerCommand();
|
|
26
|
-
// Configure merge driver
|
|
27
|
-
execSync(`git config ${scope} merge.eventmodel.name "Event Model semantic merge driver"`, { stdio: 'pipe' });
|
|
28
|
-
execSync(`git config ${scope} merge.eventmodel.driver "${cmd} merge --base %O --ours %A --theirs %B --output %A"`, { stdio: 'pipe' });
|
|
29
|
-
// Configure diff driver - use text format for human-readable diffs
|
|
30
|
-
execSync(`git config ${scope} diff.eventmodel.command "${cmd} diff"`, { stdio: 'pipe' });
|
|
31
|
-
console.log(`Git drivers configured (${scope === '--global' ? 'globally' : 'locally'}).`);
|
|
32
|
-
}
|
|
33
|
-
function addGitAttributes() {
|
|
34
|
-
const gitattributesPath = path.join(process.cwd(), '.gitattributes');
|
|
35
|
-
const lines = [
|
|
36
|
-
'*.eventmodel merge=eventmodel',
|
|
37
|
-
'*.eventmodel diff=eventmodel',
|
|
38
|
-
];
|
|
39
|
-
let existingContent = '';
|
|
40
|
-
if (fs.existsSync(gitattributesPath)) {
|
|
41
|
-
existingContent = fs.readFileSync(gitattributesPath, 'utf-8');
|
|
42
|
-
}
|
|
43
|
-
const linesToAdd = [];
|
|
44
|
-
for (const line of lines) {
|
|
45
|
-
if (!existingContent.includes(line)) {
|
|
46
|
-
linesToAdd.push(line);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (linesToAdd.length === 0) {
|
|
50
|
-
console.log('.gitattributes already configured.');
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
const newContent = existingContent.trim()
|
|
54
|
-
? existingContent.trim() + '\n' + linesToAdd.join('\n') + '\n'
|
|
55
|
-
: linesToAdd.join('\n') + '\n';
|
|
56
|
-
fs.writeFileSync(gitattributesPath, newContent);
|
|
57
|
-
console.log('.gitattributes updated with eventmodel configuration.');
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
export function gitSetup(global) {
|
|
61
|
-
console.log('Setting up git integration for Event Modeler...\n');
|
|
62
|
-
if (!isGitRepo() && !global) {
|
|
63
|
-
console.error('Error: Not in a git repository.');
|
|
64
|
-
console.error('Run with --global to configure globally, or run from within a git repo.');
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
const scope = global ? '--global' : '--local';
|
|
68
|
-
try {
|
|
69
|
-
configureGitDrivers(scope);
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
console.error(`Error configuring git: ${err.message}`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
if (!global) {
|
|
76
|
-
addGitAttributes();
|
|
77
|
-
}
|
|
78
|
-
console.log('\nSetup complete! Git will now use semantic diff and merge for .eventmodel files.');
|
|
79
|
-
if (global) {
|
|
80
|
-
console.log('\nTo enable for a specific repository, add to .gitattributes:');
|
|
81
|
-
console.log(' *.eventmodel merge=eventmodel');
|
|
82
|
-
console.log(' *.eventmodel diff=eventmodel');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
export function gitStatus() {
|
|
86
|
-
console.log('Git Integration Status\n');
|
|
87
|
-
// Check if in a git repo
|
|
88
|
-
if (!isGitRepo()) {
|
|
89
|
-
console.log('Git repository: No');
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
console.log('Git repository: Yes');
|
|
93
|
-
// Check merge driver
|
|
94
|
-
try {
|
|
95
|
-
const mergeDriver = execSync('git config --get merge.eventmodel.driver', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
96
|
-
console.log(`Merge driver: Configured`);
|
|
97
|
-
console.log(` ${mergeDriver}`);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
console.log('Merge driver: Not configured');
|
|
101
|
-
}
|
|
102
|
-
// Check diff driver
|
|
103
|
-
try {
|
|
104
|
-
const diffDriver = execSync('git config --get diff.eventmodel.command', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
105
|
-
console.log(`Diff driver: Configured`);
|
|
106
|
-
console.log(` ${diffDriver}`);
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
console.log('Diff driver: Not configured');
|
|
110
|
-
}
|
|
111
|
-
// Check .gitattributes
|
|
112
|
-
const gitattributesPath = path.join(process.cwd(), '.gitattributes');
|
|
113
|
-
if (fs.existsSync(gitattributesPath)) {
|
|
114
|
-
const content = fs.readFileSync(gitattributesPath, 'utf-8');
|
|
115
|
-
const hasMerge = content.includes('*.eventmodel merge=eventmodel');
|
|
116
|
-
const hasDiff = content.includes('*.eventmodel diff=eventmodel');
|
|
117
|
-
console.log(`\n.gitattributes:`);
|
|
118
|
-
console.log(` merge=eventmodel: ${hasMerge ? 'Yes' : 'No'}`);
|
|
119
|
-
console.log(` diff=eventmodel: ${hasDiff ? 'Yes' : 'No'}`);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
console.log('\n.gitattributes: Not found');
|
|
123
|
-
}
|
|
124
|
-
console.log('\nRun "eventmodeler git setup" to configure git integration.');
|
|
125
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const meta: {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
};
|
|
5
|
-
export declare const content = "\n# Building Code Generators for Event Models\n\nThe `eventmodeler codegen` commands output structured JSON that code generators can consume:\n- `eventmodeler codegen slice \"<slice-name>\"` for per-slice generation\n- `eventmodeler codegen events [--chapter \"<chapter-name>\"]` for deduplicated event scaffolding across slices\n\n## The Codegen Pipeline\n\nPer-slice pipeline:\n\n```\neventmodeler codegen slice \"Place Order\" | your-generator > output-files\n```\n\nEvents-only pipeline:\n\n```\neventmodeler codegen events --chapter \"Register Products\" | your-events-generator > events.kt\n```\n\nYour generator receives JSON on stdin and produces code files.\n\n## Understanding the Input\n\nRun codegen to see what you'll receive:\n\n```bash\neventmodeler codegen slice \"Place Order\"\n```\n\n### Top-Level Structure\n\n```json\n{\n \"sliceType\": \"STATE_CHANGE | STATE_VIEW | AUTOMATION\",\n \"slice\": { \"id\": \"...\", \"name\": \"Place Order\" },\n \"chapter\": { \"name\": \"...\", \"hierarchy\": [\"Parent\", \"Child\"] },\n \"elements\": { ... },\n \"inboundDependencies\": [ ... ],\n \"internalFlows\": [ ... ],\n \"scenarios\": [ ... ]\n}\n```\n\n### Events-Only Structure (`codegen events`)\n\n```json\n{\n \"chapter\": { \"name\": \"Register Products\", \"parent\": { \"name\": \"Configuration\" } },\n \"sliceCount\": 4,\n \"eventCount\": 9,\n \"slices\": [\n { \"id\": \"...\", \"name\": \"Place Product\", \"sliceType\": \"STATE_CHANGE\" }\n ],\n \"events\": [\n {\n \"id\": \"...\",\n \"name\": \"ProductRegistered\",\n \"fields\": [ ... ],\n \"aggregate\": \"Product\",\n \"sourceSlices\": [\"Place Product\", \"Import Product\"]\n }\n ]\n}\n```\n\nUse `codegen events` when generating shared event artifacts in one pass (for a chapter or entire model).\n\n### Slice Types and What to Generate\n\n| sliceType | Components | Generate |\n|-----------|------------|----------|\n| `STATE_CHANGE` | Screen, Command, Event | API endpoint, command handler, event, tests |\n| `STATE_VIEW` | ReadModel | Projection/denormalizer, query endpoint |\n| `AUTOMATION` | Processor, ReadModel, Command, Event | Event handler, saga/process manager |\n\n## Building a Generator: Step by Step\n\n### 1. Parse the Input\n\n```typescript\n// generator.ts\nimport { stdin } from 'process';\n\ninterface CodegenInput {\n sliceType: 'STATE_CHANGE' | 'STATE_VIEW' | 'AUTOMATION';\n slice: { id: string; name: string };\n elements: {\n commands: Array<{ name: string; fields: Field[] }>;\n events: Array<{ name: string; fields: Field[]; aggregate?: string }>;\n readModels: Array<{ name: string; fields: Field[] }>;\n screens: Array<{ name: string; fields: Field[] }>;\n processors: Array<{ name: string }>;\n };\n inboundDependencies: InboundDependency[];\n internalFlows: Flow[];\n scenarios: Scenario[];\n}\n\nasync function readInput(): Promise<CodegenInput> {\n const chunks: Buffer[] = [];\n for await (const chunk of stdin) {\n chunks.push(chunk);\n }\n return JSON.parse(Buffer.concat(chunks).toString());\n}\n```\n\n### 2. Route by Slice Type\n\n```typescript\nasync function main() {\n const input = await readInput();\n\n switch (input.sliceType) {\n case 'STATE_CHANGE':\n generateStateChangeSlice(input);\n break;\n case 'STATE_VIEW':\n generateStateViewSlice(input);\n break;\n case 'AUTOMATION':\n generateAutomationSlice(input);\n break;\n }\n}\n```\n\n### 3. Generate from Elements\n\n**Commands \u2192 DTOs/Request types:**\n```typescript\nfunction generateCommand(cmd: Command): string {\n const fields = cmd.fields\n .map(f => ` ${f.name}: ${mapType(f.type)}${f.optional ? ' | null' : ''};`)\n .join('\\n');\n\n return `export interface ${cmd.name}Command {\\n${fields}\\n}`;\n}\n```\n\n**Events \u2192 Event types with metadata:**\n```typescript\nfunction generateEvent(evt: Event): string {\n const fields = evt.fields.map(f => {\n const generated = f.generated ? ' // generated' : '';\n return ` ${f.name}: ${mapType(f.type)};${generated}`;\n }).join('\\n');\n\n return `export interface ${evt.name}Event {\\n type: '${evt.name}';\\n${fields}\\n}`;\n}\n```\n\n**ReadModels \u2192 Projection types:**\n```typescript\nfunction generateReadModel(rm: ReadModel): string {\n const fields = rm.fields\n .map(f => ` ${f.name}: ${mapType(f.type)}${f.optional ? ' | null' : ''};`)\n .join('\\n');\n\n return `export interface ${rm.name} {\\n${fields}\\n}`;\n}\n```\n\n### 4. Use Field Mappings for Projections\n\nThe `inboundDependencies` show how external events map to read model fields:\n\n```typescript\nfunction generateProjection(input: CodegenInput): string {\n const rm = input.elements.readModels[0];\n const handlers: string[] = [];\n\n for (const dep of input.inboundDependencies) {\n if (dep.target.name === rm.name) {\n const mappings = dep.fieldMappings\n .map(m => ` state.${m.targetFieldName} = event.${m.sourceFieldName};`)\n .join('\\n');\n\n handlers.push(`\n on${dep.source.name}(state: ${rm.name}, event: ${dep.source.name}Event) {\n${mappings}\n }`);\n }\n }\n\n return `export class ${rm.name}Projection {\\n${handlers.join('\\n')}\\n}`;\n}\n```\n\n### 5. Generate Tests from Scenarios\n\nScenarios are BDD specs that become tests. There are two scenario patterns:\n\n**State-Change Scenarios** (whenCommand \u2192 then events/error):\n- `whenCommand`: The command being tested\n- `then.type: 'events'`: Expected events produced\n- `then.type: 'error'`: Expected error\n\n**Automation Scenarios** (whenEvents \u2192 then command):\n- `whenEvents`: The event(s) that trigger the processor\n- `then.type: 'command'`: The command the processor should issue\n\n```typescript\nfunction generateTests(input: CodegenInput): string {\n return input.scenarios.map(scenario => {\n const givenSetup = scenario.given\n .map(g => ` await given(new ${g.eventName}(${JSON.stringify(g.fieldValues)}));`)\n .join('\\n');\n\n // State-change scenarios: when is a command\n let whenAction = '';\n if (scenario.whenCommand) {\n whenAction = ` await when(new ${scenario.whenCommand.commandName}(${JSON.stringify(scenario.whenCommand.fieldValues)}));`;\n }\n // Automation scenarios: when is event(s)\n if (scenario.whenEvents?.length > 0) {\n whenAction = scenario.whenEvents\n .map(e => ` await whenEvent(new ${e.eventName}(${JSON.stringify(e.fieldValues)}));`)\n .join('\\n');\n }\n\n let thenAssertion: string;\n if (scenario.then.type === 'events') {\n const expected = scenario.then.expectedEvents!\n .map(e => `new ${e.eventName}(${JSON.stringify(e.fieldValues)})`)\n .join(', ');\n thenAssertion = ` await thenExpect([${expected}]);`;\n } else if (scenario.then.type === 'command') {\n const cmd = scenario.then.expectedCommand!;\n thenAssertion = ` await thenExpectCommand(new ${cmd.commandName}(${JSON.stringify(cmd.fieldValues)}));`;\n } else if (scenario.then.type === 'error') {\n thenAssertion = ` await thenExpectError('${scenario.then.errorType}', '${scenario.then.errorMessage}');`;\n } else {\n thenAssertion = ` await thenExpectState(${JSON.stringify(scenario.then.expectedFieldValues)});`;\n }\n\n return `\n test('${scenario.name}', async () => {\n${givenSetup}\n${whenAction}\n${thenAssertion}\n });`;\n }).join('\\n');\n}\n```\n\n## Type Mapping\n\nMap event model types to your target language:\n\n```typescript\nfunction mapType(fieldType: string, isList?: boolean): string {\n const baseType = {\n 'UUID': 'string',\n 'String': 'string',\n 'Int': 'number',\n 'Long': 'number',\n 'Double': 'number',\n 'Decimal': 'Decimal', // or 'number' or 'BigNumber'\n 'Boolean': 'boolean',\n 'Date': 'Date',\n 'DateTime': 'Date',\n 'Custom': 'object', // handle subfields separately\n }[fieldType] ?? 'unknown';\n\n return isList ? `${baseType}[]` : baseType;\n}\n```\n\nFor Custom types with subfields, generate inline or separate types:\n\n```typescript\nfunction generateFieldType(field: Field): string {\n if (field.type === 'Custom' && field.subfields) {\n const nested = field.subfields\n .map(sf => `${sf.name}: ${mapType(sf.type)}`)\n .join('; ');\n const type = `{ ${nested} }`;\n return field.list ? `Array<${type}>` : type;\n }\n return mapType(field.type, field.list);\n}\n```\n\n## Handling Aggregates\n\nEvents include their aggregate name when inside one:\n\n```typescript\nfunction generateEventHandler(input: CodegenInput): string {\n const event = input.elements.events[0];\n const aggregate = event.aggregate;\n\n if (aggregate) {\n return `\nexport class ${aggregate}Aggregate {\n apply(event: ${event.name}Event) {\n // Update aggregate state from event\n }\n}`;\n }\n // ...\n}\n```\n\n## Running Your Generator\n\n```bash\n# Single slice\neventmodeler codegen slice \"Place Order\" | node generator.js\n\n# All slices (bash loop)\nfor slice in $(eventmodeler list slices --format json | 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\n eventmodeler codegen slice \"$slice\" | node generator.js\ndone\n\n# Chapter-scoped events scaffolding\neventmodeler codegen events --chapter \"Register Products\" | node generate-events.js\n\n# Whole-model events scaffolding\neventmodeler codegen events | node generate-events.js\n```\n\n## Best Practices\n\n1. **Handle all slice types** - Your generator should handle STATE_CHANGE, STATE_VIEW, and AUTOMATION\n2. **Use field mappings** - They define the projection logic between events and read models\n3. **Generate tests from scenarios** - Scenarios are executable specifications\n4. **Respect field attributes** - `generated` fields don't need input, `optional` fields can be null\n5. **Use aggregate info** - Events in aggregates should be handled by that aggregate\n6. **Handle Custom types recursively** - Subfields can themselves be Custom types\n7. **Make it idempotent** - Running twice should produce the same output\n";
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const meta: {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
};
|
|
5
|
-
export declare const content = "\n# Connecting Slices\n\nAfter 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.\n\n## The Data Flow Pattern\n\nIn event modeling, data flows in a specific pattern:\n\n```\n[State-Change Slice] [State-View Slice] [Next Slice]\n\n Screen ReadModel Screen\n | ^ | ^\n v | v |\n Command | - - - - - - - - - - - - - +\n | | OR\n v | Processor\n Event - - - - - - - - - - - - - - - + ^\n |\n + - - - - - - - - - - -+\n```\n\n**Key flows:**\n1. **Event \u2192 ReadModel**: Events project their data into read models\n2. **ReadModel \u2192 Screen**: Read models provide data to screens (for user viewing)\n3. **ReadModel \u2192 Processor**: Read models provide data to processors (for automation)\n\nWithin-slice flows (Screen\u2192Command, Command\u2192Event, Processor\u2192Command) are created automatically when you create a slice.\n\n## Command Syntax\n\n```bash\neventmodeler create flow --from \"<source>\" --to \"<target>\"\n```\n\n## Valid Flow Combinations\n\n| Source | Target | Use Case |\n|--------|--------|----------|\n| Event | ReadModel | Project event data into a view |\n| ReadModel | Screen | Provide data for user display |\n| ReadModel | Processor | Provide data for automation logic |\n\n## Workflow: Connecting a Complete Model\n\n### 1. Create Your Slices First\n\n```bash\n# User places an order\neventmodeler create state-change-slice --xml '<state-change-slice name=\"Place Order\">\n <screen name=\"Checkout\">...</screen>\n <command name=\"PlaceOrder\">...</command>\n <event name=\"OrderPlaced\">...</event>\n</state-change-slice>'\n\n# View for order status\neventmodeler create state-view-slice --xml '<state-view-slice name=\"View Order Status\" after=\"Place Order\">\n <read-model name=\"OrderStatus\">...</read-model>\n</state-view-slice>'\n\n# System automatically fulfills order (includes its own read model)\neventmodeler create automation-slice --xml '<automation-slice name=\"Auto Fulfill\" after=\"View Order Status\">\n <read-model name=\"OrderReadyToFulfill\">\n <field name=\"orderId\" type=\"UUID\"/>\n <field name=\"isPaid\" type=\"Boolean\"/>\n </read-model>\n <processor name=\"Fulfillment Processor\"/>\n <command name=\"FulfillOrder\">...</command>\n <event name=\"OrderFulfilled\">...</event>\n</automation-slice>'\n```\n\n### 2. Connect Events to Read Models\n\nEvents project their data into read models:\n\n```bash\n# OrderPlaced feeds into OrderStatus view\neventmodeler create flow --from \"OrderPlaced\" --to \"OrderStatus\"\n\n# OrderFulfilled also updates OrderStatus view\neventmodeler create flow --from \"OrderFulfilled\" --to \"OrderStatus\"\n```\n\n### 3. Connect Events to Automation Read Models\n\nAutomation slices have their own internal read model. Connect external events to feed it:\n\n```bash\n# OrderPlaced feeds the automation's read model\neventmodeler create flow --from \"OrderPlaced\" --to \"OrderReadyToFulfill\"\n\n# PaymentReceived also feeds the automation's read model\neventmodeler create flow --from \"PaymentReceived\" --to \"OrderReadyToFulfill\"\n```\n\nNote: The flow from ReadModel \u2192 Processor is internal to the automation slice and created automatically.\n\n### 4. Connect Read Models to Screens\n\nState-view read models provide data to screens:\n\n```bash\n# OrderStatus provides data to a screen in another slice\neventmodeler create flow --from \"OrderStatus\" --to \"Order Details Screen\"\n```\n\n### 5. Set Up Field Mappings\n\nAfter creating flows, map the fields:\n\n```bash\neventmodeler map fields --flow \"OrderPlaced\u2192OrderStatus\" --xml '\n <mapping from=\"orderId\" to=\"orderId\"/>\n <mapping from=\"customerId\" to=\"customerId\"/>\n <mapping from=\"placedAt\" to=\"placedAt\"/>\n'\n```\n\n### 6. Verify Completeness\n\nCheck that all flows have proper field mappings:\n\n```bash\neventmodeler show model-completeness\neventmodeler show completeness \"OrderStatus\"\n```\n\n## Handling Duplicate Names with IDs\n\nWhen 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.\n\n### Finding Element IDs\n\n```bash\n# List events with their IDs\neventmodeler list events\n# Output:\n# <events>\n# <event id=\"abc12345-...\" name=\"OrderPlaced\" fields=\"4\"/>\n# <event id=\"def67890-...\" name=\"OrderPlaced\" fields=\"4\"/> <!-- linked copy -->\n# </events>\n```\n\n### Using IDs in Commands\n\nPrefix the ID (or ID prefix) with `id:`:\n\n```bash\n# Create flow using ID prefix (first 8 characters is usually enough)\neventmodeler create flow --from \"id:abc12345\" --to \"OrderStatus\"\n\n# Map fields using ID prefix\neventmodeler map fields --flow \"id:abc12345\u2192OrderStatus\" --xml '\n <mapping from=\"orderId\" to=\"orderId\"/>\n'\n```\n\n### When the CLI Finds Duplicates\n\nIf you use a name that matches multiple elements, the CLI will show you the options:\n\n```bash\neventmodeler create flow --from \"OrderPlaced\" --to \"OrderStatus\"\n# Error: Multiple events found with name \"OrderPlaced\"\n# Please specify using the element ID:\n# - \"OrderPlaced\" (id: abc12345-1234-5678-9abc-def012345678)\n# - \"OrderPlaced\" (id: def67890-1234-5678-9abc-def012345678)\n#\n# Usage: eventmodeler create flow --from \"id:abc12345\" --to \"OrderStatus\"\n```\n\n## Common Patterns\n\n### Pattern 1: Event Sourced View\nMultiple events feed into one read model:\n\n```bash\neventmodeler create flow --from \"OrderPlaced\" --to \"OrderSummary\"\neventmodeler create flow --from \"OrderShipped\" --to \"OrderSummary\"\neventmodeler create flow --from \"OrderDelivered\" --to \"OrderSummary\"\n```\n\n### Pattern 2: Automation Triggered by Events\nEvents feed an automation slice's internal read model:\n\n```bash\neventmodeler create flow --from \"OrderPlaced\" --to \"OrderReadyForShipment\"\neventmodeler create flow --from \"PaymentReceived\" --to \"OrderReadyForShipment\"\n```\n\n### Pattern 3: User Dashboard\nA read model feeds a screen for user viewing:\n\n```bash\neventmodeler create flow --from \"UserDashboard\" --to \"Dashboard Screen\"\n```\n\n## Error Handling\n\nThe CLI prevents invalid flows:\n\n```bash\n# This will error - can't go directly from Event to Screen\neventmodeler create flow --from \"OrderPlaced\" --to \"Checkout Screen\"\n# Error: Cannot create flow directly from Event to Screen.\n# Events flow to ReadModels, which then flow to Screens.\n\n# This will error - can't go from ReadModel to ReadModel\neventmodeler create flow --from \"OrderStatus\" --to \"CustomerProfile\"\n# Error: Cannot create flow from ReadModel to ReadModel.\n```\n\n## Best Practices\n\n1. **Create all slices first** - Easier to see the full picture before connecting\n2. **Connect events to read models** - Every event should feed at least one read model\n3. **Think about what triggers what** - Automation slices need data from read models\n4. **Map fields after creating flows** - Use `map fields` to complete the data flow\n5. **Verify completeness** - Run `show model-completeness` to find missing mappings\n6. **Use IDs for duplicates** - When names aren't unique, use `id:` prefix with element IDs\n";
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const meta: {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
};
|
|
5
|
-
export declare const content = "\n# Creating Slices\n\nThere are three slice types in an event model. Each represents a different pattern of data flow.\n\n---\n\n## State-Change Slices\n\nA state-change slice represents a **user-initiated action** that changes system state:\n\n```\nScreen \u2192 Command \u2192 Event\n```\n\n- **Screen**: The UI where the user initiates the action\n- **Command**: The intent/request to change state\n- **Event**: The fact that state changed\n\n### When to Use\n\nUse state-change slices when:\n- A user performs an action (clicks a button, submits a form)\n- That action changes the state of an entity\n- Examples: \"Place Order\", \"Cancel Subscription\", \"Update Profile\", \"Add Item to Cart\"\n\n### Command Syntax\n\n```bash\neventmodeler create state-change-slice --xml '<state-change-slice name=\"Slice Name\" [after=\"Other Slice\"]>\n <screen name=\"Screen Name\">\n <field name=\"fieldName\" type=\"Type\"/>\n </screen>\n <command name=\"CommandName\">\n <field name=\"fieldName\" type=\"Type\"/>\n </command>\n <event name=\"EventName\">\n <field name=\"fieldName\" type=\"Type\"/>\n </event>\n</state-change-slice>'\n```\n\n### Example: Place Order Slice\n\n```bash\neventmodeler create state-change-slice --xml '<state-change-slice name=\"Place Order\">\n <screen name=\"Checkout Screen\">\n <field name=\"customerId\" type=\"UUID\"/>\n <field name=\"items\" type=\"String\" isUserInput=\"true\"/>\n <field name=\"shippingAddress\" type=\"String\" isUserInput=\"true\"/>\n </screen>\n <command name=\"PlaceOrder\">\n <field name=\"customerId\" type=\"UUID\"/>\n <field name=\"items\" type=\"String\"/>\n <field name=\"shippingAddress\" type=\"String\"/>\n </command>\n <event name=\"OrderPlaced\">\n <field name=\"orderId\" type=\"UUID\" isGenerated=\"true\"/>\n <field name=\"customerId\" type=\"UUID\"/>\n <field name=\"items\" type=\"String\"/>\n <field name=\"shippingAddress\" type=\"String\"/>\n <field name=\"placedAt\" type=\"DateTime\" isGenerated=\"true\"/>\n </event>\n</state-change-slice>'\n```\n\n### Automatic Behavior\n\n1. **Positioning**: Places slice at end, or use `after=\"Slice Name\"` or `before=\"Slice Name\"`\n2. **Field mappings**: Automatically inferred by matching field names between Screen \u2192 Command and Command \u2192 Event\n3. **Flows**: Automatically creates Screen\u2192Command and Command\u2192Event flows\n\n---\n\n## Automation Slices\n\nAn automation slice represents a **system-initiated action**:\n\n```\nReadModel \u2192 Processor \u2192 Command \u2192 Event\n```\n\n- **ReadModel**: The state/data the processor needs to make decisions\n- **Processor**: The system component that decides when and how to act\n- **Command**: The intent/request to change state\n- **Event**: The fact that state changed\n\n### When to Use\n\nUse automation slices when:\n- The system automatically responds to events\n- No direct user interaction triggers the action\n- Examples: \"Auto-assign Reviewer\", \"Send Reminder Email\", \"Calculate Shipping\", \"Process Payment\"\n\n### Command Syntax\n\n```bash\neventmodeler create automation-slice --xml '<automation-slice name=\"Slice Name\" [after=\"Other Slice\"]>\n <read-model name=\"ReadModelName\">\n <field name=\"fieldName\" type=\"Type\"/>\n </read-model>\n <processor name=\"Processor Name\"/>\n <command name=\"CommandName\">\n <field name=\"fieldName\" type=\"Type\"/>\n </command>\n <event name=\"EventName\">\n <field name=\"fieldName\" type=\"Type\"/>\n </event>\n</automation-slice>'\n```\n\n### Example: Auto-Fulfill Order\n\n```bash\neventmodeler create automation-slice --xml '<automation-slice name=\"Auto Fulfill Order\" after=\"Place Order\">\n <read-model name=\"OrderReadyForFulfillment\">\n <field name=\"orderId\" type=\"UUID\"/>\n <field name=\"isPaid\" type=\"Boolean\"/>\n <field name=\"shippingAddress\" type=\"String\"/>\n </read-model>\n <processor name=\"Order Fulfillment Processor\"/>\n <command name=\"FulfillOrder\">\n <field name=\"orderId\" type=\"UUID\"/>\n <field name=\"warehouseId\" type=\"UUID\" isGenerated=\"true\"/>\n </command>\n <event name=\"OrderFulfilled\">\n <field name=\"orderId\" type=\"UUID\"/>\n <field name=\"warehouseId\" type=\"UUID\"/>\n <field name=\"fulfilledAt\" type=\"DateTime\" isGenerated=\"true\"/>\n </event>\n</automation-slice>'\n```\n\n### Automatic Behavior\n\n1. **Positioning**: Places slice at end, or use `after=\"Slice Name\"` or `before=\"Slice Name\"`\n2. **Field mappings**: Automatically inferred by matching field names: ReadModel \u2192 Command, Command \u2192 Event\n3. **Flows**: Automatically creates ReadModel \u2192 Processor, Processor \u2192 Command, Command \u2192 Event\n\n### Connecting Trigger Events\n\nAfter creating the automation slice, connect external events to its read model:\n\n```bash\neventmodeler create flow --from \"OrderPlaced\" --to \"OrderReadyForFulfillment\"\neventmodeler create flow --from \"PaymentReceived\" --to \"OrderReadyForFulfillment\"\n```\n\n### Automation vs State-View Read Models\n\n| Automation ReadModel | State-View ReadModel |\n|---------------------|---------------------|\n| Inside automation slice | Standalone slice |\n| Feeds a Processor | Feeds a Screen |\n| \"What the processor needs\" | \"What the user sees\" |\n| Created with automation-slice | Created with state-view-slice |\n\n---\n\n## State-View Slices\n\nA state-view slice represents a **read model** - a projection of event data for querying:\n\n```\nRead Model (fed by events)\n```\n\n### When to Use\n\nUse state-view slices when:\n- You need to display data to users\n- A processor needs to query current state\n- You want to aggregate data from multiple events\n- Examples: \"Order Summary\", \"User Dashboard\", \"Inventory Levels\", \"Account Balance\"\n\n### Command Syntax\n\n```bash\neventmodeler create state-view-slice --xml '<state-view-slice name=\"Slice Name\" [after=\"Other Slice\"]>\n <read-model name=\"ReadModelName\">\n <field name=\"fieldName\" type=\"Type\"/>\n </read-model>\n</state-view-slice>'\n```\n\n### Example: Order Summary View\n\n```bash\neventmodeler create state-view-slice --xml '<state-view-slice name=\"View Order Summary\" after=\"Place Order\">\n <read-model name=\"OrderSummary\">\n <field name=\"orderId\" type=\"UUID\"/>\n <field name=\"customerId\" type=\"UUID\"/>\n <field name=\"customerName\" type=\"String\"/>\n <field name=\"status\" type=\"String\"/>\n <field name=\"totalAmount\" type=\"Decimal\"/>\n <field name=\"itemCount\" type=\"Int\"/>\n <field name=\"placedAt\" type=\"DateTime\"/>\n <field name=\"lastUpdatedAt\" type=\"DateTime\"/>\n </read-model>\n</state-view-slice>'\n```\n\n### Connecting Events After Creation\n\nThe `create state-view-slice` command only creates the read model. To complete the picture:\n\n1. Create flows from events: `eventmodeler create flow --from \"EventName\" --to \"ReadModelName\"`\n2. Map fields: `eventmodeler map fields --flow \"EventName\u2192ReadModelName\" --xml '...'`\n3. Verify: `eventmodeler show completeness \"ReadModelName\"`\n\n---\n\n## Field Types\n\n`UUID`, `String`, `Int`, `Long`, `Double`, `Decimal`, `Boolean`, `Date`, `DateTime`, `Custom`\n\n## Field Attributes\n\n- `isOptional=\"true\"` - Field may not have a value\n- `isGenerated=\"true\"` - System-generated (e.g., IDs, timestamps), not from user input\n- `isUserInput=\"true\"` - Entered by the user on the screen\n- `isList=\"true\"` - Field is a list/array of the specified type\n\n## Working with Lists and Custom Types\n\n**List of primitives** - use `isList=\"true\"` with any valid type:\n```xml\n<field name=\"tags\" type=\"String\" isList=\"true\"/>\n<field name=\"quantities\" type=\"Int\" isList=\"true\"/>\n```\n\n### Custom Types (Nested Objects)\n\n**CRITICAL: Custom fields MUST contain subfields.** The `type=\"Custom\"` indicates a nested object structure, and the nested `<field>` elements define its properties. A Custom field without subfields is invalid and creates an empty, useless type.\n\n**Custom type with nested fields:**\n```xml\n<field name=\"address\" type=\"Custom\">\n <field name=\"street\" type=\"String\"/>\n <field name=\"city\" type=\"String\"/>\n <field name=\"postalCode\" type=\"String\"/>\n</field>\n```\n\n**List of custom objects** - combine `type=\"Custom\"` with `isList=\"true\"`:\n```xml\n<field name=\"lineItems\" type=\"Custom\" isList=\"true\">\n <field name=\"productId\" type=\"UUID\"/>\n <field name=\"quantity\" type=\"Int\"/>\n <field name=\"unitPrice\" type=\"Decimal\"/>\n</field>\n```\n\n**Nested custom types** - subfields can themselves be Custom types:\n```xml\n<field name=\"customer\" type=\"Custom\">\n <field name=\"customerId\" type=\"UUID\"/>\n <field name=\"name\" type=\"String\"/>\n <field name=\"billingAddress\" type=\"Custom\">\n <field name=\"street\" type=\"String\"/>\n <field name=\"city\" type=\"String\"/>\n <field name=\"country\" type=\"String\"/>\n </field>\n</field>\n```\n\n## Common Mistakes\n\n- **Creating empty Custom fields**: Custom fields MUST have subfields that define their structure.\n - Wrong: `<field name=\"address\" type=\"Custom\"/>`\n - Right: `<field name=\"address\" type=\"Custom\"><field name=\"street\" type=\"String\"/>...</field>`\n\n- **Using \"List\" as a field type**: \"List\" is not a valid type. Use `isList=\"true\"` with a valid type instead.\n - Wrong: `<field name=\"items\" type=\"List\"/>`\n - Right: `<field name=\"items\" type=\"String\" isList=\"true\"/>`\n\n- **Using wrong attribute names**: Always use camelCase attributes.\n - Wrong: `optional=\"true\"`, `generated=\"true\"`, `list=\"true\"`, `user-input=\"true\"`\n - Right: `isOptional=\"true\"`, `isGenerated=\"true\"`, `isList=\"true\"`, `isUserInput=\"true\"`\n\n## Workflow\n\n1. **Understand the action** - What is the user/system trying to do?\n2. **Design the fields** - What information flows through this slice?\n3. **Propose before creating** - Get approval on the design first\n4. **Create the slice** - Run the command\n5. **Verify** - `eventmodeler show slice \"<name>\"`\n6. **Connect** - Create flows to/from other slices as needed\n\n## Best Practices\n\n1. **Get approval first** - Propose the design before creating\n2. **Use meaningful names** - Slice name should describe the action (\"Place Order\" not \"Order Slice\")\n3. **Include generated fields** - Events should have IDs and timestamps\n4. **Match field names** - Same names enable automatic mapping\n5. **Consider what's user input vs system data** - Mark appropriately\n6. **Name automation read models for their purpose** - \"OrderReadyForFulfillment\" not \"OrderData\"\n7. **Name processors descriptively** - \"Order Fulfillment Processor\" not just \"Processor\"\n";
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const meta: {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
};
|
|
5
|
-
export declare const content = "\n# Exploring Event Models with the CLI\n\nThe `eventmodeler` CLI lets you query and explore event models linked to your project.\n\n## Getting an Overview\n\n**Start here** to understand the model:\n\n```bash\neventmodeler summary\n```\nShows statistics: total slices, events, commands, read models, etc.\n\n```bash\neventmodeler list slices\n```\nLists all slices with their status (created, in-progress, blocked, done). Slices represent vertical features.\n\n```bash\neventmodeler list slices --chapter \"<name>\"\n```\nLists only slices within a specific chapter.\n\n```bash\neventmodeler list chapters\n```\nLists chapters - timeline sections that group slices.\n\n## Listing Elements\n\n```bash\neventmodeler list events # All events in the model\neventmodeler list commands # All commands in the model\neventmodeler list readmodels # All read models\neventmodeler list screens # All screens\neventmodeler list processors # All processors\neventmodeler list scenarios # All scenarios\neventmodeler list aggregates # All aggregates (event groupings)\neventmodeler list actors # All actors (user/system roles)\n```\n\nAll `list` commands support `--format xml|json`.\n\n## Searching\n\n```bash\neventmodeler search \"<term>\"\n```\nSearches across all entity names: slices, events, commands, read models, aggregates, and actors.\n\n## Drilling Into Details\n\n### Show a Slice\n```bash\neventmodeler show slice \"<name>\"\n```\nReturns XML with:\n- All components in the slice (screens, commands, events, read-models, processors)\n- **Inline flow annotations** on each component showing `<flows-from>` and `<flows-to>` connections\n- Fields on each component with types and attributes\n- Information flow (which components connect to which)\n- **Inbound flows**: External elements flowing INTO this slice (with field mappings)\n- **Outbound flows**: Elements in this slice flowing OUT to other slices (with field mappings)\n- Scenarios (Given-When-Then test cases)\n\nThe inbound/outbound flows show cross-slice dependencies - crucial for understanding how a slice integrates with the rest of the system.\n\n**Scenarios are key** - they define the slice's behavior and give it context. A slice's scenarios show:\n- **Given**: What events have already occurred (the starting state)\n- **When**: What command is being executed with what field values\n- **Then**: The expected outcome - either:\n - Events that should be produced\n - An error that should occur\n - A read model assertion (expected state)\n\nExample scenario in slice output:\n```xml\n<scenarios>\n <scenario name=\"Successfully place order\">\n <given>\n <event type=\"CustomerRegistered\">customerId: \"123\"</event>\n </given>\n <when>\n <command type=\"PlaceOrder\">customerId: \"123\", items: [...]</command>\n </when>\n <then>\n <event type=\"OrderPlaced\">orderId: \"456\", customerId: \"123\"</event>\n </then>\n </scenario>\n <scenario name=\"Cannot place order without items\">\n <when>\n <command type=\"PlaceOrder\">customerId: \"123\", items: []</command>\n </when>\n <then>\n <error type=\"ValidationError\">Order must contain at least one item</error>\n </then>\n </scenario>\n</scenarios>\n```\n\nReading scenarios helps understand:\n- What preconditions the slice expects\n- What inputs the command accepts\n- What the happy path looks like\n- What error cases are handled\n\n### Show an Event or Command\n```bash\neventmodeler show event \"<name>\"\neventmodeler show command \"<name>\"\n```\nReturns XML with the element's fields and connections.\n\n### Show Read Models, Screens, and Processors\n```bash\neventmodeler show readmodel \"<name>\"\neventmodeler show screen \"<name>\"\neventmodeler show processor \"<name>\"\n```\nUse these for detailed flow context:\n- `show readmodel`: fields, source events, and who consumes it\n- `show screen`: fields, source read models, and triggered commands\n- `show processor`: fields, source read models, and triggered commands\n\n### Show Aggregate and Scenario\n```bash\neventmodeler show aggregate \"<name>\"\neventmodeler show scenario \"<name>\"\n```\n- `show aggregate`: aggregate ID configuration and contained events\n- `show scenario`: complete Given-When-Then with field values\n\n### Show a Chapter\n```bash\neventmodeler show chapter \"<name>\"\n```\nShows the chapter with:\n- All slices within the chapter\n- **Flow graph**: How slices connect to each other (which events flow to which read models, etc.) with field mappings\n- **External dependencies**:\n - Inbound: Flows from outside the chapter into slices within it\n - Outbound: Flows from slices in this chapter to elements outside it\n\nUse `show chapter` to understand the relationships between slices in a feature area. Use `list slices --chapter` for a simple list without flow details.\n\n### Show an Actor\n```bash\neventmodeler show actor \"<name>\"\n```\nShows the actor and lists all screens associated with it.\n\n## Managing Slice Status\n\nSlices have a status that tracks their progress: `created`, `in-progress`, `blocked`, `done`.\n\n```bash\n# Mark a slice as in progress\neventmodeler mark \"Place Order\" in-progress\n\n# Mark a slice as done\neventmodeler mark \"Place Order\" done\n\n# Mark a slice as blocked\neventmodeler mark \"Place Order\" blocked\n\n# Reset to created\neventmodeler mark \"Place Order\" created\n```\n\nUse `list slices` to see current statuses.\n\n## Checking Information Completeness\n\nInformation completeness tracks whether fields flow correctly between connected elements.\n\n### Check a Specific Element\n```bash\neventmodeler show completeness \"<name>\"\n```\nAuto-detects the element type (command, event, read model, screen, or processor) and shows:\n- Incoming flows to that element\n- Which fields are satisfied (have a source)\n- Which fields are unsatisfied (missing a source)\n\n### Check the Entire Model\n```bash\neventmodeler show model-completeness\n```\nProject-wide view of all information flows:\n- Summary: complete count, incomplete count, total\n- Lists all incomplete flows with their unsatisfied fields\n\n### Check Aggregate ID Fields\n```bash\neventmodeler show aggregate-completeness \"<aggregate-name>\"\n```\nChecks if all events in an aggregate have the aggregate's ID field.\n\n## Exploration Strategy\n\nWhen exploring an unfamiliar event model:\n\n1. **Get the big picture**: `eventmodeler summary` \u2192 understand scale\n2. **See the features**: `eventmodeler list slices` \u2192 what does this system do?\n3. **Understand organization**: `eventmodeler list chapters` \u2192 how is it structured over time?\n4. **Dive into a slice**: `eventmodeler show slice \"<name>\"` \u2192 how does one feature work?\n5. **Read the scenarios**: They tell you what the slice actually does, its preconditions, and edge cases\n6. **Search for specifics**: `eventmodeler search \"<term>\"` \u2192 find related elements\n\nWhen checking model health:\n1. **Overall completeness**: `eventmodeler show model-completeness` \u2192 any broken flows?\n2. **Specific element**: `eventmodeler show completeness \"<name>\"` \u2192 what's missing here?\n3. **Aggregate consistency**: `eventmodeler show aggregate-completeness \"<name>\"` \u2192 ID fields present?\n\nWhen looking for something specific:\n- Know the feature name? \u2192 `show slice`\n- Know an event name? \u2192 `show event` or `search`\n- Know a read model/screen/processor name? \u2192 `show readmodel` / `show screen` / `show processor`\n- Need scenario details? \u2192 `list scenarios` then `show scenario`\n- Want all events? \u2192 `list events`\n- Checking data flow? \u2192 `show completeness` or `show model-completeness`\n";
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const meta: {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
};
|
|
5
|
-
export declare const content = "\n# Information Flow: Fields, Mappings, and Completeness\n\nThis guide covers the full lifecycle of information in an event model: designing fields, adding them to elements, mapping data between connected elements, updating field properties, and verifying completeness.\n\n---\n\n## Designing Read Models\n\nRead models are projections of event data - views that serve specific screens or use cases. Every field in a read model should trace back to event fields.\n\n### Understand the Context\n```bash\n# Look at the slice containing the read model\neventmodeler show slice \"<slice-name>\"\n\n# Check current completeness - what's missing?\neventmodeler show completeness \"<read-model-name>\"\n\n# See what events feed into this read model\neventmodeler list events\n```\n\n### Design the Fields\nBased on the use case, identify what fields are needed:\n- **Identity fields** - IDs to identify the entity\n- **Display fields** - What the user sees\n- **Status fields** - Current state\n- **Computed fields** - Aggregations, counts, derived values\n- **Timestamp fields** - When things happened\n\n### Propose Before Adding\n```\nI suggest these fields for the OrderHistory read model:\n- orderId (UUID) - identifies the order\n- customerId (UUID) - who placed it\n- status (String) - current status: pending, shipped, delivered, cancelled\n- total (Decimal) - order total\n- placedAt (DateTime) - when it was placed\n- lastUpdatedAt (DateTime) - when status last changed\n\nDoes this look right?\n```\n\n## Adding Fields\n\nField types: `UUID`, `String`, `Int`, `Long`, `Double`, `Decimal`, `Boolean`, `Date`, `DateTime`, `Custom`\n\n```bash\n# Add a simple field\neventmodeler add field --read-model \"OrderSummary\" --xml '<field name=\"orderId\" type=\"UUID\"/>'\n\n# Add an optional field\neventmodeler add field --read-model \"OrderSummary\" --xml '<field name=\"cancelledAt\" type=\"DateTime\" isOptional=\"true\"/>'\n\n# Add a list field\neventmodeler add field --read-model \"OrderSummary\" --xml '<field name=\"itemIds\" type=\"UUID\" isList=\"true\"/>'\n\n# Add a complex/nested field\neventmodeler add field --read-model \"OrderSummary\" --xml '<field name=\"shippingAddress\" type=\"Custom\">\n <field name=\"street\" type=\"String\"/>\n <field name=\"city\" type=\"String\"/>\n <field name=\"postalCode\" type=\"String\"/>\n</field>'\n```\n\n**CRITICAL: Custom fields MUST contain subfields.** A Custom field without subfields is invalid.\n\n### Lists and Custom Types\n\n**List of custom objects** - combine `type=\"Custom\"` with `isList=\"true\"`:\n```bash\neventmodeler add field --read-model \"OrderSummary\" --xml '<field name=\"lineItems\" type=\"Custom\" isList=\"true\">\n <field name=\"productId\" type=\"UUID\"/>\n <field name=\"productName\" type=\"String\"/>\n <field name=\"quantity\" type=\"Int\"/>\n <field name=\"unitPrice\" type=\"Decimal\"/>\n</field>'\n```\n\n**Nested custom types** - subfields can themselves be Custom:\n```bash\neventmodeler add field --read-model \"OrderSummary\" --xml '<field name=\"customer\" type=\"Custom\">\n <field name=\"customerId\" type=\"UUID\"/>\n <field name=\"name\" type=\"String\"/>\n <field name=\"billingAddress\" type=\"Custom\">\n <field name=\"street\" type=\"String\"/>\n <field name=\"city\" type=\"String\"/>\n <field name=\"country\" type=\"String\"/>\n </field>\n</field>'\n```\n\n---\n\n## Mapping Fields on Flows\n\nField mappings define how data flows from source elements to target elements. When you connect an Event to a ReadModel with a flow, the field mappings specify which event fields populate which read model fields.\n\n### Command Syntax\n\n```bash\neventmodeler map fields --flow \"<Source>\u2192<Target>\" --xml '<mapping from=\"sourceField\" to=\"targetField\"/>'\n```\n\nOr with multiple mappings:\n\n```bash\neventmodeler map fields --flow \"<Source>\u2192<Target>\" --xml '\n <mapping from=\"orderId\" to=\"orderId\"/>\n <mapping from=\"customerId\" to=\"customerId\"/>\n <mapping from=\"totalAmount\" to=\"total\"/>\n'\n```\n\n### Flow Identifier Formats\n\nThe `--flow` argument accepts:\n\n1. **Name arrow format** (most common): `\"SourceName\u2192TargetName\"` or `\"SourceName->TargetName\"`\n2. **Element ID prefix** (for duplicates): `\"id:abc12345\u2192TargetName\"`\n3. **Flow ID directly**: The UUID of the flow itself\n\n### JSON Alternative\n\n```bash\neventmodeler map fields --flow \"OrderPlaced\u2192OrderSummary\" --json '[\n {\"from\": \"orderId\", \"to\": \"orderId\"},\n {\"from\": \"customerId\", \"to\": \"customerId\"}\n]'\n```\n\n### Workflow\n\n1. **Find flows needing mappings**: `eventmodeler show model-completeness`\n2. **See source fields**: `eventmodeler show event \"OrderPlaced\"`\n3. **See target fields**: `eventmodeler show completeness \"OrderSummary\"`\n4. **Create mappings**: `eventmodeler map fields --flow \"OrderPlaced\u2192OrderSummary\" --xml '...'`\n5. **Verify**: `eventmodeler show completeness \"OrderSummary\"`\n\n### Mapping Nested Fields\n\nFor Custom (nested) field types, use dot notation:\n\n```bash\neventmodeler map fields --flow \"OrderPlaced\u2192OrderSummary\" --xml '\n <mapping from=\"shippingAddress.street\" to=\"deliveryAddress.street\"/>\n <mapping from=\"shippingAddress.city\" to=\"deliveryAddress.city\"/>\n <mapping from=\"shippingAddress.postalCode\" to=\"deliveryAddress.zip\"/>\n'\n```\n\n### Multiple Flows to Same Target\n\nA read model often receives data from multiple events. Map each flow separately:\n\n```bash\n# Initial data from OrderPlaced\neventmodeler map fields --flow \"OrderPlaced\u2192OrderSummary\" --xml '\n <mapping from=\"orderId\" to=\"orderId\"/>\n <mapping from=\"customerId\" to=\"customerId\"/>\n <mapping from=\"totalAmount\" to=\"total\"/>\n'\n\n# Status updates from OrderShipped\neventmodeler map fields --flow \"OrderShipped\u2192OrderSummary\" --xml '\n <mapping from=\"shippedAt\" to=\"shippedDate\"/>\n <mapping from=\"trackingNumber\" to=\"trackingNumber\"/>\n'\n```\n\n### Updating Existing Mappings\n\nRunning `map fields` again **merges** with existing mappings:\n- New mappings are added\n- If a target field already has a mapping, it's replaced with the new source\n\n### Common Mapping Patterns\n\n| Pattern | Example |\n|---------|---------|\n| Identity | `<mapping from=\"orderId\" to=\"orderId\"/>` |\n| Rename | `<mapping from=\"totalAmount\" to=\"total\"/>` |\n| Nested to flat | `<mapping from=\"customer.id\" to=\"customerId\"/>` |\n| Flat to nested | `<mapping from=\"street\" to=\"address.street\"/>` |\n\n---\n\n## Updating Field Properties\n\nUse `update field` to modify field attributes without changing the field's name or structure.\n\n### Command Syntax\n\n```bash\neventmodeler update field --<element-type> \"<name>\" --field \"<field-name>\" [options]\n```\n\nElement types: `--command`, `--event`, `--read-model`, `--screen`, `--processor`\n\n### Available Options\n\n| Option | Values | Description |\n|--------|--------|-------------|\n| `--optional` | `true`/`false` | Mark field as not required for completeness |\n| `--generated` | `true`/`false` | Mark field as system-generated (not from user input) |\n| `--user-input` | `true`/`false` | Mark field as user-provided (screens only) |\n| `--type` | Field type | Change the field's data type |\n\n### Examples\n\n```bash\n# Mark a field as optional\neventmodeler update field --event \"OrderPlaced\" --field \"promoCode\" --optional true\n\n# Mark a field as system-generated\neventmodeler update field --event \"OrderPlaced\" --field \"orderId\" --generated true\n\n# Mark screen fields as user input\neventmodeler update field --screen \"Checkout\" --field \"email\" --user-input true\n\n# Change a field's type\neventmodeler update field --read-model \"OrderSummary\" --field \"total\" --type Decimal\n\n# Combine multiple updates\neventmodeler update field --event \"UserRegistered\" --field \"createdAt\" --generated true --optional false\n```\n\n### Updating Nested Fields\n\nUse dot notation for fields inside Custom types:\n\n```bash\neventmodeler update field --event \"OrderPlaced\" --field \"shippingAddress.postalCode\" --optional true\n```\n\n### Field Property Meanings\n\n**isOptional**: Field may not always have a value. Completeness checks don't require this field to have a mapping source.\n\n**isGenerated**: Field is system-generated, not derived from other fields. Completeness checks don't require this field to have a mapping source. Use for: IDs, timestamps, sequence numbers, computed values.\n\n**isUserInput** (screens only): Field comes from user input on the screen. Identifies which screen fields are inputs vs. display-only.\n\n---\n\n## Removing Fields\n\n```bash\n# Remove a field from an event\neventmodeler remove field --event \"OrderPlaced\" --field \"legacyId\"\n\n# Remove a field from a read model\neventmodeler remove field --read-model \"OrderSummary\" --field \"deprecatedStatus\"\n\n# Remove a nested field using dot notation\neventmodeler remove field --event \"OrderPlaced\" --field \"metadata.debugInfo\"\n```\n\n**Caution**: Removing a field may break existing field mappings. Check completeness first:\n```bash\neventmodeler show completeness \"<element-name>\"\n```\n\n---\n\n## Checking Completeness\n\n### Check a Specific Element\n```bash\neventmodeler show completeness \"<name>\"\n```\nShows incoming flows and which fields are satisfied vs unsatisfied.\n\n### Check the Entire Model\n```bash\neventmodeler show model-completeness\n```\nProject-wide view: complete count, incomplete count, and all incomplete flows with their unsatisfied fields.\n\n### Check Aggregate ID Fields\n```bash\neventmodeler show aggregate-completeness \"<aggregate-name>\"\n```\n\n---\n\n## Common Mistakes\n\n- **Creating empty Custom fields**: Custom fields MUST have subfields.\n - Wrong: `<field name=\"address\" type=\"Custom\"/>`\n - Right: `<field name=\"address\" type=\"Custom\"><field name=\"street\" type=\"String\"/>...</field>`\n\n- **Using \"List\" as a field type**: Use `isList=\"true\"` with a valid type instead.\n - Wrong: `<field name=\"items\" type=\"List\"/>`\n - Right: `<field name=\"items\" type=\"String\" isList=\"true\"/>`\n\n- **Using wrong attribute names**: Always use camelCase attributes.\n - Wrong: `optional=\"true\"`, `generated=\"true\"`, `list=\"true\"`\n - Right: `isOptional=\"true\"`, `isGenerated=\"true\"`, `isList=\"true\"`\n\n## Best Practices\n\n1. **Get approval before adding fields** - Propose the design, let the user confirm\n2. **Consider all source events** - A read model may need data from multiple events\n3. **Use appropriate types** - `Decimal` for money, `DateTime` for timestamps\n4. **Mark optional fields** - Fields that may not always have values (e.g., `cancelledAt`)\n5. **Mark generated fields early** - When creating slices, immediately mark IDs and timestamps as generated\n6. **Map all fields at once** - More efficient than one mapping at a time\n7. **Verify after mapping** - Run `show completeness` to confirm all fields are satisfied\n8. **Check mappings before removing** - Removing a field can break existing mappings\n";
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const meta: {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
};
|
|
5
|
-
export declare const content = "\n# Designing Scenarios for Slices\n\nScenarios define the behavior of a slice. There are two main scenario patterns:\n\n1. **State-Change Scenarios** (for state-change slices): Given events \u2192 When command \u2192 Then events/error\n2. **Automation Scenarios** (for automation slices): Given events \u2192 Then command (simpler Given-Then format)\n\n## Your Workflow\n\nWhen helping design scenarios for a slice:\n\n### 1. Gather Context\n```bash\n# Look at the slice to understand its components\neventmodeler show slice \"<slice-name>\"\n\n# See what chapter it belongs to and nearby slices for context\neventmodeler list chapters\neventmodeler show chapter \"<chapter-name>\"\n\n# List all events to understand what's available for Given clauses\neventmodeler list events\n```\n\n### 2. Identify the Slice Type\n\n**State-Change Slice** (Screen \u2192 Command \u2192 Event):\n- User triggers a command\n- Command produces event(s)\n- Use: Given \u2192 When command \u2192 Then events/error\n\n**Automation Slice** (ReadModel \u2192 Processor \u2192 Command \u2192 Event):\n- Events trigger the processor to issue a command\n- Use simpler Given \u2192 Then format (no When)\n- Given: All events that lead to the trigger condition\n- Then: The command that should be dispatched (or noCommand)\n\n### 3. Design Scenarios by Type\n\n---\n\n## State-Change Scenarios\n\nFor slices where a user action triggers a command:\n\n**Happy Path** - The command succeeds under normal conditions\n- Given: Any required preconditions (existing events)\n- When: The command with valid input\n- Then: The expected event(s) with field values\n\n**Validation Errors** - Bad input is rejected\n- Given: (may be empty)\n- When: Command with invalid input\n- Then: Error with message\n\n**Business Rule Violations** - Valid input but rule prevents action\n- Given: Events that create a state where the action isn't allowed\n- When: The command\n- Then: Error explaining why\n\n### State-Change Examples\n\n```bash\n# Happy path - command succeeds\neventmodeler add scenario --slice \"<slice-name>\" --xml '<scenario name=\"Successfully place order\" description=\"A registered customer can place an order with valid items\">\n <given>\n <event name=\"CustomerRegistered\">\n <field name=\"customerId\">cust-123</field>\n </event>\n </given>\n <when>\n <command name=\"PlaceOrder\">\n <field name=\"customerId\">cust-123</field>\n <field name=\"items\">[{\"productId\": \"prod-1\", \"quantity\": 2}]</field>\n </command>\n </when>\n <then type=\"events\">\n <event name=\"OrderPlaced\">\n <field name=\"orderId\">order-456</field>\n <field name=\"customerId\">cust-123</field>\n </event>\n </then>\n</scenario>'\n```\n\n```bash\n# Validation error\neventmodeler add scenario --slice \"<slice-name>\" --xml '<scenario name=\"Cannot place empty order\" description=\"Orders must contain at least one item - empty item lists are rejected\">\n <when>\n <command name=\"PlaceOrder\">\n <field name=\"customerId\">cust-123</field>\n <field name=\"items\">[]</field>\n </command>\n </when>\n <then type=\"error\" errorType=\"ValidationError\">Order must contain at least one item</then>\n</scenario>'\n```\n\n```bash\n# Business rule violation\neventmodeler add scenario --slice \"Cancel Order\" --xml '<scenario name=\"Cannot cancel shipped order\" description=\"Once an order has been shipped, it can no longer be cancelled\">\n <given>\n <event name=\"OrderPlaced\">\n <field name=\"orderId\">order-123</field>\n </event>\n <event name=\"OrderShipped\">\n <field name=\"orderId\">order-123</field>\n <field name=\"shippedAt\">2024-01-15T10:00:00Z</field>\n </event>\n </given>\n <when>\n <command name=\"CancelOrder\">\n <field name=\"orderId\">order-123</field>\n </command>\n </when>\n <then type=\"error\" errorType=\"BusinessRuleViolation\">Cannot cancel an order that has already shipped</then>\n</scenario>'\n```\n\n---\n\n## Automation Scenarios\n\nFor slices where events trigger a processor to issue a command. Use the simpler **Given-Then** format:\n\n**Trigger Conditions Met** - Events cause processor to act\n- Given: All the events that have occurred (including the trigger events)\n- Then: The command the processor issues\n\n**Trigger Conditions Not Met** - Events don't cause action\n- Given: Events that don't meet the trigger condition\n- Then: noCommand (processor should not dispatch)\n\n### Automation Examples\n\n```bash\n# Payment received triggers shipment initiation\n# Given: OrderPlaced + PaymentReceived \u2192 Then: InitiateShipment command\neventmodeler add scenario --slice \"Auto Ship Order\" --xml '<scenario name=\"Payment triggers shipment\" description=\"Given an order has been placed and payment received, the system automatically initiates shipment\">\n <given>\n <event name=\"OrderPlaced\">\n <field name=\"orderId\">order-123</field>\n <field name=\"customerId\">cust-456</field>\n </event>\n <event name=\"PaymentReceived\">\n <field name=\"orderId\">order-123</field>\n <field name=\"amount\">99.99</field>\n </event>\n </given>\n <then type=\"command\">\n <command name=\"InitiateShipment\">\n <field name=\"orderId\">order-123</field>\n <field name=\"warehouseId\">wh-001</field>\n </command>\n </then>\n</scenario>'\n```\n\n```bash\n# Negative case - conditions not met, no command dispatched\neventmodeler add scenario --slice \"Auto Ship Order\" --xml '<scenario name=\"No shipment without payment\" description=\"Given only an order placed (no payment), the system should not initiate shipment\">\n <given>\n <event name=\"OrderPlaced\">\n <field name=\"orderId\">order-123</field>\n <field name=\"customerId\">cust-456</field>\n </event>\n </given>\n <then type=\"noCommand\"/>\n</scenario>'\n```\n\n---\n\n## Read Model Assertions\n\nFor state-view slices, test that events project correctly to read models:\n\n```bash\neventmodeler add scenario --slice \"<slice-name>\" --xml '<scenario name=\"Order appears in summary\" description=\"After placing an order, it should be visible in the order summary with pending status\">\n <then type=\"readModelAssertion\">\n <read-model name=\"OrderSummary\">\n <field name=\"orderId\">order-456</field>\n <field name=\"status\">pending</field>\n </read-model>\n </then>\n</scenario>'\n```\n\n---\n\n## Scenario Types Summary\n\n| Slice Type | Format | Then Contains |\n|------------|--------|---------------|\n| State-Change | Given \u2192 When (Command) \u2192 Then | Events or Error |\n| Automation | Given (Events) \u2192 Then | Command or noCommand |\n| State-View | Given \u2192 Then | Read Model Assertion |\n\n## JSON Format Alternative\n\nYou can also use JSON format:\n\n**State-Change Scenario:**\n```bash\neventmodeler add scenario --slice \"<slice-name>\" --json '{\n \"name\": \"Successfully place order\",\n \"description\": \"A registered customer can place an order\",\n \"given\": [\n { \"event\": \"CustomerRegistered\", \"fieldValues\": { \"customerId\": \"cust-123\" } }\n ],\n \"when\": {\n \"command\": \"PlaceOrder\",\n \"commandFieldValues\": { \"customerId\": \"cust-123\", \"items\": [] }\n },\n \"then\": {\n \"type\": \"events\",\n \"events\": [\n { \"event\": \"OrderPlaced\", \"fieldValues\": { \"orderId\": \"order-456\" } }\n ]\n }\n}'\n```\n\n**Automation Scenario (Given-Then format):**\n```bash\neventmodeler add scenario --slice \"<slice-name>\" --json '{\n \"name\": \"Payment triggers shipment\",\n \"description\": \"Given order placed and payment received, shipment is initiated\",\n \"given\": [\n { \"event\": \"OrderPlaced\", \"fieldValues\": { \"orderId\": \"order-123\" } },\n { \"event\": \"PaymentReceived\", \"fieldValues\": { \"orderId\": \"order-123\", \"amount\": 99.99 } }\n ],\n \"then\": {\n \"type\": \"command\",\n \"command\": \"InitiateShipment\",\n \"commandFieldValues\": { \"orderId\": \"order-123\" }\n }\n}'\n```\n\n**Automation Scenario (negative case):**\n```bash\neventmodeler add scenario --slice \"<slice-name>\" --json '{\n \"name\": \"No shipment without payment\",\n \"description\": \"Given only order placed (no payment), no command dispatched\",\n \"given\": [\n { \"event\": \"OrderPlaced\", \"fieldValues\": { \"orderId\": \"order-123\" } }\n ],\n \"then\": {\n \"type\": \"noCommand\"\n }\n}'\n```\n\n## Best Practices\n\n1. **Write clear descriptions** - Explain why this scenario matters and what business rule it captures\n2. **Use realistic example values** - \"cust-123\" not \"string\", \"2024-01-15\" not \"date\"\n3. **Name scenarios descriptively** - \"Cannot cancel shipped order\" not \"Error case 1\"\n4. **Include all relevant fields** - Don't skip fields that matter for the scenario\n5. **Think about state** - What events need to exist for this scenario to make sense?\n6. **Cover the negative cases** - These often reveal missing business rules (use `noCommand` for automations)\n7. **Match format to slice type** - Use Given-When-Then for state-change, Given-Then for automation\n";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runGuide(subcommand: string | undefined): void;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
|
-
import * as crypto from 'node:crypto';
|
|
3
|
-
import { loadRawEvents } from '../../lib/file-loader.js';
|
|
4
|
-
import { createCloudClient } from '../../lib/cloud-client.js';
|
|
5
|
-
import { isAuthenticated } from '../../lib/config.js';
|
|
6
|
-
/**
|
|
7
|
-
* Import a .eventmodel file to the cloud backend.
|
|
8
|
-
* Creates a new cloud model with a fresh modelId.
|
|
9
|
-
*/
|
|
10
|
-
export async function importModel(filePath, options = {}) {
|
|
11
|
-
// 1. Validate authentication
|
|
12
|
-
if (!isAuthenticated()) {
|
|
13
|
-
console.error('Not authenticated. Run "eventmodeler login" first.');
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
// 2. Load and validate file
|
|
17
|
-
const absolutePath = path.resolve(filePath);
|
|
18
|
-
console.log(`Loading events from: ${absolutePath}`);
|
|
19
|
-
let events;
|
|
20
|
-
try {
|
|
21
|
-
events = loadRawEvents(absolutePath);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
console.error(`Failed to load file: ${error instanceof Error ? error.message : error}`);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
if (events.length === 0) {
|
|
28
|
-
console.error('File contains no events.');
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
console.log(`Found ${events.length} events`);
|
|
32
|
-
// 3. Determine model name
|
|
33
|
-
const modelName = options.name ?? deriveModelName(filePath, events);
|
|
34
|
-
// 4. Generate new unique modelId
|
|
35
|
-
const modelId = crypto.randomUUID();
|
|
36
|
-
// 5. Filter out any existing EventModelCreated events (we'll create a new one)
|
|
37
|
-
const filteredEvents = events.filter((e) => e.type !== 'EventModelCreated');
|
|
38
|
-
// 6. Import to cloud
|
|
39
|
-
console.log(`Importing as: ${modelName} (${modelId})`);
|
|
40
|
-
const client = await createCloudClient();
|
|
41
|
-
try {
|
|
42
|
-
const result = await client.importModel(modelId, modelName, filteredEvents);
|
|
43
|
-
console.log(`\nSuccessfully imported ${result.eventCount} events!`);
|
|
44
|
-
console.log(`Model ID: ${result.modelId}`);
|
|
45
|
-
console.log(`\nView at: https://eventmodeler.app/model/${result.modelId}`);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
console.error(`Import failed: ${error instanceof Error ? error.message : error}`);
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Derive model name from file or existing EventModelCreated event.
|
|
54
|
-
*/
|
|
55
|
-
function deriveModelName(filePath, events) {
|
|
56
|
-
// Try to extract from existing EventModelCreated event
|
|
57
|
-
const createEvent = events.find((e) => e.type === 'EventModelCreated');
|
|
58
|
-
if (createEvent?.data?.name) {
|
|
59
|
-
return createEvent.data.name;
|
|
60
|
-
}
|
|
61
|
-
// Fall back to filename without extension
|
|
62
|
-
return path.basename(filePath, '.eventmodel');
|
|
63
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
-
export function listChapters(model, format) {
|
|
3
|
-
const chapters = [...model.chapters.values()];
|
|
4
|
-
// Sort by x position (left to right on timeline)
|
|
5
|
-
const sorted = [...chapters].sort((a, b) => a.position.x - b.position.x);
|
|
6
|
-
if (format === 'json') {
|
|
7
|
-
outputJson({
|
|
8
|
-
chapters: sorted.map(ch => ({ id: ch.id, name: ch.name }))
|
|
9
|
-
});
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
if (chapters.length === 0) {
|
|
13
|
-
console.log('<chapters/>');
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
console.log('<chapters>');
|
|
17
|
-
for (const chapter of sorted) {
|
|
18
|
-
console.log(` <chapter id="${chapter.id}" name="${escapeXml(chapter.name)}"/>`);
|
|
19
|
-
}
|
|
20
|
-
console.log('</chapters>');
|
|
21
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
-
export function listCommands(model, format) {
|
|
3
|
-
const commands = [...model.commands.values()];
|
|
4
|
-
const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
5
|
-
if (format === 'json') {
|
|
6
|
-
outputJson({
|
|
7
|
-
commands: sorted.map(cmd => ({ id: cmd.id, name: cmd.name, fields: cmd.fields.length }))
|
|
8
|
-
});
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
if (commands.length === 0) {
|
|
12
|
-
console.log('<commands/>');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
console.log('<commands>');
|
|
16
|
-
for (const cmd of sorted) {
|
|
17
|
-
console.log(` <command id="${cmd.id}" name="${escapeXml(cmd.name)}" fields="${cmd.fields.length}"/>`);
|
|
18
|
-
}
|
|
19
|
-
console.log('</commands>');
|
|
20
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
-
// Find which aggregate an event belongs to (center point inside aggregate bounds)
|
|
3
|
-
function findAggregateForEvent(model, event) {
|
|
4
|
-
const centerX = event.position.x + event.width / 2;
|
|
5
|
-
const centerY = event.position.y + event.height / 2;
|
|
6
|
-
for (const aggregate of model.aggregates.values()) {
|
|
7
|
-
const bounds = {
|
|
8
|
-
left: aggregate.position.x,
|
|
9
|
-
right: aggregate.position.x + aggregate.size.width,
|
|
10
|
-
top: aggregate.position.y,
|
|
11
|
-
bottom: aggregate.position.y + aggregate.size.height,
|
|
12
|
-
};
|
|
13
|
-
if (centerX >= bounds.left && centerX <= bounds.right && centerY >= bounds.top && centerY <= bounds.bottom) {
|
|
14
|
-
return aggregate;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
// Find which slice contains an event
|
|
20
|
-
function findSliceForEvent(model, event) {
|
|
21
|
-
for (const slice of model.slices.values()) {
|
|
22
|
-
const centerX = event.position.x + event.width / 2;
|
|
23
|
-
const centerY = event.position.y + event.height / 2;
|
|
24
|
-
if (centerX >= slice.position.x &&
|
|
25
|
-
centerX <= slice.position.x + slice.size.width &&
|
|
26
|
-
centerY >= slice.position.y &&
|
|
27
|
-
centerY <= slice.position.y + slice.size.height) {
|
|
28
|
-
return slice.name;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
export function listEvents(model, format) {
|
|
34
|
-
const events = [...model.events.values()];
|
|
35
|
-
// Sort: originals first, then copies; alphabetically within each group
|
|
36
|
-
const sorted = [...events].sort((a, b) => {
|
|
37
|
-
// Originals before copies
|
|
38
|
-
if (!a.originalNodeId && b.originalNodeId)
|
|
39
|
-
return -1;
|
|
40
|
-
if (a.originalNodeId && !b.originalNodeId)
|
|
41
|
-
return 1;
|
|
42
|
-
// Alphabetically by name
|
|
43
|
-
return a.name.localeCompare(b.name);
|
|
44
|
-
});
|
|
45
|
-
if (format === 'json') {
|
|
46
|
-
outputJson({
|
|
47
|
-
events: sorted.map(evt => {
|
|
48
|
-
const aggregate = findAggregateForEvent(model, evt);
|
|
49
|
-
const result = {
|
|
50
|
-
id: evt.id,
|
|
51
|
-
name: evt.name,
|
|
52
|
-
fields: evt.fields.length,
|
|
53
|
-
};
|
|
54
|
-
if (aggregate)
|
|
55
|
-
result.aggregate = aggregate.name;
|
|
56
|
-
if (evt.originalNodeId) {
|
|
57
|
-
result.linkedCopy = true;
|
|
58
|
-
const original = model.events.get(evt.originalNodeId);
|
|
59
|
-
const originSlice = original ? findSliceForEvent(model, original) : null;
|
|
60
|
-
if (originSlice)
|
|
61
|
-
result.originSlice = originSlice;
|
|
62
|
-
}
|
|
63
|
-
else if (evt.canonicalId) {
|
|
64
|
-
const copyCount = [...model.events.values()].filter(e => e.canonicalId === evt.canonicalId && e.originalNodeId).length;
|
|
65
|
-
if (copyCount > 0)
|
|
66
|
-
result.copies = copyCount;
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
})
|
|
70
|
-
});
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (events.length === 0) {
|
|
74
|
-
console.log('<events/>');
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
console.log('<events>');
|
|
78
|
-
for (const evt of sorted) {
|
|
79
|
-
const aggregate = findAggregateForEvent(model, evt);
|
|
80
|
-
const aggregateAttr = aggregate ? ` aggregate="${escapeXml(aggregate.name)}"` : '';
|
|
81
|
-
if (evt.originalNodeId) {
|
|
82
|
-
// This is a linked copy - show origin info
|
|
83
|
-
const original = model.events.get(evt.originalNodeId);
|
|
84
|
-
const originSlice = original ? findSliceForEvent(model, original) : null;
|
|
85
|
-
const originAttr = originSlice ? ` origin-slice="${escapeXml(originSlice)}"` : '';
|
|
86
|
-
console.log(` <event id="${evt.id}" name="${escapeXml(evt.name)}" fields="${evt.fields.length}"${aggregateAttr} linked-copy="true"${originAttr}/>`);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
// This is an original - show copy count
|
|
90
|
-
const copyCount = evt.canonicalId
|
|
91
|
-
? [...model.events.values()].filter(e => e.canonicalId === evt.canonicalId && e.originalNodeId).length
|
|
92
|
-
: 0;
|
|
93
|
-
const copiesAttr = copyCount > 0 ? ` copies="${copyCount}"` : '';
|
|
94
|
-
console.log(` <event id="${evt.id}" name="${escapeXml(evt.name)}" fields="${evt.fields.length}"${aggregateAttr}${copiesAttr}/>`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
console.log('</events>');
|
|
98
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
-
export function listProcessors(model, format) {
|
|
3
|
-
const processors = [...model.processors.values()];
|
|
4
|
-
const sorted = [...processors].sort((a, b) => a.name.localeCompare(b.name));
|
|
5
|
-
if (format === 'json') {
|
|
6
|
-
outputJson({
|
|
7
|
-
processors: sorted.map(proc => ({ id: proc.id, name: proc.name, fields: proc.fields.length }))
|
|
8
|
-
});
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
if (processors.length === 0) {
|
|
12
|
-
console.log('<processors/>');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
console.log('<processors>');
|
|
16
|
-
for (const proc of sorted) {
|
|
17
|
-
console.log(` <processor id="${proc.id}" name="${escapeXml(proc.name)}" fields="${proc.fields.length}"/>`);
|
|
18
|
-
}
|
|
19
|
-
console.log('</processors>');
|
|
20
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { escapeXml, outputJson } from '../../lib/format.js';
|
|
2
|
-
export function listReadModels(model, format) {
|
|
3
|
-
// Filter out linked copies (they have originalNodeId set)
|
|
4
|
-
const readModels = [...model.readModels.values()].filter(rm => !rm.originalNodeId);
|
|
5
|
-
const sorted = [...readModels].sort((a, b) => a.name.localeCompare(b.name));
|
|
6
|
-
if (format === 'json') {
|
|
7
|
-
outputJson({
|
|
8
|
-
readModels: sorted.map(rm => ({ id: rm.id, name: rm.name, fields: rm.fields.length }))
|
|
9
|
-
});
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
if (readModels.length === 0) {
|
|
13
|
-
console.log('<read-models/>');
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
console.log('<read-models>');
|
|
17
|
-
for (const rm of sorted) {
|
|
18
|
-
console.log(` <read-model id="${rm.id}" name="${escapeXml(rm.name)}" fields="${rm.fields.length}"/>`);
|
|
19
|
-
}
|
|
20
|
-
console.log('</read-models>');
|
|
21
|
-
}
|