eventmodeler 0.6.1 → 0.6.3

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 (3) hide show
  1. package/README.md +221 -91
  2. package/dist/index.js +281 -73
  3. package/package.json +5 -1
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # eventmodeler
2
2
 
3
- CLI tool for interacting with [Event Model](https://eventmodeling.org) files. Query, update, and export event models from the terminal.
4
-
5
- Works with `.eventmodel` files created by the [Event Modeling App](https://www.eventmodeling.app).
3
+ Command line client for Event Modeler cloud models. Use it to inspect a model, place elements on the canvas, maintain fields and scenarios, and drive code generation loops from a terminal or agent.
6
4
 
7
5
  ## Installation
8
6
 
@@ -10,166 +8,298 @@ Works with `.eventmodel` files created by the [Event Modeling App](https://www.e
10
8
  npm install -g eventmodeler
11
9
  ```
12
10
 
13
- ## Quick Start
14
-
15
- ```bash
16
- # Open Event Modeling app in browser
17
- eventmodeler
11
+ Requires Node.js 18 or newer.
18
12
 
19
- # List all slices
20
- eventmodeler list slices
13
+ ## Setup
21
14
 
22
- # Show details of a slice
23
- eventmodeler show slice "Place Order"
15
+ ```bash
16
+ # Authenticate in the browser
17
+ eventmodeler login
24
18
 
25
- # Search for elements
26
- eventmodeler search "order"
19
+ # Create a cloud model, or use an existing model id from the app
20
+ eventmodeler create model "Ordering"
27
21
 
28
- # Get JSON output instead of XML
29
- eventmodeler list slices --format json
22
+ # Link the current repo/directory to a model
23
+ eventmodeler init
30
24
  ```
31
25
 
32
- ## Commands
26
+ `eventmodeler init` writes `.eventmodeler.json` in your project. Most commands read the model id from that file. You can also target an element directly with the global `--id <uuid>` option when a command accepts an optional name.
33
27
 
34
- ### List Commands
28
+ Useful auth commands:
35
29
 
36
30
  ```bash
37
- eventmodeler list slices # List all slices with status
38
- eventmodeler list events # List all events
39
- eventmodeler list commands # List all commands
40
- eventmodeler list chapters # List all chapters
41
- eventmodeler list aggregates # List all aggregates
42
- eventmodeler list actors # List all actors
31
+ eventmodeler whoami
32
+ eventmodeler logout
43
33
  ```
44
34
 
45
- ### Show Commands
35
+ ## Common Workflow
46
36
 
47
37
  ```bash
48
- eventmodeler show slice <name> # Show detailed view of a slice
49
- eventmodeler show event <name> # Show detailed view of an event
50
- eventmodeler show command <name> # Show detailed view of a command
51
- eventmodeler show chapter <name> # Show chapter with its slices
52
- eventmodeler show actor <name> # Show actor with its screens
38
+ # Explore the model
39
+ eventmodeler show model
40
+ eventmodeler list slices
41
+ eventmodeler show slice "Place Order"
42
+ eventmodeler search order
43
+
44
+ # Build out the canvas
45
+ eventmodeler create place command PlaceOrder --x 120 --y 200
46
+ eventmodeler create place event OrderPlaced --x 120 --y 360
47
+ eventmodeler create flow --from PlaceOrder --to OrderPlaced
48
+
49
+ # Add fields
50
+ eventmodeler add field PlaceOrder '{"name":"orderId","type":"UUID"}'
51
+ eventmodeler add subfield PlaceOrder --field customer '{"name":"email","type":"String"}'
52
+
53
+ # Create a scenario atomically
54
+ eventmodeler create scenario --slice "Place Order" '{
55
+ "name": "happy path",
56
+ "when": [
57
+ {
58
+ "entryType": "command",
59
+ "elementName": "PlaceOrder",
60
+ "fieldValues": { "orderId": "uuid-1" }
61
+ }
62
+ ],
63
+ "then": [
64
+ {
65
+ "entryType": "event",
66
+ "elementName": "OrderPlaced",
67
+ "fieldValues": { "orderId": "uuid-1" }
68
+ }
69
+ ]
70
+ }'
53
71
  ```
54
72
 
55
- ### Completeness Commands
73
+ ## Read Commands
56
74
 
57
75
  ```bash
58
- eventmodeler show completeness <name> # Show field mapping completeness for any element
59
- eventmodeler show model-completeness # Show completeness of all flows in the model
60
- eventmodeler show aggregate-completeness <name> # Check if events have the aggregate ID field
76
+ eventmodeler show model
77
+ eventmodeler show context [name]
78
+ eventmodeler show chapter [name]
79
+ eventmodeler show slice [name]
80
+ eventmodeler show event [name]
81
+ eventmodeler show command [name]
82
+ eventmodeler show readmodel [name]
83
+ eventmodeler show screen [name]
84
+ eventmodeler show processor [name]
85
+ eventmodeler show external-event [name]
86
+ eventmodeler show completeness
87
+ eventmodeler summary
88
+ eventmodeler search <term>
61
89
  ```
62
90
 
63
- ### Search
91
+ List commands:
64
92
 
65
93
  ```bash
66
- eventmodeler search <term> # Search for entities by name
94
+ eventmodeler list slices [--chapter <name>]
95
+ eventmodeler list events
96
+ eventmodeler list commands
97
+ eventmodeler list readmodels
98
+ eventmodeler list screens
99
+ eventmodeler list processors
100
+ eventmodeler list external-events
101
+ eventmodeler list aggregates
102
+ eventmodeler list actors
103
+ eventmodeler list chapters
104
+ eventmodeler list contexts
105
+ eventmodeler list swimlanes
106
+ eventmodeler list notes
107
+ eventmodeler list scenarios
67
108
  ```
68
109
 
69
- ### Modify Commands
110
+ Output is JSON from the backend SDK. Use `eventmodeler help <topic>` or command-specific `--help` for schema examples.
111
+
112
+ ## Canvas And Model Editing
113
+
114
+ Create models, slices, elements, flows, and linked copies:
70
115
 
71
116
  ```bash
72
- # Mark slice status
73
- eventmodeler mark "Place Order" done
74
- eventmodeler mark "Cancel Order" in-progress
117
+ eventmodeler create model <name>
118
+ eventmodeler create slice '<json>'
119
+ eventmodeler create place <type> <name> --x <n> --y <n>
120
+ eventmodeler create flow --from <source> --to <target>
121
+ eventmodeler create place-copy <type> [originalName] --x <n> --y <n>
122
+ eventmodeler create move-copy <type> [name] --x <n> --y <n>
123
+ eventmodeler create remove-copy <type> [name]
124
+ ```
75
125
 
76
- # Add scenario to a slice
77
- eventmodeler add scenario --slice "Place Order" --json '{"name": "Happy path", ...}'
126
+ Element types for `create place`:
78
127
 
79
- # Add field to an entity
80
- eventmodeler add field --event "OrderPlaced" --json '{"name": "orderId", "type": "UUID"}'
128
+ ```text
129
+ command, event, readmodel, screen, processor, external-event,
130
+ aggregate, actor, chapter, context, note, swimlane, slice
131
+ ```
81
132
 
82
- # Remove field
83
- eventmodeler remove field --event "OrderPlaced" --field "legacyId"
133
+ Linked copies are supported for:
84
134
 
85
- # Update field properties
86
- eventmodeler update field --read-model "OrderSummary" --field "notes" --optional true
135
+ ```text
136
+ event, readmodel, screen, external-event
137
+ ```
87
138
 
88
- # Map fields between elements
89
- eventmodeler map fields --flow "OrderPlaced→OrderSummary" --json '[{"from": "total", "to": "totalAmount"}]'
139
+ Move, resize, rename, and remove existing elements:
140
+
141
+ ```bash
142
+ eventmodeler move <type> [name] --x <n> --y <n>
143
+ eventmodeler resize <type> [name] --width <n> --height <n>
144
+ eventmodeler rename <type> <oldName> <newName>
145
+ eventmodeler remove <type> [name]
146
+ eventmodeler remove flow --from <source> --to <target>
90
147
  ```
91
148
 
92
- ### Export
149
+ Mark slice status:
93
150
 
94
151
  ```bash
95
- eventmodeler summary # Show model summary statistics
96
- eventmodeler export json # Export entire model as JSON
152
+ eventmodeler mark "Place Order" planned
153
+ eventmodeler mark "Place Order" created
154
+ eventmodeler mark "Place Order" in-progress
155
+ eventmodeler mark "Place Order" blocked
156
+ eventmodeler mark "Place Order" done
97
157
  ```
98
158
 
99
- ## Output Format
159
+ ## Fields
160
+
161
+ Fields are supported on commands, events, readmodels, screens, processors, and external events.
162
+
163
+ ```bash
164
+ eventmodeler add field [elementName] '{"name":"customerId","type":"UUID"}'
165
+ eventmodeler add subfield [elementName] --field <parentFieldName> '{"name":"email","type":"String"}'
166
+
167
+ eventmodeler update field [elementName] --field <name> --name <newName>
168
+ eventmodeler update field [elementName] --field <name> --type <newType>
169
+ eventmodeler update field [elementName] --field <name> --optional true
170
+ eventmodeler update field [elementName] --field <name> --generated false
171
+ eventmodeler update field [elementName] --field <name> --list true
172
+ eventmodeler update field [elementName] --field <name> --user-input true
173
+
174
+ eventmodeler update subfield [elementName] --subfield <id> --name <newName>
175
+ eventmodeler update subfield [elementName] --subfield <id> --type <newType>
176
+ eventmodeler update reorder [elementName] --field <name> --position <n>
177
+ eventmodeler update reorder-subfield [elementName] --subfield <id> --position <n>
178
+
179
+ eventmodeler remove field [elementName] --field <name>
180
+ eventmodeler remove subfield [elementName] --subfield <id>
181
+ eventmodeler set aggregate-id [aggregateName] --field-name <name> --field-type UUID
182
+ ```
100
183
 
101
- By default, output is in XML format (optimized for AI agents). Use `--format json` for JSON output:
184
+ For field mappings:
102
185
 
103
186
  ```bash
104
- eventmodeler list slices --format json
105
- eventmodeler show slice "Place Order" --format json
187
+ eventmodeler map fields '[{"source":"orderId","target":"orderId"}]' --from PlaceOrder --to OrderPlaced
188
+ eventmodeler remove mapping --from PlaceOrder --to OrderPlaced --mapping <mappingId>
106
189
  ```
107
190
 
108
- ### Setting Default Format
191
+ ## Scenarios
109
192
 
110
- Set your preferred default format via:
193
+ Create a full Given/When/Then scenario in one backend transaction:
111
194
 
112
- **Environment variable:**
113
195
  ```bash
114
- export EVENTMODELER_FORMAT=json
196
+ eventmodeler add scenario --slice "Place Order" '<scenario-json>'
197
+ eventmodeler create scenario --slice "Place Order" '<scenario-json>'
115
198
  ```
116
199
 
117
- **Config file** (`~/.eventmodeler/config.json`):
200
+ Scenario JSON shape:
201
+
118
202
  ```json
119
203
  {
120
- "format": "json"
204
+ "name": "happy path",
205
+ "description": "optional",
206
+ "given": [
207
+ { "entryType": "event", "elementName": "OrderPlaced" }
208
+ ],
209
+ "when": [
210
+ { "entryType": "command", "elementName": "PlaceOrder" }
211
+ ],
212
+ "then": [
213
+ { "entryType": "event", "elementName": "OrderPlaced" },
214
+ { "entryType": "error", "errorType": "Validation", "errorMessage": "Invalid order" }
215
+ ]
121
216
  }
122
217
  ```
123
218
 
124
- Priority: CLI flag > environment variable > config file > default (xml)
125
-
126
- ## Options
219
+ Allowed entry types:
127
220
 
128
- ```
129
- -f, --file <path> Path to .eventmodel file (default: auto-detect)
130
- --format <xml|json> Output format (default: xml)
131
- -h, --help Show help message
132
- -v, --version Show version number
221
+ ```text
222
+ given: event, readmodel
223
+ when: command, event
224
+ then: event, command, readmodel, error
133
225
  ```
134
226
 
135
- ## Use Cases
227
+ Example values can be provided in the scenario JSON via `fieldValues`, or edited later:
228
+
229
+ ```bash
230
+ eventmodeler add entry --scenario "happy path" --section then --type event --element OrderPlaced
231
+ eventmodeler set example --scenario "happy path" --entry <entryId> --type event --field orderId --value uuid-1
232
+ eventmodeler set example --scenario "happy path" --entry <entryId> --type event --field tags --values vip,web
233
+ eventmodeler set example --scenario "happy path" --entry <entryId> --type event --field address --json '{"city":"NYC"}'
234
+ eventmodeler set description --scenario "happy path" --text "Updated description"
235
+ eventmodeler set position --scenario "happy path" --section then --entry <entryId> --position 0
236
+ eventmodeler remove example --scenario "happy path" --entry <entryId> --type event --field orderId
237
+ eventmodeler remove entry --scenario "happy path" --section then --entry <entryId>
238
+ eventmodeler remove scenario "happy path"
239
+ ```
136
240
 
137
- ### For AI Agents
241
+ Primitive example values are strings. Custom fields use JSON objects, and Custom list fields use arrays of objects.
138
242
 
139
- The CLI is designed to work well with AI coding assistants. The XML output format provides structured, readable data that AI agents can easily parse and understand:
243
+ ## Screen And Note Details
140
244
 
141
245
  ```bash
142
- eventmodeler show slice "Place Order"
246
+ eventmodeler design <screenName> '<excalidraw-json-array>'
247
+ eventmodeler set note-description [noteName] --text "Markdown or plain text"
143
248
  ```
144
249
 
145
- ### For Code Generators
250
+ ## Automation Loop
146
251
 
147
- Use JSON output to build code generators that read your event model:
252
+ The CLI can poll planned slices and run your generator command for each one.
148
253
 
149
254
  ```bash
150
- eventmodeler list events --format json | your-codegen-tool
151
- eventmodeler show slice "Place Order" --format json
255
+ eventmodeler init loop
256
+ eventmodeler loop
152
257
  ```
153
258
 
154
- ### For CI/CD
259
+ `eventmodeler init loop` adds a `loop` block to `.eventmodeler.json` and creates a sample `generate.sh`. The generated script marks a slice in progress, exports slice JSON, runs your codegen placeholder, and marks the slice done or blocked.
155
260
 
156
- Check model completeness in your pipeline:
261
+ ## Configuration
157
262
 
158
- ```bash
159
- eventmodeler show model-completeness --format json
263
+ Global config lives at:
264
+
265
+ ```text
266
+ ~/.eventmodeler/config.json
267
+ ```
268
+
269
+ Project config lives at:
270
+
271
+ ```text
272
+ .eventmodeler.json
273
+ ```
274
+
275
+ The production backend is used by default:
276
+
277
+ ```text
278
+ https://api.eventmodeler.com
160
279
  ```
161
280
 
162
- ## Requirements
281
+ Override endpoints when developing locally:
163
282
 
164
- - Node.js >= 18.0.0
165
- - A `.eventmodel` file in the current directory (or specify with `-f`)
283
+ ```bash
284
+ export EVENTMODELER_BACKEND_URL=http://localhost:8080
285
+ export EVENTMODELER_KEYCLOAK_URL=http://localhost:18180/realms/eventmodeler
286
+ ```
287
+
288
+ ## Help Topics
166
289
 
167
- ## License
290
+ Task-oriented help is built into the CLI:
168
291
 
169
- MIT
292
+ ```bash
293
+ eventmodeler help
294
+ eventmodeler help build-slice
295
+ eventmodeler help write-scenarios
296
+ eventmodeler help manipulate-canvas
297
+ eventmodeler help linked-copies
298
+ eventmodeler help check-completeness
299
+ eventmodeler guide json-reference
300
+ ```
170
301
 
171
302
  ## Links
172
303
 
173
- - [Event Modeling App](https://www.eventmodeling.app)
174
- - [Event Modeling](https://eventmodeling.org)
175
- - [GitHub Repository](https://github.com/theoema/event-modeler)
304
+ - Event Modeling: https://eventmodeling.org
305
+ - GitHub: https://github.com/theoema/event-modeler
package/dist/index.js CHANGED
@@ -1234,6 +1234,15 @@ var createClient = (config = {}) => {
1234
1234
  var client = createClient(createClientConfig(createConfig({ baseUrl: "http://localhost:8080" })));
1235
1235
 
1236
1236
  // src/api/generated/sdk.gen.ts
1237
+ var resizeSwimLane = (options) => (options.client ?? client).post({
1238
+ security: [{ scheme: "bearer", type: "http" }],
1239
+ url: "/api/swim-lanes/resize-swim-lane",
1240
+ ...options,
1241
+ headers: {
1242
+ "Content-Type": "application/json",
1243
+ ...options.headers
1244
+ }
1245
+ });
1237
1246
  var renameSwimLane = (options) => (options.client ?? client).post({
1238
1247
  security: [{ scheme: "bearer", type: "http" }],
1239
1248
  url: "/api/swim-lanes/rename-swim-lane",
@@ -1270,6 +1279,15 @@ var moveSwimLane = (options) => (options.client ?? client).post({
1270
1279
  ...options.headers
1271
1280
  }
1272
1281
  });
1282
+ var resizeSlice = (options) => (options.client ?? client).post({
1283
+ security: [{ scheme: "bearer", type: "http" }],
1284
+ url: "/api/slices/resize-slice",
1285
+ ...options,
1286
+ headers: {
1287
+ "Content-Type": "application/json",
1288
+ ...options.headers
1289
+ }
1290
+ });
1273
1291
  var renameSlice = (options) => (options.client ?? client).post({
1274
1292
  security: [{ scheme: "bearer", type: "http" }],
1275
1293
  url: "/api/slices/rename-slice",
@@ -3097,6 +3115,15 @@ var addEventField = (options) => (options.client ?? client).post({
3097
3115
  ...options.headers
3098
3116
  }
3099
3117
  });
3118
+ var resizeContext = (options) => (options.client ?? client).post({
3119
+ security: [{ scheme: "bearer", type: "http" }],
3120
+ url: "/api/contexts/resize-context",
3121
+ ...options,
3122
+ headers: {
3123
+ "Content-Type": "application/json",
3124
+ ...options.headers
3125
+ }
3126
+ });
3100
3127
  var renameContext = (options) => (options.client ?? client).post({
3101
3128
  security: [{ scheme: "bearer", type: "http" }],
3102
3129
  url: "/api/contexts/rename-context",
@@ -3367,6 +3394,15 @@ var addCommandField = (options) => (options.client ?? client).post({
3367
3394
  ...options.headers
3368
3395
  }
3369
3396
  });
3397
+ var resizeChapter = (options) => (options.client ?? client).post({
3398
+ security: [{ scheme: "bearer", type: "http" }],
3399
+ url: "/api/chapters/resize-chapter",
3400
+ ...options,
3401
+ headers: {
3402
+ "Content-Type": "application/json",
3403
+ ...options.headers
3404
+ }
3405
+ });
3370
3406
  var renameChapter = (options) => (options.client ?? client).post({
3371
3407
  security: [{ scheme: "bearer", type: "http" }],
3372
3408
  url: "/api/chapters/rename-chapter",
@@ -3412,6 +3448,15 @@ var setAggregateIdField = (options) => (options.client ?? client).post({
3412
3448
  ...options.headers
3413
3449
  }
3414
3450
  });
3451
+ var resizeAggregate = (options) => (options.client ?? client).post({
3452
+ security: [{ scheme: "bearer", type: "http" }],
3453
+ url: "/api/aggregates/resize-aggregate",
3454
+ ...options,
3455
+ headers: {
3456
+ "Content-Type": "application/json",
3457
+ ...options.headers
3458
+ }
3459
+ });
3415
3460
  var renameAggregate = (options) => (options.client ?? client).post({
3416
3461
  security: [{ scheme: "bearer", type: "http" }],
3417
3462
  url: "/api/aggregates/rename-aggregate",
@@ -3448,6 +3493,15 @@ var moveAggregate = (options) => (options.client ?? client).post({
3448
3493
  ...options.headers
3449
3494
  }
3450
3495
  });
3496
+ var resizeActor = (options) => (options.client ?? client).post({
3497
+ security: [{ scheme: "bearer", type: "http" }],
3498
+ url: "/api/actors/resize-actor",
3499
+ ...options,
3500
+ headers: {
3501
+ "Content-Type": "application/json",
3502
+ ...options.headers
3503
+ }
3504
+ });
3451
3505
  var renameActor = (options) => (options.client ?? client).post({
3452
3506
  security: [{ scheme: "bearer", type: "http" }],
3453
3507
  url: "/api/actors/rename-actor",
@@ -3524,6 +3578,36 @@ var listModels = (options) => (options?.client ?? client).get({
3524
3578
  url: "/api/models",
3525
3579
  ...options
3526
3580
  });
3581
+ var swimLanes = (options) => (options.client ?? client).get({
3582
+ security: [{ scheme: "bearer", type: "http" }],
3583
+ url: "/api/models/{modelId}/swim-lanes",
3584
+ ...options
3585
+ });
3586
+ var slices = (options) => (options.client ?? client).get({
3587
+ security: [{ scheme: "bearer", type: "http" }],
3588
+ url: "/api/models/{modelId}/slices",
3589
+ ...options
3590
+ });
3591
+ var contexts = (options) => (options.client ?? client).get({
3592
+ security: [{ scheme: "bearer", type: "http" }],
3593
+ url: "/api/models/{modelId}/contexts",
3594
+ ...options
3595
+ });
3596
+ var chapters = (options) => (options.client ?? client).get({
3597
+ security: [{ scheme: "bearer", type: "http" }],
3598
+ url: "/api/models/{modelId}/chapters",
3599
+ ...options
3600
+ });
3601
+ var aggregates = (options) => (options.client ?? client).get({
3602
+ security: [{ scheme: "bearer", type: "http" }],
3603
+ url: "/api/models/{modelId}/aggregates",
3604
+ ...options
3605
+ });
3606
+ var actors = (options) => (options.client ?? client).get({
3607
+ security: [{ scheme: "bearer", type: "http" }],
3608
+ url: "/api/models/{modelId}/actors",
3609
+ ...options
3610
+ });
3527
3611
  var listExternalEvents = (options) => (options.client ?? client).get({
3528
3612
  security: [{ scheme: "bearer", type: "http" }],
3529
3613
  url: "/api/external-event-stickies/{modelId}/external-event-list",
@@ -4030,6 +4114,9 @@ EXAMPLE:
4030
4114
  NOTES:
4031
4115
  - The JSON is a positional argument (not a --json flag)
4032
4116
  - Every element and flow needs a unique UUID
4117
+ - Field and subfield UUIDs are generated when omitted
4118
+ - Field flags such as isGenerated, isList, isOptional, and isUserInput are not
4119
+ applied by create slice; add or update them after creation
4033
4120
  - Valid flow pairs: screen->command, command->event, command->external-event,
4034
4121
  event->readmodel, readmodel->screen, readmodel->processor, processor->command,
4035
4122
  external-event->processor
@@ -4238,10 +4325,10 @@ WHEN TO USE: You need to specify slice behavior as Given-When-Then test cases.
4238
4325
  ## Task: Add a full scenario to a slice (compound command)
4239
4326
 
4240
4327
  COMMAND:
4241
- eventmodeler add scenario '<json>' --slice <sliceName>
4328
+ eventmodeler create scenario '<json>' --slice <sliceName>
4242
4329
 
4243
4330
  EXAMPLE (happy path — state-change slice):
4244
- eventmodeler add scenario '{
4331
+ eventmodeler create scenario '{
4245
4332
  "name": "Successfully place order",
4246
4333
  "given": [
4247
4334
  {"entryType": "event", "elementName": "CustomerRegistered", "fieldValues": {"customerId": "cust-001"}}
@@ -4255,7 +4342,7 @@ EXAMPLE (happy path — state-change slice):
4255
4342
  }' --slice "Place Order"
4256
4343
 
4257
4344
  EXAMPLE (error case):
4258
- eventmodeler add scenario '{
4345
+ eventmodeler create scenario '{
4259
4346
  "name": "Reject order with no items",
4260
4347
  "when": [
4261
4348
  {"entryType": "command", "elementName": "PlaceOrder", "fieldValues": {"customerId": "cust-001"}}
@@ -4266,7 +4353,7 @@ EXAMPLE (error case):
4266
4353
  }' --slice "Place Order"
4267
4354
 
4268
4355
  EXAMPLE (readmodel assertion in then):
4269
- eventmodeler add scenario '{
4356
+ eventmodeler create scenario '{
4270
4357
  "name": "Order appears in summary",
4271
4358
  "given": [
4272
4359
  {"entryType": "event", "elementName": "OrderPlaced", "fieldValues": {"orderId": "ord-001", "customerId": "cust-001"}}
@@ -4731,11 +4818,9 @@ WHEN TO USE: You need the exact JSON shape for a CLI command.
4731
4818
 
4732
4819
  Field types: UUID, String, Int, Long, Double, Decimal, Boolean, Date, DateTime, Custom
4733
4820
 
4734
- Optional attributes (add to same JSON object):
4735
- "isOptional": true
4736
- "isGenerated": true
4737
- "isList": true
4738
- "isUserInput": true (screens only)
4821
+ Note: create slice generates field/subfield IDs when omitted. Field metadata
4822
+ flags such as isOptional, isGenerated, isList, and isUserInput are not applied
4823
+ by create slice; add or update those flags after creation.
4739
4824
 
4740
4825
  Custom type with subfields:
4741
4826
  {
@@ -4790,9 +4875,9 @@ WHEN TO USE: You need the exact JSON shape for a CLI command.
4790
4875
  { "source": "sourceFieldName", "target": "targetFieldName" }
4791
4876
  ]
4792
4877
 
4793
- ## add scenario '<json>' --slice <sliceName>
4878
+ ## create scenario '<json>' --slice <sliceName>
4794
4879
 
4795
- eventmodeler add scenario '<json>' --slice "Place Order"
4880
+ eventmodeler create scenario '<json>' --slice "Place Order"
4796
4881
 
4797
4882
  SCENARIO JSON:
4798
4883
  {
@@ -5371,6 +5456,65 @@ function registerListCommands(program) {
5371
5456
  }
5372
5457
  }
5373
5458
 
5459
+ // src/lib/scenario-field-values.ts
5460
+ function isPlainObject(v) {
5461
+ return typeof v === "object" && v !== null && !Array.isArray(v);
5462
+ }
5463
+ function toFieldValueWire(value, path4 = "fieldValues") {
5464
+ if (Array.isArray(value)) {
5465
+ const allObjects = value.length > 0 && value.every(isPlainObject);
5466
+ const anyObject = value.some(isPlainObject);
5467
+ if (allObjects) {
5468
+ return { treeList: value.map((item, i) => toFieldValueWire(item, `${path4}[${i}]`)) };
5469
+ }
5470
+ if (anyObject) {
5471
+ throw new Error(`${path4}: list mixes primitives and objects; use either all scalar values or all objects.`);
5472
+ }
5473
+ return { list: value.map(String) };
5474
+ }
5475
+ if (isPlainObject(value)) {
5476
+ return {
5477
+ tree: Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toFieldValueWire(child, `${path4}.${key}`)]))
5478
+ };
5479
+ }
5480
+ return { scalar: String(value) };
5481
+ }
5482
+ function toFieldValuesWire(values) {
5483
+ if (!values)
5484
+ return {};
5485
+ return Object.fromEntries(Object.entries(values).map(([fieldName, value]) => [fieldName, toFieldValueWire(value, `fieldValues.${fieldName}`)]));
5486
+ }
5487
+ function unwrapFieldValueWire(value) {
5488
+ if (!isPlainObject(value))
5489
+ return value;
5490
+ if ("scalar" in value)
5491
+ return value.scalar;
5492
+ if ("list" in value)
5493
+ return value.list;
5494
+ if ("tree" in value && isPlainObject(value.tree)) {
5495
+ return Object.fromEntries(Object.entries(value.tree).map(([fieldName, child]) => [fieldName, unwrapFieldValueWire(child)]));
5496
+ }
5497
+ if ("treeList" in value && Array.isArray(value.treeList)) {
5498
+ return value.treeList.map((item) => unwrapFieldValueWire(item));
5499
+ }
5500
+ return value;
5501
+ }
5502
+ function unwrapFieldValuesWire(values) {
5503
+ if (!isPlainObject(values))
5504
+ return values;
5505
+ return Object.fromEntries(Object.entries(values).map(([fieldName, value]) => [fieldName, unwrapFieldValueWire(value)]));
5506
+ }
5507
+ function unwrapScenarioFieldValuesInResponse(value) {
5508
+ if (Array.isArray(value))
5509
+ return value.map(unwrapScenarioFieldValuesInResponse);
5510
+ if (!isPlainObject(value))
5511
+ return value;
5512
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [
5513
+ key,
5514
+ key === "fieldValues" ? unwrapFieldValuesWire(child) : unwrapScenarioFieldValuesInResponse(child)
5515
+ ]));
5516
+ }
5517
+
5374
5518
  // src/commands/show-schemas.ts
5375
5519
  var FIELD_VIEW = `FieldView:
5376
5520
  {
@@ -5629,22 +5773,22 @@ function registerShowCommands(program) {
5629
5773
  const show = program.command("show").description("Show detailed information");
5630
5774
  show.command("model").description("Show full model hierarchy").addHelpText("after", SHOW_MODEL_HELP).action(async () => {
5631
5775
  const modelId = requireModelId();
5632
- out(unwrap(await showModel({ path: { modelId } })));
5776
+ out(unwrapScenarioFieldValuesInResponse(unwrap(await showModel({ path: { modelId } }))));
5633
5777
  });
5634
5778
  show.command("context [name]").description("Show context with contained chapters and slices").addHelpText("after", SHOW_CONTEXT_HELP).action(async (name, cmd) => {
5635
5779
  const modelId = requireModelId();
5636
5780
  const contextId = await resolve2(modelId, "context", name ?? "", getGlobalId());
5637
- out(unwrap(await showContext({ path: { modelId, contextId } })));
5781
+ out(unwrapScenarioFieldValuesInResponse(unwrap(await showContext({ path: { modelId, contextId } }))));
5638
5782
  });
5639
5783
  show.command("chapter [name]").description("Show chapter with contained slices").addHelpText("after", SHOW_CHAPTER_HELP).action(async (name, cmd) => {
5640
5784
  const modelId = requireModelId();
5641
5785
  const chapterId = await resolve2(modelId, "chapter", name ?? "", getGlobalId());
5642
- out(unwrap(await showChapter({ path: { modelId, chapterId } })));
5786
+ out(unwrapScenarioFieldValuesInResponse(unwrap(await showChapter({ path: { modelId, chapterId } }))));
5643
5787
  });
5644
5788
  show.command("slice [name]").description("Show slice with all elements, flows, and scenarios").addHelpText("after", SHOW_SLICE_HELP).action(async (name, cmd) => {
5645
5789
  const modelId = requireModelId();
5646
5790
  const sliceId = await resolve2(modelId, "slice", name ?? "", getGlobalId());
5647
- out(unwrap(await showSlice({ path: { modelId, sliceId } })));
5791
+ out(unwrapScenarioFieldValuesInResponse(unwrap(await showSlice({ path: { modelId, sliceId } }))));
5648
5792
  });
5649
5793
  for (const type of ["event", "command", "readmodel", "screen", "processor", "external-event"]) {
5650
5794
  show.command(`${type} [name]`).description(`Show ${type} detail with fields and flows`).addHelpText("after", SHOW_ELEMENT_HELP).action(async (name, cmd) => {
@@ -5733,7 +5877,7 @@ var ADD_SUBFIELD_MAP = {
5733
5877
  processor: addProcessorSubfield,
5734
5878
  "external-event": addExternalEventSubfield
5735
5879
  };
5736
- function isPlainObject(v) {
5880
+ function isPlainObject2(v) {
5737
5881
  return typeof v === "object" && v !== null && !Array.isArray(v);
5738
5882
  }
5739
5883
  var ENTRY_DISPATCH = {
@@ -5787,8 +5931,8 @@ async function addEntries(modelId, scenarioId, section, entries, positionOffset
5787
5931
  if (entry.fieldValues) {
5788
5932
  for (const [fieldName, value] of Object.entries(entry.fieldValues)) {
5789
5933
  if (Array.isArray(value)) {
5790
- const allObjects = value.length > 0 && value.every(isPlainObject);
5791
- const anyObject = value.some((v) => isPlainObject(v));
5934
+ const allObjects = value.length > 0 && value.every(isPlainObject2);
5935
+ const anyObject = value.some((v) => isPlainObject2(v));
5792
5936
  if (allObjects) {
5793
5937
  if (!info.setFieldCustomListValues)
5794
5938
  throw new Error(`Cannot set Custom list values on ${entry.entryType}`);
@@ -5804,7 +5948,7 @@ async function addEntries(modelId, scenarioId, section, entries, positionOffset
5804
5948
  body: { modelId, scenarioId, entryId, fieldName, values: value.map(String) }
5805
5949
  }));
5806
5950
  }
5807
- } else if (isPlainObject(value)) {
5951
+ } else if (isPlainObject2(value)) {
5808
5952
  if (!info.setFieldCustomValue)
5809
5953
  throw new Error(`Cannot set Custom value on ${entry.entryType}`);
5810
5954
  unwrap(await info.setFieldCustomValue({
@@ -5822,6 +5966,60 @@ async function addEntries(modelId, scenarioId, section, entries, positionOffset
5822
5966
  }
5823
5967
  }
5824
5968
  }
5969
+ async function createScenarioFromJson(json, sliceName) {
5970
+ const modelId = requireModelId();
5971
+ const sliceId = await resolve2(modelId, "slice", sliceName, getGlobalId());
5972
+ const spec = JSON.parse(json);
5973
+ const scenarioId = randomUUID();
5974
+ const buildEntries = async (section, entries) => {
5975
+ if (!entries)
5976
+ return [];
5977
+ const dispatch = ENTRY_DISPATCH[section];
5978
+ const out2 = [];
5979
+ for (const entry of entries) {
5980
+ const info = dispatch[entry.entryType];
5981
+ if (!info) {
5982
+ throw new Error(`Unknown ${section} entry type: ${entry.entryType}`);
5983
+ }
5984
+ if (entry.entryType === "error") {
5985
+ out2.push({
5986
+ entryType: "error",
5987
+ errorType: entry.errorType ?? "",
5988
+ errorMessage: entry.errorMessage ?? "",
5989
+ fieldValues: {}
5990
+ });
5991
+ } else {
5992
+ if (!entry.elementName) {
5993
+ throw new Error(`${section} ${entry.entryType} entry is missing elementName`);
5994
+ }
5995
+ const elementId = await resolve2(modelId, info.resolveType, entry.elementName);
5996
+ out2.push({
5997
+ entryType: entry.entryType,
5998
+ elementId,
5999
+ fieldValues: toFieldValuesWire(entry.fieldValues)
6000
+ });
6001
+ }
6002
+ }
6003
+ return out2;
6004
+ };
6005
+ const given = await buildEntries("given", spec.given);
6006
+ const when = await buildEntries("when", spec.when);
6007
+ const then = await buildEntries("then", spec.then);
6008
+ unwrap(await createScenario({
6009
+ body: {
6010
+ modelId,
6011
+ sliceId,
6012
+ scenarioId,
6013
+ name: spec.name,
6014
+ ...spec.description ? { description: spec.description } : {},
6015
+ given,
6016
+ when,
6017
+ then
6018
+ }
6019
+ }));
6020
+ const entryCount = given.length + when.length + then.length;
6021
+ return { name: spec.name, entryCount };
6022
+ }
5825
6023
  function registerAddCommands(program) {
5826
6024
  const add = program.command("add").description("Add fields or scenarios");
5827
6025
  add.command("field [elementName] <json>").description("Add a field to an element").action(async (elementNameOrJson, jsonOrUndefined, cmd) => {
@@ -5914,7 +6112,7 @@ element name doesn't resolve, the whole spec is rejected before hitting the
5914
6112
  backend.
5915
6113
 
5916
6114
  Example (primitives + Custom + list):
5917
- add scenario --slice "Place Order" '{
6115
+ create scenario --slice "Place Order" '{
5918
6116
  "name": "happy path",
5919
6117
  "when": [{ "entryType": "command", "elementName": "PlaceOrder",
5920
6118
  "fieldValues": { "orderId": "uuid-1",
@@ -5925,58 +6123,8 @@ Example (primitives + Custom + list):
5925
6123
  { "sku": "sku-2", "qty": "1" } ] } }]
5926
6124
  }'
5927
6125
  `).action(async (json, opts, cmd) => {
5928
- const modelId = requireModelId();
5929
- const sliceId = await resolve2(modelId, "slice", opts.slice, getGlobalId());
5930
- const spec = JSON.parse(json);
5931
- const scenarioId = randomUUID();
5932
- const buildEntries = async (section, entries) => {
5933
- if (!entries)
5934
- return [];
5935
- const dispatch = ENTRY_DISPATCH[section];
5936
- const out2 = [];
5937
- for (const entry of entries) {
5938
- const info = dispatch[entry.entryType];
5939
- if (!info) {
5940
- throw new Error(`Unknown ${section} entry type: ${entry.entryType}`);
5941
- }
5942
- if (entry.entryType === "error") {
5943
- out2.push({
5944
- entryType: "error",
5945
- errorType: entry.errorType ?? "",
5946
- errorMessage: entry.errorMessage ?? "",
5947
- fieldValues: {}
5948
- });
5949
- } else {
5950
- if (!entry.elementName) {
5951
- throw new Error(`${section} ${entry.entryType} entry is missing elementName`);
5952
- }
5953
- const elementId = await resolve2(modelId, info.resolveType, entry.elementName);
5954
- out2.push({
5955
- entryType: entry.entryType,
5956
- elementId,
5957
- fieldValues: entry.fieldValues ?? {}
5958
- });
5959
- }
5960
- }
5961
- return out2;
5962
- };
5963
- const given = await buildEntries("given", spec.given);
5964
- const when = await buildEntries("when", spec.when);
5965
- const then = await buildEntries("then", spec.then);
5966
- unwrap(await createScenario({
5967
- body: {
5968
- modelId,
5969
- sliceId,
5970
- scenarioId,
5971
- name: spec.name,
5972
- ...spec.description ? { description: spec.description } : {},
5973
- given,
5974
- when,
5975
- then
5976
- }
5977
- }));
5978
- const entryCount = given.length + when.length + then.length;
5979
- console.log(`Added scenario "${spec.name}"${entryCount > 0 ? ` with ${entryCount} entries` : ""}.`);
6126
+ const result = await createScenarioFromJson(json, opts.slice);
6127
+ console.log(`Created scenario "${result.name}"${result.entryCount > 0 ? ` with ${result.entryCount} entries` : ""}.`);
5980
6128
  });
5981
6129
  add.command("entry").description("Add a GWT entry to a scenario").requiredOption("--scenario <name>", "Scenario name").requiredOption("--section <section>", "Section: given, when, or then").requiredOption("--type <type>", "Entry type: event, command, readmodel, or error").option("--element <name>", "Element name (required unless --type error)").option("--error-type <type>", "Error type (for --type error)").option("--error-message <msg>", "Error message (for --type error)").option("--position <n>", "Position within section (appends at end by default)", parseInt).action(async (opts) => {
5982
6130
  const modelId = requireModelId();
@@ -6194,8 +6342,62 @@ function registerMoveCommands(program) {
6194
6342
  });
6195
6343
  }
6196
6344
 
6345
+ // src/commands/resize.ts
6346
+ var RESIZE_TABLE = {
6347
+ slice: { resize: resizeSlice, list: slices, idKey: "sliceId" },
6348
+ swimlane: { resize: resizeSwimLane, list: swimLanes, idKey: "swimLaneId" },
6349
+ context: { resize: resizeContext, list: contexts, idKey: "contextId" },
6350
+ chapter: { resize: resizeChapter, list: chapters, idKey: "chapterId" },
6351
+ aggregate: { resize: resizeAggregate, list: aggregates, idKey: "aggregateId" },
6352
+ actor: { resize: resizeActor, list: actors, idKey: "actorId" }
6353
+ };
6354
+ function registerResizeCommands(program) {
6355
+ program.command("resize <type> [name]").description(`Resize an element. Types: ${Object.keys(RESIZE_TABLE).join(", ")}`).requiredOption("--width <n>", "New width", Number).requiredOption("--height <n>", "New height", Number).option("--x <n>", "Override X coordinate (defaults to current)", Number).option("--y <n>", "Override Y coordinate (defaults to current)", Number).action(async (type, name, opts) => {
6356
+ const entry = RESIZE_TABLE[type];
6357
+ if (!entry) {
6358
+ throw new Error(`Unknown type: ${type}. Valid types: ${Object.keys(RESIZE_TABLE).join(", ")}`);
6359
+ }
6360
+ const modelId = requireModelId();
6361
+ const id = await resolve2(modelId, type, name ?? "", getGlobalId());
6362
+ let x = opts.x;
6363
+ let y = opts.y;
6364
+ if (x === undefined || y === undefined) {
6365
+ const list = unwrap(await entry.list({ path: { modelId } }));
6366
+ const current = list.items.find((item) => String(item[entry.idKey]) === id);
6367
+ if (!current) {
6368
+ throw new Error(`${type} ${name ?? id} not found in current model state`);
6369
+ }
6370
+ if (x === undefined)
6371
+ x = Number(current.x);
6372
+ if (y === undefined)
6373
+ y = Number(current.y);
6374
+ }
6375
+ unwrap(await entry.resize({
6376
+ body: { modelId, [entry.idKey]: id, x, y, width: opts.width, height: opts.height }
6377
+ }));
6378
+ console.log(`Resized ${type} to ${opts.width}x${opts.height} at (${x}, ${y}).`);
6379
+ });
6380
+ }
6381
+
6197
6382
  // src/commands/create.ts
6198
6383
  import { randomUUID as randomUUID2 } from "node:crypto";
6384
+ function normalizeSliceFields(fields) {
6385
+ return (fields ?? []).map((field) => ({
6386
+ ...field,
6387
+ id: field.id ?? randomUUID2(),
6388
+ type: field.type ?? field.fieldType,
6389
+ subfields: normalizeSliceFields(field.subfields)
6390
+ }));
6391
+ }
6392
+ function normalizeSliceSpec(spec) {
6393
+ return {
6394
+ ...spec,
6395
+ elements: (spec.elements ?? []).map((element) => ({
6396
+ ...element,
6397
+ fields: normalizeSliceFields(element.fields)
6398
+ }))
6399
+ };
6400
+ }
6199
6401
  var FLOW_DISPATCH = {
6200
6402
  "screen->command": { fn: specifyScreenToCommandFlow, sourceHandle: "bottom-source", targetHandle: "top-target" },
6201
6403
  "command->event": { fn: specifyCommandToEventFlow, sourceHandle: "bottom-source", targetHandle: "top-target" },
@@ -6251,9 +6453,14 @@ function registerCreateCommands(program) {
6251
6453
  create.command("slice <json>").description("Create a full slice from JSON specification").action(async (json) => {
6252
6454
  const modelId = requireModelId();
6253
6455
  const spec = JSON.parse(json);
6254
- unwrap(await createSlice({ body: { modelId, sliceId: randomUUID2(), ...spec } }));
6456
+ const body = { modelId, sliceId: randomUUID2(), ...normalizeSliceSpec(spec) };
6457
+ unwrap(await createSlice({ body }));
6255
6458
  console.log("Created slice.");
6256
6459
  });
6460
+ create.command("scenario <json>").description("Atomically create a full Given/When/Then scenario").requiredOption("--slice <name>", "Slice name").action(async (json, opts) => {
6461
+ const result = await createScenarioFromJson(json, opts.slice);
6462
+ console.log(`Created scenario "${result.name}"${result.entryCount > 0 ? ` with ${result.entryCount} entries` : ""}.`);
6463
+ });
6257
6464
  create.command("flow").description("Create a flow between two elements").requiredOption("--from <source>", "Source element name").requiredOption("--to <target>", "Target element name").option("--source-handle <handle>", "Source handle ID (default: auto based on flow type)").option("--target-handle <handle>", "Target handle ID (default: auto based on flow type)").action(async (opts) => {
6258
6465
  const modelId = requireModelId();
6259
6466
  const source = await resolveAnyElement(modelId, opts.from);
@@ -7021,6 +7228,7 @@ registerAddCommands(program);
7021
7228
  registerRemoveCommands(program);
7022
7229
  registerRenameCommands(program);
7023
7230
  registerMoveCommands(program);
7231
+ registerResizeCommands(program);
7024
7232
  registerCreateCommands(program);
7025
7233
  registerUpdateCommands(program);
7026
7234
  registerMapCommands(program);
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "eventmodeler",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "CLI tool for event modeling - explore, design, and generate code from your event models",
5
5
  "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/theoema/event-modeler.git"
9
+ },
6
10
  "bin": {
7
11
  "eventmodeler": "./dist/index.js"
8
12
  },