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.
- package/dist/index.js +6776 -2132
- package/package.json +11 -5
- 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/auth.js +0 -331
- 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/config.js +0 -95
- 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/project-config.js +0 -83
- 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/add-scenario/index.js +0 -307
- 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/codegen.js +0 -339
- package/dist/slices/guide/guides/connect-slices.d.ts +0 -5
- package/dist/slices/guide/guides/connect-slices.js +0 -202
- package/dist/slices/guide/guides/create-slices.d.ts +0 -5
- package/dist/slices/guide/guides/create-slices.js +0 -303
- package/dist/slices/guide/guides/explore.d.ts +0 -5
- package/dist/slices/guide/guides/explore.js +0 -251
- package/dist/slices/guide/guides/information-flow.d.ts +0 -5
- package/dist/slices/guide/guides/information-flow.js +0 -318
- package/dist/slices/guide/guides/scenarios.d.ts +0 -5
- package/dist/slices/guide/guides/scenarios.js +0 -269
- package/dist/slices/guide/index.d.ts +0 -1
- package/dist/slices/guide/index.js +0 -40
- 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/init/index.js +0 -80
- 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/login/index.js +0 -20
- package/dist/slices/logout/index.d.ts +0 -1
- package/dist/slices/logout/index.js +0 -14
- 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/open-app/index.js +0 -36
- 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/slices/whoami/index.js +0 -44
- package/dist/types.d.ts +0 -195
- package/dist/types.js +0 -1
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
export const meta = {
|
|
2
|
-
name: 'scenarios',
|
|
3
|
-
description: 'Design Given-When-Then scenarios for slices: happy paths, errors, automations',
|
|
4
|
-
};
|
|
5
|
-
export const content = `
|
|
6
|
-
# Designing Scenarios for Slices
|
|
7
|
-
|
|
8
|
-
Scenarios define the behavior of a slice. There are two main scenario patterns:
|
|
9
|
-
|
|
10
|
-
1. **State-Change Scenarios** (for state-change slices): Given events → When command → Then events/error
|
|
11
|
-
2. **Automation Scenarios** (for automation slices): Given events → Then command (simpler Given-Then format)
|
|
12
|
-
|
|
13
|
-
## Your Workflow
|
|
14
|
-
|
|
15
|
-
When helping design scenarios for a slice:
|
|
16
|
-
|
|
17
|
-
### 1. Gather Context
|
|
18
|
-
\`\`\`bash
|
|
19
|
-
# Look at the slice to understand its components
|
|
20
|
-
eventmodeler show slice "<slice-name>"
|
|
21
|
-
|
|
22
|
-
# See what chapter it belongs to and nearby slices for context
|
|
23
|
-
eventmodeler list chapters
|
|
24
|
-
eventmodeler show chapter "<chapter-name>"
|
|
25
|
-
|
|
26
|
-
# List all events to understand what's available for Given clauses
|
|
27
|
-
eventmodeler list events
|
|
28
|
-
\`\`\`
|
|
29
|
-
|
|
30
|
-
### 2. Identify the Slice Type
|
|
31
|
-
|
|
32
|
-
**State-Change Slice** (Screen → Command → Event):
|
|
33
|
-
- User triggers a command
|
|
34
|
-
- Command produces event(s)
|
|
35
|
-
- Use: Given → When command → Then events/error
|
|
36
|
-
|
|
37
|
-
**Automation Slice** (ReadModel → Processor → Command → Event):
|
|
38
|
-
- Events trigger the processor to issue a command
|
|
39
|
-
- Use simpler Given → Then format (no When)
|
|
40
|
-
- Given: All events that lead to the trigger condition
|
|
41
|
-
- Then: The command that should be dispatched (or noCommand)
|
|
42
|
-
|
|
43
|
-
### 3. Design Scenarios by Type
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## State-Change Scenarios
|
|
48
|
-
|
|
49
|
-
For slices where a user action triggers a command:
|
|
50
|
-
|
|
51
|
-
**Happy Path** - The command succeeds under normal conditions
|
|
52
|
-
- Given: Any required preconditions (existing events)
|
|
53
|
-
- When: The command with valid input
|
|
54
|
-
- Then: The expected event(s) with field values
|
|
55
|
-
|
|
56
|
-
**Validation Errors** - Bad input is rejected
|
|
57
|
-
- Given: (may be empty)
|
|
58
|
-
- When: Command with invalid input
|
|
59
|
-
- Then: Error with message
|
|
60
|
-
|
|
61
|
-
**Business Rule Violations** - Valid input but rule prevents action
|
|
62
|
-
- Given: Events that create a state where the action isn't allowed
|
|
63
|
-
- When: The command
|
|
64
|
-
- Then: Error explaining why
|
|
65
|
-
|
|
66
|
-
### State-Change Examples
|
|
67
|
-
|
|
68
|
-
\`\`\`bash
|
|
69
|
-
# Happy path - command succeeds
|
|
70
|
-
eventmodeler add scenario --slice "<slice-name>" --xml '<scenario name="Successfully place order" description="A registered customer can place an order with valid items">
|
|
71
|
-
<given>
|
|
72
|
-
<event name="CustomerRegistered">
|
|
73
|
-
<field name="customerId">cust-123</field>
|
|
74
|
-
</event>
|
|
75
|
-
</given>
|
|
76
|
-
<when>
|
|
77
|
-
<command name="PlaceOrder">
|
|
78
|
-
<field name="customerId">cust-123</field>
|
|
79
|
-
<field name="items">[{"productId": "prod-1", "quantity": 2}]</field>
|
|
80
|
-
</command>
|
|
81
|
-
</when>
|
|
82
|
-
<then type="events">
|
|
83
|
-
<event name="OrderPlaced">
|
|
84
|
-
<field name="orderId">order-456</field>
|
|
85
|
-
<field name="customerId">cust-123</field>
|
|
86
|
-
</event>
|
|
87
|
-
</then>
|
|
88
|
-
</scenario>'
|
|
89
|
-
\`\`\`
|
|
90
|
-
|
|
91
|
-
\`\`\`bash
|
|
92
|
-
# Validation error
|
|
93
|
-
eventmodeler 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">
|
|
94
|
-
<when>
|
|
95
|
-
<command name="PlaceOrder">
|
|
96
|
-
<field name="customerId">cust-123</field>
|
|
97
|
-
<field name="items">[]</field>
|
|
98
|
-
</command>
|
|
99
|
-
</when>
|
|
100
|
-
<then type="error" errorType="ValidationError">Order must contain at least one item</then>
|
|
101
|
-
</scenario>'
|
|
102
|
-
\`\`\`
|
|
103
|
-
|
|
104
|
-
\`\`\`bash
|
|
105
|
-
# Business rule violation
|
|
106
|
-
eventmodeler 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">
|
|
107
|
-
<given>
|
|
108
|
-
<event name="OrderPlaced">
|
|
109
|
-
<field name="orderId">order-123</field>
|
|
110
|
-
</event>
|
|
111
|
-
<event name="OrderShipped">
|
|
112
|
-
<field name="orderId">order-123</field>
|
|
113
|
-
<field name="shippedAt">2024-01-15T10:00:00Z</field>
|
|
114
|
-
</event>
|
|
115
|
-
</given>
|
|
116
|
-
<when>
|
|
117
|
-
<command name="CancelOrder">
|
|
118
|
-
<field name="orderId">order-123</field>
|
|
119
|
-
</command>
|
|
120
|
-
</when>
|
|
121
|
-
<then type="error" errorType="BusinessRuleViolation">Cannot cancel an order that has already shipped</then>
|
|
122
|
-
</scenario>'
|
|
123
|
-
\`\`\`
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Automation Scenarios
|
|
128
|
-
|
|
129
|
-
For slices where events trigger a processor to issue a command. Use the simpler **Given-Then** format:
|
|
130
|
-
|
|
131
|
-
**Trigger Conditions Met** - Events cause processor to act
|
|
132
|
-
- Given: All the events that have occurred (including the trigger events)
|
|
133
|
-
- Then: The command the processor issues
|
|
134
|
-
|
|
135
|
-
**Trigger Conditions Not Met** - Events don't cause action
|
|
136
|
-
- Given: Events that don't meet the trigger condition
|
|
137
|
-
- Then: noCommand (processor should not dispatch)
|
|
138
|
-
|
|
139
|
-
### Automation Examples
|
|
140
|
-
|
|
141
|
-
\`\`\`bash
|
|
142
|
-
# Payment received triggers shipment initiation
|
|
143
|
-
# Given: OrderPlaced + PaymentReceived → Then: InitiateShipment command
|
|
144
|
-
eventmodeler 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">
|
|
145
|
-
<given>
|
|
146
|
-
<event name="OrderPlaced">
|
|
147
|
-
<field name="orderId">order-123</field>
|
|
148
|
-
<field name="customerId">cust-456</field>
|
|
149
|
-
</event>
|
|
150
|
-
<event name="PaymentReceived">
|
|
151
|
-
<field name="orderId">order-123</field>
|
|
152
|
-
<field name="amount">99.99</field>
|
|
153
|
-
</event>
|
|
154
|
-
</given>
|
|
155
|
-
<then type="command">
|
|
156
|
-
<command name="InitiateShipment">
|
|
157
|
-
<field name="orderId">order-123</field>
|
|
158
|
-
<field name="warehouseId">wh-001</field>
|
|
159
|
-
</command>
|
|
160
|
-
</then>
|
|
161
|
-
</scenario>'
|
|
162
|
-
\`\`\`
|
|
163
|
-
|
|
164
|
-
\`\`\`bash
|
|
165
|
-
# Negative case - conditions not met, no command dispatched
|
|
166
|
-
eventmodeler 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">
|
|
167
|
-
<given>
|
|
168
|
-
<event name="OrderPlaced">
|
|
169
|
-
<field name="orderId">order-123</field>
|
|
170
|
-
<field name="customerId">cust-456</field>
|
|
171
|
-
</event>
|
|
172
|
-
</given>
|
|
173
|
-
<then type="noCommand"/>
|
|
174
|
-
</scenario>'
|
|
175
|
-
\`\`\`
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## Read Model Assertions
|
|
180
|
-
|
|
181
|
-
For state-view slices, test that events project correctly to read models:
|
|
182
|
-
|
|
183
|
-
\`\`\`bash
|
|
184
|
-
eventmodeler 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">
|
|
185
|
-
<then type="readModelAssertion">
|
|
186
|
-
<read-model name="OrderSummary">
|
|
187
|
-
<field name="orderId">order-456</field>
|
|
188
|
-
<field name="status">pending</field>
|
|
189
|
-
</read-model>
|
|
190
|
-
</then>
|
|
191
|
-
</scenario>'
|
|
192
|
-
\`\`\`
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
## Scenario Types Summary
|
|
197
|
-
|
|
198
|
-
| Slice Type | Format | Then Contains |
|
|
199
|
-
|------------|--------|---------------|
|
|
200
|
-
| State-Change | Given → When (Command) → Then | Events or Error |
|
|
201
|
-
| Automation | Given (Events) → Then | Command or noCommand |
|
|
202
|
-
| State-View | Given → Then | Read Model Assertion |
|
|
203
|
-
|
|
204
|
-
## JSON Format Alternative
|
|
205
|
-
|
|
206
|
-
You can also use JSON format:
|
|
207
|
-
|
|
208
|
-
**State-Change Scenario:**
|
|
209
|
-
\`\`\`bash
|
|
210
|
-
eventmodeler add scenario --slice "<slice-name>" --json '{
|
|
211
|
-
"name": "Successfully place order",
|
|
212
|
-
"description": "A registered customer can place an order",
|
|
213
|
-
"given": [
|
|
214
|
-
{ "event": "CustomerRegistered", "fieldValues": { "customerId": "cust-123" } }
|
|
215
|
-
],
|
|
216
|
-
"when": {
|
|
217
|
-
"command": "PlaceOrder",
|
|
218
|
-
"commandFieldValues": { "customerId": "cust-123", "items": [] }
|
|
219
|
-
},
|
|
220
|
-
"then": {
|
|
221
|
-
"type": "events",
|
|
222
|
-
"events": [
|
|
223
|
-
{ "event": "OrderPlaced", "fieldValues": { "orderId": "order-456" } }
|
|
224
|
-
]
|
|
225
|
-
}
|
|
226
|
-
}'
|
|
227
|
-
\`\`\`
|
|
228
|
-
|
|
229
|
-
**Automation Scenario (Given-Then format):**
|
|
230
|
-
\`\`\`bash
|
|
231
|
-
eventmodeler add scenario --slice "<slice-name>" --json '{
|
|
232
|
-
"name": "Payment triggers shipment",
|
|
233
|
-
"description": "Given order placed and payment received, shipment is initiated",
|
|
234
|
-
"given": [
|
|
235
|
-
{ "event": "OrderPlaced", "fieldValues": { "orderId": "order-123" } },
|
|
236
|
-
{ "event": "PaymentReceived", "fieldValues": { "orderId": "order-123", "amount": 99.99 } }
|
|
237
|
-
],
|
|
238
|
-
"then": {
|
|
239
|
-
"type": "command",
|
|
240
|
-
"command": "InitiateShipment",
|
|
241
|
-
"commandFieldValues": { "orderId": "order-123" }
|
|
242
|
-
}
|
|
243
|
-
}'
|
|
244
|
-
\`\`\`
|
|
245
|
-
|
|
246
|
-
**Automation Scenario (negative case):**
|
|
247
|
-
\`\`\`bash
|
|
248
|
-
eventmodeler add scenario --slice "<slice-name>" --json '{
|
|
249
|
-
"name": "No shipment without payment",
|
|
250
|
-
"description": "Given only order placed (no payment), no command dispatched",
|
|
251
|
-
"given": [
|
|
252
|
-
{ "event": "OrderPlaced", "fieldValues": { "orderId": "order-123" } }
|
|
253
|
-
],
|
|
254
|
-
"then": {
|
|
255
|
-
"type": "noCommand"
|
|
256
|
-
}
|
|
257
|
-
}'
|
|
258
|
-
\`\`\`
|
|
259
|
-
|
|
260
|
-
## Best Practices
|
|
261
|
-
|
|
262
|
-
1. **Write clear descriptions** - Explain why this scenario matters and what business rule it captures
|
|
263
|
-
2. **Use realistic example values** - "cust-123" not "string", "2024-01-15" not "date"
|
|
264
|
-
3. **Name scenarios descriptively** - "Cannot cancel shipped order" not "Error case 1"
|
|
265
|
-
4. **Include all relevant fields** - Don't skip fields that matter for the scenario
|
|
266
|
-
5. **Think about state** - What events need to exist for this scenario to make sense?
|
|
267
|
-
6. **Cover the negative cases** - These often reveal missing business rules (use \`noCommand\` for automations)
|
|
268
|
-
7. **Match format to slice type** - Use Given-When-Then for state-change, Given-Then for automation
|
|
269
|
-
`;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runGuide(subcommand: string | undefined): void;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { meta as exploreMeta, content as exploreContent } from './guides/explore.js';
|
|
2
|
-
import { meta as scenariosMeta, content as scenariosContent } from './guides/scenarios.js';
|
|
3
|
-
import { meta as informationFlowMeta, content as informationFlowContent } from './guides/information-flow.js';
|
|
4
|
-
import { meta as createSlicesMeta, content as createSlicesContent } from './guides/create-slices.js';
|
|
5
|
-
import { meta as connectSlicesMeta, content as connectSlicesContent } from './guides/connect-slices.js';
|
|
6
|
-
import { meta as codegenMeta, content as codegenContent } from './guides/codegen.js';
|
|
7
|
-
const GUIDES = [
|
|
8
|
-
{ meta: exploreMeta, content: exploreContent },
|
|
9
|
-
{ meta: createSlicesMeta, content: createSlicesContent },
|
|
10
|
-
{ meta: connectSlicesMeta, content: connectSlicesContent },
|
|
11
|
-
{ meta: informationFlowMeta, content: informationFlowContent },
|
|
12
|
-
{ meta: scenariosMeta, content: scenariosContent },
|
|
13
|
-
{ meta: codegenMeta, content: codegenContent },
|
|
14
|
-
];
|
|
15
|
-
export function runGuide(subcommand) {
|
|
16
|
-
if (!subcommand) {
|
|
17
|
-
printGuideList();
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
const guide = GUIDES.find(g => g.meta.name === subcommand);
|
|
21
|
-
if (!guide) {
|
|
22
|
-
console.error(`Unknown guide: ${subcommand}`);
|
|
23
|
-
console.error(`Run "eventmodeler guide" to see available guides.`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
console.log(guide.content);
|
|
27
|
-
}
|
|
28
|
-
function printGuideList() {
|
|
29
|
-
console.log(`
|
|
30
|
-
eventmodeler guide - Conceptual guides for working with event models
|
|
31
|
-
|
|
32
|
-
USAGE:
|
|
33
|
-
eventmodeler guide <name>
|
|
34
|
-
|
|
35
|
-
AVAILABLE GUIDES:
|
|
36
|
-
${GUIDES.map(g => ` ${g.meta.name.padEnd(20)} ${g.meta.description}`).join('\n')}
|
|
37
|
-
|
|
38
|
-
Run "eventmodeler guide <name>" to read a guide.
|
|
39
|
-
`);
|
|
40
|
-
}
|
|
@@ -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,80 +0,0 @@
|
|
|
1
|
-
import * as readline from 'node:readline';
|
|
2
|
-
import { isAuthenticated } from '../../lib/config.js';
|
|
3
|
-
import { listModels } from '../../api/index.js';
|
|
4
|
-
import { saveProjectConfig, getProjectRoot, isInProject } from '../../lib/project-config.js';
|
|
5
|
-
/**
|
|
6
|
-
* Interactive init command to set up a project.
|
|
7
|
-
* Connects the current directory to an event model.
|
|
8
|
-
*/
|
|
9
|
-
export async function init() {
|
|
10
|
-
if (isInProject()) {
|
|
11
|
-
console.log('This project is already initialized.');
|
|
12
|
-
console.log('To reconfigure, delete .eventmodeler.json and run init again.');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
const projectRoot = getProjectRoot();
|
|
16
|
-
console.log(`Initializing event model project in: ${projectRoot}\n`);
|
|
17
|
-
if (!isAuthenticated()) {
|
|
18
|
-
console.log('You need to be logged in first.');
|
|
19
|
-
console.log('Run "eventmodeler login" first, then try init again.');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const rl = readline.createInterface({
|
|
23
|
-
input: process.stdin,
|
|
24
|
-
output: process.stdout,
|
|
25
|
-
});
|
|
26
|
-
const question = (prompt) => {
|
|
27
|
-
return new Promise((resolve) => {
|
|
28
|
-
rl.question(prompt, (answer) => {
|
|
29
|
-
resolve(answer.trim());
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
};
|
|
33
|
-
try {
|
|
34
|
-
console.log('Fetching your event models...\n');
|
|
35
|
-
const models = await listModels();
|
|
36
|
-
if (models.length === 0) {
|
|
37
|
-
console.log('You don\'t have any event models yet.');
|
|
38
|
-
console.log('Create one at https://eventmodeler.app first, then run init again.');
|
|
39
|
-
rl.close();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
console.log('Your event models:\n');
|
|
43
|
-
models.forEach((model, index) => {
|
|
44
|
-
console.log(` ${index + 1}. ${model.name} (${model.modelId})`);
|
|
45
|
-
});
|
|
46
|
-
console.log(` ${models.length + 1}. Create a new model\n`);
|
|
47
|
-
const modelChoice = await question(`Select a model (1-${models.length + 1}): `);
|
|
48
|
-
const modelIndex = parseInt(modelChoice, 10) - 1;
|
|
49
|
-
let selectedModel;
|
|
50
|
-
if (modelIndex === models.length) {
|
|
51
|
-
console.log('\nTo create a new model, visit https://eventmodeler.app');
|
|
52
|
-
console.log('After creating it, run init again to link it to this project.');
|
|
53
|
-
rl.close();
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
else if (modelIndex >= 0 && modelIndex < models.length) {
|
|
57
|
-
selectedModel = models[modelIndex];
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
console.log('\nInvalid choice. Please run init again.');
|
|
61
|
-
rl.close();
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const config = {
|
|
65
|
-
type: 'cloud',
|
|
66
|
-
modelId: selectedModel.modelId,
|
|
67
|
-
modelName: selectedModel.name,
|
|
68
|
-
};
|
|
69
|
-
const configPath = saveProjectConfig(config, projectRoot);
|
|
70
|
-
console.log(`\nProject initialized successfully!`);
|
|
71
|
-
console.log(`Config saved to: ${configPath}`);
|
|
72
|
-
console.log(`\nLinked to model: ${selectedModel.name}`);
|
|
73
|
-
console.log('\nYou can now use eventmodeler commands in this directory.');
|
|
74
|
-
rl.close();
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
rl.close();
|
|
78
|
-
throw err;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -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
|
-
}
|