eventmodeler 0.5.0 → 0.6.1

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 (150) hide show
  1. package/dist/index.js +6776 -2132
  2. package/package.json +11 -5
  3. package/dist/api/index.d.ts +0 -285
  4. package/dist/api/index.js +0 -323
  5. package/dist/cloud/slices/index.d.ts +0 -276
  6. package/dist/cloud/slices/index.js +0 -406
  7. package/dist/eventmodeler.js +0 -5646
  8. package/dist/formatters.d.ts +0 -17
  9. package/dist/formatters.js +0 -482
  10. package/dist/index.d.ts +0 -2
  11. package/dist/lib/auth.d.ts +0 -24
  12. package/dist/lib/auth.js +0 -331
  13. package/dist/lib/backend.d.ts +0 -43
  14. package/dist/lib/backend.js +0 -73
  15. package/dist/lib/chapter-utils.d.ts +0 -13
  16. package/dist/lib/chapter-utils.js +0 -71
  17. package/dist/lib/cloud-client.d.ts +0 -69
  18. package/dist/lib/cloud-client.js +0 -364
  19. package/dist/lib/config.d.ts +0 -30
  20. package/dist/lib/config.js +0 -95
  21. package/dist/lib/diff/merge-rules.d.ts +0 -45
  22. package/dist/lib/diff/merge-rules.js +0 -210
  23. package/dist/lib/diff/model-differ.d.ts +0 -8
  24. package/dist/lib/diff/model-differ.js +0 -568
  25. package/dist/lib/diff/three-way-merge.d.ts +0 -7
  26. package/dist/lib/diff/three-way-merge.js +0 -390
  27. package/dist/lib/diff/types.d.ts +0 -75
  28. package/dist/lib/diff/types.js +0 -1
  29. package/dist/lib/element-lookup.d.ts +0 -58
  30. package/dist/lib/element-lookup.js +0 -126
  31. package/dist/lib/file-loader.d.ts +0 -8
  32. package/dist/lib/file-loader.js +0 -108
  33. package/dist/lib/flow-utils.d.ts +0 -53
  34. package/dist/lib/flow-utils.js +0 -348
  35. package/dist/lib/format.d.ts +0 -10
  36. package/dist/lib/format.js +0 -23
  37. package/dist/lib/project-config.d.ts +0 -27
  38. package/dist/lib/project-config.js +0 -83
  39. package/dist/lib/slice-utils.d.ts +0 -59
  40. package/dist/lib/slice-utils.js +0 -140
  41. package/dist/local/slices/index.d.ts +0 -11
  42. package/dist/local/slices/index.js +0 -13
  43. package/dist/projection.d.ts +0 -3
  44. package/dist/projection.js +0 -828
  45. package/dist/slices/add-field/index.d.ts +0 -8
  46. package/dist/slices/add-field/index.js +0 -211
  47. package/dist/slices/add-scenario/index.d.ts +0 -27
  48. package/dist/slices/add-scenario/index.js +0 -307
  49. package/dist/slices/codegen-chapter-events/index.d.ts +0 -2
  50. package/dist/slices/codegen-chapter-events/index.js +0 -145
  51. package/dist/slices/codegen-slice/index.d.ts +0 -2
  52. package/dist/slices/codegen-slice/index.js +0 -448
  53. package/dist/slices/create-automation-slice/index.d.ts +0 -2
  54. package/dist/slices/create-automation-slice/index.js +0 -304
  55. package/dist/slices/create-flow/index.d.ts +0 -2
  56. package/dist/slices/create-flow/index.js +0 -183
  57. package/dist/slices/create-state-change-slice/index.d.ts +0 -2
  58. package/dist/slices/create-state-change-slice/index.js +0 -263
  59. package/dist/slices/create-state-view-slice/index.d.ts +0 -2
  60. package/dist/slices/create-state-view-slice/index.js +0 -128
  61. package/dist/slices/diff/index.d.ts +0 -11
  62. package/dist/slices/diff/index.js +0 -293
  63. package/dist/slices/export-eventmodel-to-json/index.d.ts +0 -2
  64. package/dist/slices/export-eventmodel-to-json/index.js +0 -355
  65. package/dist/slices/git/index.d.ts +0 -2
  66. package/dist/slices/git/index.js +0 -125
  67. package/dist/slices/guide/guides/codegen.d.ts +0 -5
  68. package/dist/slices/guide/guides/codegen.js +0 -339
  69. package/dist/slices/guide/guides/connect-slices.d.ts +0 -5
  70. package/dist/slices/guide/guides/connect-slices.js +0 -202
  71. package/dist/slices/guide/guides/create-slices.d.ts +0 -5
  72. package/dist/slices/guide/guides/create-slices.js +0 -303
  73. package/dist/slices/guide/guides/explore.d.ts +0 -5
  74. package/dist/slices/guide/guides/explore.js +0 -251
  75. package/dist/slices/guide/guides/information-flow.d.ts +0 -5
  76. package/dist/slices/guide/guides/information-flow.js +0 -318
  77. package/dist/slices/guide/guides/scenarios.d.ts +0 -5
  78. package/dist/slices/guide/guides/scenarios.js +0 -269
  79. package/dist/slices/guide/index.d.ts +0 -1
  80. package/dist/slices/guide/index.js +0 -40
  81. package/dist/slices/import/index.d.ts +0 -8
  82. package/dist/slices/import/index.js +0 -63
  83. package/dist/slices/init/index.d.ts +0 -5
  84. package/dist/slices/init/index.js +0 -80
  85. package/dist/slices/list-chapters/index.d.ts +0 -3
  86. package/dist/slices/list-chapters/index.js +0 -21
  87. package/dist/slices/list-commands/index.d.ts +0 -3
  88. package/dist/slices/list-commands/index.js +0 -20
  89. package/dist/slices/list-events/index.d.ts +0 -3
  90. package/dist/slices/list-events/index.js +0 -98
  91. package/dist/slices/list-processors/index.d.ts +0 -3
  92. package/dist/slices/list-processors/index.js +0 -20
  93. package/dist/slices/list-readmodels/index.d.ts +0 -3
  94. package/dist/slices/list-readmodels/index.js +0 -21
  95. package/dist/slices/list-scenarios/index.d.ts +0 -3
  96. package/dist/slices/list-scenarios/index.js +0 -35
  97. package/dist/slices/list-screens/index.d.ts +0 -3
  98. package/dist/slices/list-screens/index.js +0 -47
  99. package/dist/slices/list-slices/index.d.ts +0 -3
  100. package/dist/slices/list-slices/index.js +0 -35
  101. package/dist/slices/login/index.d.ts +0 -1
  102. package/dist/slices/login/index.js +0 -20
  103. package/dist/slices/logout/index.d.ts +0 -1
  104. package/dist/slices/logout/index.js +0 -14
  105. package/dist/slices/map-fields/index.d.ts +0 -2
  106. package/dist/slices/map-fields/index.js +0 -269
  107. package/dist/slices/mark-slice-status/index.d.ts +0 -2
  108. package/dist/slices/mark-slice-status/index.js +0 -31
  109. package/dist/slices/merge/index.d.ts +0 -19
  110. package/dist/slices/merge/index.js +0 -147
  111. package/dist/slices/open-app/index.d.ts +0 -1
  112. package/dist/slices/open-app/index.js +0 -36
  113. package/dist/slices/remove-field/index.d.ts +0 -8
  114. package/dist/slices/remove-field/index.js +0 -167
  115. package/dist/slices/remove-scenario/index.d.ts +0 -2
  116. package/dist/slices/remove-scenario/index.js +0 -77
  117. package/dist/slices/search/index.d.ts +0 -3
  118. package/dist/slices/search/index.js +0 -302
  119. package/dist/slices/show-actor/index.d.ts +0 -4
  120. package/dist/slices/show-actor/index.js +0 -115
  121. package/dist/slices/show-aggregate/index.d.ts +0 -3
  122. package/dist/slices/show-aggregate/index.js +0 -108
  123. package/dist/slices/show-aggregate-completeness/index.d.ts +0 -4
  124. package/dist/slices/show-aggregate-completeness/index.js +0 -181
  125. package/dist/slices/show-chapter/index.d.ts +0 -3
  126. package/dist/slices/show-chapter/index.js +0 -195
  127. package/dist/slices/show-command/index.d.ts +0 -3
  128. package/dist/slices/show-command/index.js +0 -133
  129. package/dist/slices/show-completeness/index.d.ts +0 -4
  130. package/dist/slices/show-completeness/index.js +0 -731
  131. package/dist/slices/show-event/index.d.ts +0 -3
  132. package/dist/slices/show-event/index.js +0 -118
  133. package/dist/slices/show-model-summary/index.d.ts +0 -3
  134. package/dist/slices/show-model-summary/index.js +0 -31
  135. package/dist/slices/show-processor/index.d.ts +0 -3
  136. package/dist/slices/show-processor/index.js +0 -111
  137. package/dist/slices/show-readmodel/index.d.ts +0 -3
  138. package/dist/slices/show-readmodel/index.js +0 -158
  139. package/dist/slices/show-scenario/index.d.ts +0 -3
  140. package/dist/slices/show-scenario/index.js +0 -196
  141. package/dist/slices/show-screen/index.d.ts +0 -3
  142. package/dist/slices/show-screen/index.js +0 -139
  143. package/dist/slices/show-slice/index.d.ts +0 -3
  144. package/dist/slices/show-slice/index.js +0 -696
  145. package/dist/slices/update-field/index.d.ts +0 -15
  146. package/dist/slices/update-field/index.js +0 -208
  147. package/dist/slices/whoami/index.d.ts +0 -2
  148. package/dist/slices/whoami/index.js +0 -44
  149. package/dist/types.d.ts +0 -195
  150. package/dist/types.js +0 -1
@@ -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,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 --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
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,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), use element IDs with the `id:` prefix. This works with every command that accepts an element name \u2014 not just flows. See `eventmodeler guide explore` for the full details.\n\n```bash\n# Find element IDs via list commands\neventmodeler list events\n# <event id=\"abc12345-...\" name=\"OrderPlaced\" fields=\"4\"/>\n# <event id=\"def67890-...\" name=\"OrderPlaced\" fields=\"4\"/> <!-- linked copy -->\n\n# Use ID prefix (first 8 chars is usually enough)\neventmodeler create flow --from \"id:abc12345\" --to \"OrderStatus\"\neventmodeler map fields --flow \"id:abc12345\u2192OrderStatus\" --xml '\n <mapping from=\"orderId\" to=\"orderId\"/>\n'\n```\n\nThe CLI will tell you when names are ambiguous and show the IDs to choose from.\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";