eventmodeler 0.4.7 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/dist/api/client-config.js +10 -0
  2. package/dist/api/generated/client/client.gen.js +235 -0
  3. package/dist/api/generated/client/index.js +6 -0
  4. package/dist/api/generated/client/types.gen.js +2 -0
  5. package/dist/api/generated/client/utils.gen.js +228 -0
  6. package/dist/api/generated/client.gen.js +4 -0
  7. package/dist/api/generated/core/auth.gen.js +14 -0
  8. package/dist/api/generated/core/bodySerializer.gen.js +57 -0
  9. package/dist/api/generated/core/params.gen.js +100 -0
  10. package/dist/api/generated/core/pathSerializer.gen.js +106 -0
  11. package/dist/api/generated/core/queryKeySerializer.gen.js +92 -0
  12. package/dist/api/generated/core/serverSentEvents.gen.js +133 -0
  13. package/dist/api/generated/core/types.gen.js +2 -0
  14. package/dist/api/generated/core/utils.gen.js +87 -0
  15. package/dist/api/generated/index.js +2 -0
  16. package/dist/api/generated/sdk.gen.js +4222 -0
  17. package/dist/api/generated/types.gen.js +2 -0
  18. package/dist/api/generated/zod.gen.js +7217 -0
  19. package/dist/commands/add.js +315 -0
  20. package/dist/commands/auth.js +14 -0
  21. package/dist/commands/create.js +192 -0
  22. package/dist/commands/design.js +108 -0
  23. package/dist/commands/guide.js +15 -0
  24. package/dist/commands/init.js +21 -0
  25. package/dist/commands/list-schemas.js +177 -0
  26. package/dist/commands/list.js +39 -0
  27. package/dist/commands/loop.js +101 -0
  28. package/dist/commands/map.js +40 -0
  29. package/dist/commands/mark.js +27 -0
  30. package/dist/commands/move.js +35 -0
  31. package/dist/commands/remove.js +170 -0
  32. package/dist/commands/rename.js +53 -0
  33. package/dist/commands/resize.js +30 -0
  34. package/dist/commands/search.js +14 -0
  35. package/dist/commands/set.js +199 -0
  36. package/dist/commands/show-schemas.js +259 -0
  37. package/dist/commands/show.js +56 -0
  38. package/dist/commands/summary.js +13 -0
  39. package/dist/commands/update.js +240 -0
  40. package/dist/index.js +46 -2379
  41. package/dist/lib/auth.js +1 -1
  42. package/dist/lib/config.js +0 -15
  43. package/dist/lib/excalidraw-schema.js +66 -0
  44. package/dist/lib/globals.js +8 -0
  45. package/dist/lib/model.js +11 -0
  46. package/dist/lib/project-config.js +20 -0
  47. package/dist/lib/resolve.js +59 -0
  48. package/dist/lib/scenario.js +15 -0
  49. package/dist/slices/add-scenario/index.js +2 -206
  50. package/dist/slices/guide/guides/codegen.js +1 -1
  51. package/dist/slices/guide/guides/connect-slices.js +12 -37
  52. package/dist/slices/guide/guides/create-slices.js +110 -140
  53. package/dist/slices/guide/guides/explore.js +37 -26
  54. package/dist/slices/guide/guides/information-flow.js +70 -82
  55. package/dist/slices/guide/guides/scenarios.js +82 -137
  56. package/dist/slices/guide/index.js +6 -6
  57. package/dist/slices/help/index.js +96 -0
  58. package/dist/slices/help/topics/build-codegen.js +109 -0
  59. package/dist/slices/help/topics/build-slice.js +147 -0
  60. package/dist/slices/help/topics/check-completeness.js +57 -0
  61. package/dist/slices/help/topics/connect-slices.js +99 -0
  62. package/dist/slices/help/topics/explore-model.js +112 -0
  63. package/dist/slices/help/topics/json-reference.js +188 -0
  64. package/dist/slices/help/topics/linked-copies.js +89 -0
  65. package/dist/slices/help/topics/manipulate-canvas.js +150 -0
  66. package/dist/slices/help/topics/write-scenarios.js +162 -0
  67. package/dist/slices/init/index.js +10 -4
  68. package/dist/slices/init/loop.js +60 -0
  69. package/dist/slices/login/index.js +2 -2
  70. package/dist/slices/logout/index.js +2 -2
  71. package/dist/slices/whoami/index.js +11 -36
  72. package/package.json +8 -3
  73. package/dist/api/index.d.ts +0 -285
  74. package/dist/api/index.js +0 -323
  75. package/dist/cloud/slices/index.d.ts +0 -276
  76. package/dist/cloud/slices/index.js +0 -406
  77. package/dist/eventmodeler.js +0 -5646
  78. package/dist/formatters.d.ts +0 -17
  79. package/dist/formatters.js +0 -482
  80. package/dist/index.d.ts +0 -2
  81. package/dist/lib/auth.d.ts +0 -24
  82. package/dist/lib/backend.d.ts +0 -43
  83. package/dist/lib/backend.js +0 -73
  84. package/dist/lib/chapter-utils.d.ts +0 -13
  85. package/dist/lib/chapter-utils.js +0 -71
  86. package/dist/lib/cloud-client.d.ts +0 -69
  87. package/dist/lib/cloud-client.js +0 -364
  88. package/dist/lib/config.d.ts +0 -30
  89. package/dist/lib/diff/merge-rules.d.ts +0 -45
  90. package/dist/lib/diff/merge-rules.js +0 -210
  91. package/dist/lib/diff/model-differ.d.ts +0 -8
  92. package/dist/lib/diff/model-differ.js +0 -568
  93. package/dist/lib/diff/three-way-merge.d.ts +0 -7
  94. package/dist/lib/diff/three-way-merge.js +0 -390
  95. package/dist/lib/diff/types.d.ts +0 -75
  96. package/dist/lib/diff/types.js +0 -1
  97. package/dist/lib/element-lookup.d.ts +0 -58
  98. package/dist/lib/element-lookup.js +0 -126
  99. package/dist/lib/file-loader.d.ts +0 -8
  100. package/dist/lib/file-loader.js +0 -108
  101. package/dist/lib/flow-utils.d.ts +0 -53
  102. package/dist/lib/flow-utils.js +0 -348
  103. package/dist/lib/format.d.ts +0 -10
  104. package/dist/lib/format.js +0 -23
  105. package/dist/lib/project-config.d.ts +0 -27
  106. package/dist/lib/slice-utils.d.ts +0 -59
  107. package/dist/lib/slice-utils.js +0 -140
  108. package/dist/local/slices/index.d.ts +0 -11
  109. package/dist/local/slices/index.js +0 -13
  110. package/dist/projection.d.ts +0 -3
  111. package/dist/projection.js +0 -828
  112. package/dist/slices/add-field/index.d.ts +0 -8
  113. package/dist/slices/add-field/index.js +0 -211
  114. package/dist/slices/add-scenario/index.d.ts +0 -27
  115. package/dist/slices/codegen-chapter-events/index.d.ts +0 -2
  116. package/dist/slices/codegen-chapter-events/index.js +0 -145
  117. package/dist/slices/codegen-slice/index.d.ts +0 -2
  118. package/dist/slices/codegen-slice/index.js +0 -448
  119. package/dist/slices/create-automation-slice/index.d.ts +0 -2
  120. package/dist/slices/create-automation-slice/index.js +0 -304
  121. package/dist/slices/create-flow/index.d.ts +0 -2
  122. package/dist/slices/create-flow/index.js +0 -183
  123. package/dist/slices/create-state-change-slice/index.d.ts +0 -2
  124. package/dist/slices/create-state-change-slice/index.js +0 -263
  125. package/dist/slices/create-state-view-slice/index.d.ts +0 -2
  126. package/dist/slices/create-state-view-slice/index.js +0 -128
  127. package/dist/slices/diff/index.d.ts +0 -11
  128. package/dist/slices/diff/index.js +0 -293
  129. package/dist/slices/export-eventmodel-to-json/index.d.ts +0 -2
  130. package/dist/slices/export-eventmodel-to-json/index.js +0 -355
  131. package/dist/slices/git/index.d.ts +0 -2
  132. package/dist/slices/git/index.js +0 -125
  133. package/dist/slices/guide/guides/codegen.d.ts +0 -5
  134. package/dist/slices/guide/guides/connect-slices.d.ts +0 -5
  135. package/dist/slices/guide/guides/create-slices.d.ts +0 -5
  136. package/dist/slices/guide/guides/explore.d.ts +0 -5
  137. package/dist/slices/guide/guides/information-flow.d.ts +0 -5
  138. package/dist/slices/guide/guides/scenarios.d.ts +0 -5
  139. package/dist/slices/guide/index.d.ts +0 -1
  140. package/dist/slices/import/index.d.ts +0 -8
  141. package/dist/slices/import/index.js +0 -63
  142. package/dist/slices/init/index.d.ts +0 -5
  143. package/dist/slices/list-chapters/index.d.ts +0 -3
  144. package/dist/slices/list-chapters/index.js +0 -21
  145. package/dist/slices/list-commands/index.d.ts +0 -3
  146. package/dist/slices/list-commands/index.js +0 -20
  147. package/dist/slices/list-events/index.d.ts +0 -3
  148. package/dist/slices/list-events/index.js +0 -98
  149. package/dist/slices/list-processors/index.d.ts +0 -3
  150. package/dist/slices/list-processors/index.js +0 -20
  151. package/dist/slices/list-readmodels/index.d.ts +0 -3
  152. package/dist/slices/list-readmodels/index.js +0 -21
  153. package/dist/slices/list-scenarios/index.d.ts +0 -3
  154. package/dist/slices/list-scenarios/index.js +0 -35
  155. package/dist/slices/list-screens/index.d.ts +0 -3
  156. package/dist/slices/list-screens/index.js +0 -47
  157. package/dist/slices/list-slices/index.d.ts +0 -3
  158. package/dist/slices/list-slices/index.js +0 -35
  159. package/dist/slices/login/index.d.ts +0 -1
  160. package/dist/slices/logout/index.d.ts +0 -1
  161. package/dist/slices/map-fields/index.d.ts +0 -2
  162. package/dist/slices/map-fields/index.js +0 -269
  163. package/dist/slices/mark-slice-status/index.d.ts +0 -2
  164. package/dist/slices/mark-slice-status/index.js +0 -31
  165. package/dist/slices/merge/index.d.ts +0 -19
  166. package/dist/slices/merge/index.js +0 -147
  167. package/dist/slices/open-app/index.d.ts +0 -1
  168. package/dist/slices/remove-field/index.d.ts +0 -8
  169. package/dist/slices/remove-field/index.js +0 -167
  170. package/dist/slices/remove-scenario/index.d.ts +0 -2
  171. package/dist/slices/remove-scenario/index.js +0 -77
  172. package/dist/slices/search/index.d.ts +0 -3
  173. package/dist/slices/search/index.js +0 -302
  174. package/dist/slices/show-actor/index.d.ts +0 -4
  175. package/dist/slices/show-actor/index.js +0 -115
  176. package/dist/slices/show-aggregate/index.d.ts +0 -3
  177. package/dist/slices/show-aggregate/index.js +0 -108
  178. package/dist/slices/show-aggregate-completeness/index.d.ts +0 -4
  179. package/dist/slices/show-aggregate-completeness/index.js +0 -181
  180. package/dist/slices/show-chapter/index.d.ts +0 -3
  181. package/dist/slices/show-chapter/index.js +0 -195
  182. package/dist/slices/show-command/index.d.ts +0 -3
  183. package/dist/slices/show-command/index.js +0 -133
  184. package/dist/slices/show-completeness/index.d.ts +0 -4
  185. package/dist/slices/show-completeness/index.js +0 -731
  186. package/dist/slices/show-event/index.d.ts +0 -3
  187. package/dist/slices/show-event/index.js +0 -118
  188. package/dist/slices/show-model-summary/index.d.ts +0 -3
  189. package/dist/slices/show-model-summary/index.js +0 -31
  190. package/dist/slices/show-processor/index.d.ts +0 -3
  191. package/dist/slices/show-processor/index.js +0 -111
  192. package/dist/slices/show-readmodel/index.d.ts +0 -3
  193. package/dist/slices/show-readmodel/index.js +0 -158
  194. package/dist/slices/show-scenario/index.d.ts +0 -3
  195. package/dist/slices/show-scenario/index.js +0 -196
  196. package/dist/slices/show-screen/index.d.ts +0 -3
  197. package/dist/slices/show-screen/index.js +0 -139
  198. package/dist/slices/show-slice/index.d.ts +0 -3
  199. package/dist/slices/show-slice/index.js +0 -696
  200. package/dist/slices/update-field/index.d.ts +0 -15
  201. package/dist/slices/update-field/index.js +0 -208
  202. package/dist/slices/whoami/index.d.ts +0 -2
  203. package/dist/types.d.ts +0 -195
  204. package/dist/types.js +0 -1
@@ -0,0 +1,315 @@
1
+ import { getGlobalId } from '../lib/globals';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { requireModelId } from '../lib/model';
4
+ import { resolve, resolveAnyElement, unwrap, elementIdKey } from '../lib/resolve';
5
+ import { resolveScenarioId } from '../lib/scenario';
6
+ import * as sdk from '../api/generated/sdk.gen';
7
+ const ADD_FIELD_MAP = {
8
+ command: sdk.addCommandField,
9
+ event: sdk.addEventField,
10
+ readmodel: sdk.addReadModelField,
11
+ screen: sdk.addScreenField,
12
+ processor: sdk.addProcessorField,
13
+ 'external-event': sdk.addExternalEventField,
14
+ };
15
+ const ADD_SUBFIELD_MAP = {
16
+ command: sdk.addCommandSubfield,
17
+ event: sdk.addEventSubfield,
18
+ readmodel: sdk.addReadModelSubfield,
19
+ screen: sdk.addScreenSubfield,
20
+ processor: sdk.addProcessorSubfield,
21
+ 'external-event': sdk.addExternalEventSubfield,
22
+ };
23
+ function isPlainObject(v) {
24
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
25
+ }
26
+ // Maps entry type in a GWT section to { sdkCall, idKey, primitive + Custom setters }
27
+ export const ENTRY_DISPATCH = {
28
+ given: {
29
+ event: { add: sdk.addScenarioGivenEvent, resolveType: 'event', idKey: 'eventStickyId', setFieldValue: sdk.setScenarioEventFieldValue, setFieldListValues: sdk.setScenarioEventFieldListValues, setFieldCustomValue: sdk.setScenarioEventFieldCustomValue, setFieldCustomListValues: sdk.setScenarioEventFieldCustomListValues },
30
+ readmodel: { add: sdk.addScenarioGivenReadModel, resolveType: 'readmodel', idKey: 'readModelStickyId', setFieldValue: sdk.setScenarioReadModelFieldValue, setFieldListValues: sdk.setScenarioReadModelFieldListValues, setFieldCustomValue: sdk.setScenarioReadModelFieldCustomValue, setFieldCustomListValues: sdk.setScenarioReadModelFieldCustomListValues },
31
+ },
32
+ when: {
33
+ command: { add: sdk.addScenarioWhenCommand, resolveType: 'command', idKey: 'commandStickyId', setFieldValue: sdk.setScenarioCommandFieldValue, setFieldListValues: sdk.setScenarioCommandFieldListValues, setFieldCustomValue: sdk.setScenarioCommandFieldCustomValue, setFieldCustomListValues: sdk.setScenarioCommandFieldCustomListValues },
34
+ event: { add: sdk.addScenarioWhenEvent, resolveType: 'event', idKey: 'eventStickyId', setFieldValue: sdk.setScenarioEventFieldValue, setFieldListValues: sdk.setScenarioEventFieldListValues, setFieldCustomValue: sdk.setScenarioEventFieldCustomValue, setFieldCustomListValues: sdk.setScenarioEventFieldCustomListValues },
35
+ },
36
+ then: {
37
+ event: { add: sdk.addScenarioThenEvent, resolveType: 'event', idKey: 'eventStickyId', setFieldValue: sdk.setScenarioEventFieldValue, setFieldListValues: sdk.setScenarioEventFieldListValues, setFieldCustomValue: sdk.setScenarioEventFieldCustomValue, setFieldCustomListValues: sdk.setScenarioEventFieldCustomListValues },
38
+ command: { add: sdk.addScenarioThenCommand, resolveType: 'command', idKey: 'commandStickyId', setFieldValue: sdk.setScenarioCommandFieldValue, setFieldListValues: sdk.setScenarioCommandFieldListValues, setFieldCustomValue: sdk.setScenarioCommandFieldCustomValue, setFieldCustomListValues: sdk.setScenarioCommandFieldCustomListValues },
39
+ readmodel: { add: sdk.addScenarioThenReadModel, resolveType: 'readmodel', idKey: 'readModelStickyId', setFieldValue: sdk.setScenarioReadModelFieldValue, setFieldListValues: sdk.setScenarioReadModelFieldListValues, setFieldCustomValue: sdk.setScenarioReadModelFieldCustomValue, setFieldCustomListValues: sdk.setScenarioReadModelFieldCustomListValues },
40
+ error: { add: sdk.addScenarioThenError, resolveType: '', idKey: '', setFieldValue: undefined, setFieldListValues: undefined },
41
+ },
42
+ };
43
+ async function addEntries(modelId, scenarioId, section, entries, positionOffset = 0) {
44
+ const dispatch = ENTRY_DISPATCH[section];
45
+ if (!dispatch)
46
+ throw new Error(`Unknown scenario section: ${section}`);
47
+ for (let i = 0; i < entries.length; i++) {
48
+ const entry = entries[i];
49
+ const entryId = randomUUID();
50
+ const info = dispatch[entry.entryType];
51
+ if (!info)
52
+ throw new Error(`Unknown ${section} entry type: ${entry.entryType}`);
53
+ if (entry.entryType === 'error') {
54
+ unwrap(await info.add({
55
+ body: {
56
+ modelId,
57
+ scenarioId,
58
+ entryId,
59
+ position: positionOffset + i,
60
+ errorType: entry.errorType ?? '',
61
+ errorMessage: entry.errorMessage ?? '',
62
+ }
63
+ }));
64
+ }
65
+ else {
66
+ const elementId = await resolve(modelId, info.resolveType, entry.elementName);
67
+ unwrap(await info.add({
68
+ body: {
69
+ modelId,
70
+ scenarioId,
71
+ entryId,
72
+ position: positionOffset + i,
73
+ [info.idKey]: elementId,
74
+ }
75
+ }));
76
+ // Set field values if provided. Dispatch by JS shape — backend rejects mismatches.
77
+ // string → primitive scalar
78
+ // string[] → primitive list (objects in array → Custom list)
79
+ // object → Custom scalar (recursive tree)
80
+ // object[] → Custom list of trees
81
+ if (entry.fieldValues) {
82
+ for (const [fieldName, value] of Object.entries(entry.fieldValues)) {
83
+ if (Array.isArray(value)) {
84
+ const allObjects = value.length > 0 && value.every(isPlainObject);
85
+ const anyObject = value.some((v) => isPlainObject(v));
86
+ if (allObjects) {
87
+ if (!info.setFieldCustomListValues)
88
+ throw new Error(`Cannot set Custom list values on ${entry.entryType}`);
89
+ unwrap(await info.setFieldCustomListValues({
90
+ body: { modelId, scenarioId, entryId, fieldName, items: value }
91
+ }));
92
+ }
93
+ else if (anyObject) {
94
+ throw new Error(`Field "${fieldName}": list mixes primitives and objects — use either all strings or all objects.`);
95
+ }
96
+ else {
97
+ if (!info.setFieldListValues)
98
+ throw new Error(`Cannot set list values on ${entry.entryType}`);
99
+ unwrap(await info.setFieldListValues({
100
+ body: { modelId, scenarioId, entryId, fieldName, values: value.map(String) }
101
+ }));
102
+ }
103
+ }
104
+ else if (isPlainObject(value)) {
105
+ if (!info.setFieldCustomValue)
106
+ throw new Error(`Cannot set Custom value on ${entry.entryType}`);
107
+ unwrap(await info.setFieldCustomValue({
108
+ body: { modelId, scenarioId, entryId, fieldName, value }
109
+ }));
110
+ }
111
+ else {
112
+ if (!info.setFieldValue)
113
+ throw new Error(`Cannot set value on ${entry.entryType}`);
114
+ unwrap(await info.setFieldValue({
115
+ body: { modelId, scenarioId, entryId, fieldName, value: String(value) }
116
+ }));
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ export function registerAddCommands(program) {
124
+ const add = program.command('add').description('Add fields or scenarios');
125
+ add.command('field [elementName] <json>')
126
+ .description('Add a field to an element')
127
+ .action(async (elementNameOrJson, jsonOrUndefined, cmd) => {
128
+ const modelId = requireModelId();
129
+ const hasId = getGlobalId();
130
+ const elementName = jsonOrUndefined ? elementNameOrJson : '';
131
+ const json = jsonOrUndefined ?? elementNameOrJson;
132
+ const { elementId, elementType } = await resolveAnyElement(modelId, elementName, hasId);
133
+ const fn = ADD_FIELD_MAP[elementType];
134
+ if (!fn)
135
+ throw new Error(`Cannot add fields to type: ${elementType}`);
136
+ const field = JSON.parse(json);
137
+ const key = elementIdKey(elementType);
138
+ unwrap(await fn({
139
+ body: {
140
+ modelId,
141
+ [key]: elementId,
142
+ fieldId: randomUUID(),
143
+ fieldName: field.name,
144
+ fieldType: field.type,
145
+ }
146
+ }));
147
+ console.log(`Added field "${field.name}".`);
148
+ });
149
+ // add subfield [elementName] --field <parentFieldName> <json>
150
+ add.command('subfield [elementName]')
151
+ .description('Add a subfield to an existing field')
152
+ .requiredOption('--field <name>', 'Parent field name')
153
+ .argument('<json>', 'Subfield JSON: {"name": "...", "type": "..."}')
154
+ .action(async (elementName, json, opts, cmd) => {
155
+ const modelId = requireModelId();
156
+ const hasId = getGlobalId();
157
+ const { elementId, elementType } = await resolveAnyElement(modelId, elementName ?? '', hasId);
158
+ const fn = ADD_SUBFIELD_MAP[elementType];
159
+ if (!fn)
160
+ throw new Error(`Cannot add subfields to type: ${elementType}`);
161
+ const fieldResult = unwrap(await sdk.resolveField({
162
+ query: { modelId, elementId, fieldName: opts.field }
163
+ }));
164
+ const parentFieldId = fieldResult.fieldId;
165
+ const sub = JSON.parse(json);
166
+ const key = elementIdKey(elementType);
167
+ unwrap(await fn({
168
+ body: {
169
+ modelId,
170
+ [key]: elementId,
171
+ parentFieldId,
172
+ subfieldId: randomUUID(),
173
+ fieldName: sub.name,
174
+ fieldType: sub.type,
175
+ }
176
+ }));
177
+ console.log(`Added subfield "${sub.name}" to "${opts.field}".`);
178
+ });
179
+ add.command('scenario <json>')
180
+ .description('Atomically create a full Given/When/Then scenario (shell + entries + field example values) in a single backend transaction')
181
+ .requiredOption('--slice <name>', 'Slice name')
182
+ .addHelpText('after', `
183
+ The entire scenario is created in ONE backend command — either everything
184
+ succeeds or nothing is persisted. No orphan shells, no half-applied entries,
185
+ no partial field values. All validation runs up-front; on any failure the
186
+ whole spec is rejected with a single error message.
187
+
188
+ Spec shape:
189
+ {
190
+ "name": "Scenario name",
191
+ "description": "(optional)",
192
+ "given": [<entry>, ...],
193
+ "when": [<entry>, ...],
194
+ "then": [<entry>, ...]
195
+ }
196
+
197
+ Entry shape (non-error):
198
+ { "entryType": "event"|"command"|"readmodel",
199
+ "elementName": "ElementName",
200
+ "fieldValues": { "<fieldName>": <value>, ... } }
201
+
202
+ Entry shape (error, "then" only):
203
+ { "entryType": "error", "errorType": "...", "errorMessage": "..." }
204
+
205
+ Allowed entry types per section:
206
+ given: event, readmodel
207
+ when: command, event
208
+ then: event, command, readmodel, error
209
+
210
+ fieldValues — backend dispatches by FIELD METADATA (not by JSON shape):
211
+ field is primitive scalar → value must be a JSON string "42"
212
+ field is primitive list → value must be string[] ["a","b"]
213
+ field is Custom (object) → value must be object { "city": "NYC" }
214
+ field is Custom list → value must be object[] [{...}, {...}]
215
+
216
+ Inside a Custom tree, primitive leaves MUST be JSON strings ("42", "true",
217
+ ISO dates). Field type/listness mismatches are rejected up-front.
218
+ Discover field shapes (incl. nested subfields) with: show element <name>
219
+
220
+ Element names are resolved to UUIDs client-side before the call. If any
221
+ element name doesn't resolve, the whole spec is rejected before hitting the
222
+ backend.
223
+
224
+ Example (primitives + Custom + list):
225
+ add scenario --slice "Place Order" '{
226
+ "name": "happy path",
227
+ "when": [{ "entryType": "command", "elementName": "PlaceOrder",
228
+ "fieldValues": { "orderId": "uuid-1",
229
+ "items": ["sku-1","sku-2"],
230
+ "shipTo": { "city": "NYC", "zip": "10001" } } }],
231
+ "then": [{ "entryType": "event", "elementName": "OrderPlaced",
232
+ "fieldValues": { "lines": [ { "sku": "sku-1", "qty": "2" },
233
+ { "sku": "sku-2", "qty": "1" } ] } }]
234
+ }'
235
+ `)
236
+ .action(async (json, opts, cmd) => {
237
+ const modelId = requireModelId();
238
+ const sliceId = await resolve(modelId, 'slice', opts.slice, getGlobalId());
239
+ const spec = JSON.parse(json);
240
+ const scenarioId = randomUUID();
241
+ const buildEntries = async (section, entries) => {
242
+ if (!entries)
243
+ return [];
244
+ const dispatch = ENTRY_DISPATCH[section];
245
+ const out = [];
246
+ for (const entry of entries) {
247
+ const info = dispatch[entry.entryType];
248
+ if (!info) {
249
+ throw new Error(`Unknown ${section} entry type: ${entry.entryType}`);
250
+ }
251
+ if (entry.entryType === 'error') {
252
+ out.push({
253
+ entryType: 'error',
254
+ errorType: entry.errorType ?? '',
255
+ errorMessage: entry.errorMessage ?? '',
256
+ fieldValues: {},
257
+ });
258
+ }
259
+ else {
260
+ if (!entry.elementName) {
261
+ throw new Error(`${section} ${entry.entryType} entry is missing elementName`);
262
+ }
263
+ const elementId = await resolve(modelId, info.resolveType, entry.elementName);
264
+ out.push({
265
+ entryType: entry.entryType,
266
+ elementId,
267
+ fieldValues: entry.fieldValues ?? {},
268
+ });
269
+ }
270
+ }
271
+ return out;
272
+ };
273
+ const given = await buildEntries('given', spec.given);
274
+ const when = await buildEntries('when', spec.when);
275
+ const then = await buildEntries('then', spec.then);
276
+ unwrap(await sdk.createScenario({
277
+ body: {
278
+ modelId,
279
+ sliceId,
280
+ scenarioId,
281
+ name: spec.name,
282
+ ...(spec.description ? { description: spec.description } : {}),
283
+ given,
284
+ when,
285
+ then,
286
+ },
287
+ }));
288
+ const entryCount = given.length + when.length + then.length;
289
+ console.log(`Added scenario "${spec.name}"${entryCount > 0 ? ` with ${entryCount} entries` : ''}.`);
290
+ });
291
+ // add entry --scenario <name> --section given|when|then --type event|command|readmodel|error --element <name> [--error-type <type>] [--error-message <msg>]
292
+ add.command('entry')
293
+ .description('Add a GWT entry to a scenario')
294
+ .requiredOption('--scenario <name>', 'Scenario name')
295
+ .requiredOption('--section <section>', 'Section: given, when, or then')
296
+ .requiredOption('--type <type>', 'Entry type: event, command, readmodel, or error')
297
+ .option('--element <name>', 'Element name (required unless --type error)')
298
+ .option('--error-type <type>', 'Error type (for --type error)')
299
+ .option('--error-message <msg>', 'Error message (for --type error)')
300
+ .option('--position <n>', 'Position within section (appends at end by default)', parseInt)
301
+ .action(async (opts) => {
302
+ const modelId = requireModelId();
303
+ const scenarioId = await resolveScenarioId(modelId, opts.scenario, getGlobalId());
304
+ const entry = {
305
+ entryType: opts.type,
306
+ elementName: opts.element,
307
+ errorType: opts.errorType,
308
+ errorMessage: opts.errorMessage,
309
+ };
310
+ // Use a high position to append at end if not specified
311
+ const position = opts.position ?? 9999;
312
+ await addEntries(modelId, scenarioId, opts.section, [entry], position);
313
+ console.log(`Added ${opts.section} ${opts.type} entry${opts.element ? ` "${opts.element}"` : ''}.`);
314
+ });
315
+ }
@@ -0,0 +1,14 @@
1
+ import { login } from '../slices/login/index';
2
+ import { logout } from '../slices/logout/index';
3
+ import { whoami } from '../slices/whoami/index';
4
+ export function registerAuthCommands(program) {
5
+ program.command('login')
6
+ .description('Log in to Event Modeler')
7
+ .action(() => login());
8
+ program.command('logout')
9
+ .description('Log out of Event Modeler')
10
+ .action(() => logout());
11
+ program.command('whoami')
12
+ .description('Show current authentication status')
13
+ .action(() => whoami());
14
+ }
@@ -0,0 +1,192 @@
1
+ import { getGlobalId } from '../lib/globals';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { requireModelId } from '../lib/model';
4
+ import { resolve, resolveAnyElement, unwrap, elementIdKey } from '../lib/resolve';
5
+ import * as sdk from '../api/generated/sdk.gen';
6
+ const FLOW_DISPATCH = {
7
+ 'screen->command': { fn: sdk.specifyScreenToCommandFlow, sourceHandle: 'bottom-source', targetHandle: 'top-target' },
8
+ 'command->event': { fn: sdk.specifyCommandToEventFlow, sourceHandle: 'bottom-source', targetHandle: 'top-target' },
9
+ 'command->external-event': { fn: sdk.specifyCommandToExternalEventFlow, sourceHandle: 'bottom-source', targetHandle: 'top-target' },
10
+ 'event->readmodel': { fn: sdk.specifyEventToReadModelFlow, sourceHandle: 'bottom-source', targetHandle: 'top-target' },
11
+ 'readmodel->screen': { fn: sdk.specifyReadModelToScreenFlow, sourceHandle: 'top-source', targetHandle: 'bottom-target' },
12
+ 'readmodel->processor': { fn: sdk.specifyReadModelToProcessorFlow, sourceHandle: 'top-source', targetHandle: 'bottom-target' },
13
+ 'processor->command': { fn: sdk.specifyProcessorToCommandFlow, sourceHandle: 'right-source', targetHandle: 'left-target' },
14
+ 'external-event->processor': { fn: sdk.specifyExternalEventToProcessorFlow, sourceHandle: 'top-source', targetHandle: 'bottom-target' },
15
+ };
16
+ // Canonical dimensions per element type — matches frontend ELEMENT_DIMENSIONS.
17
+ // Sizes are not user-configurable; the CLI always sends these so dimensions stay
18
+ // consistent with the canvas's rendered components.
19
+ const PLACE_MAP = {
20
+ command: { fn: sdk.placeCommandSticky, idKey: 'commandStickyId', nameKey: 'name', width: 160, height: 100 },
21
+ event: { fn: sdk.placeEventSticky, idKey: 'eventStickyId', nameKey: 'name', width: 160, height: 100 },
22
+ readmodel: { fn: sdk.placeReadModelSticky, idKey: 'readModelStickyId', nameKey: 'name', width: 160, height: 100 },
23
+ screen: { fn: sdk.placeScreen, idKey: 'screenId', nameKey: 'name', width: 180, height: 120 },
24
+ processor: { fn: sdk.placeProcessor, idKey: 'processorId', nameKey: 'name', width: 120, height: 120 },
25
+ 'external-event': { fn: sdk.placeExternalEvent, idKey: 'externalEventId', nameKey: 'name', width: 160, height: 100 },
26
+ aggregate: { fn: sdk.placeAggregate, idKey: 'aggregateId', nameKey: 'name', width: 300, height: 200 },
27
+ actor: { fn: sdk.placeActor, idKey: 'actorId', nameKey: 'name', width: 300, height: 200 },
28
+ chapter: { fn: sdk.placeChapter, idKey: 'chapterId', nameKey: 'name', width: 800, height: 1200 },
29
+ context: { fn: sdk.placeContext, idKey: 'contextId', nameKey: 'name', width: 300, height: 200 },
30
+ note: { fn: sdk.placeNote, idKey: 'noteId', nameKey: 'title', width: 200, height: 150 },
31
+ swimlane: { fn: sdk.placeSwimLane, idKey: 'swimLaneId', nameKey: 'name', width: 300, height: 200 },
32
+ slice: { fn: sdk.placeSlice, idKey: 'sliceId', nameKey: 'name', width: 300, height: 200 },
33
+ };
34
+ // Linked copy placement — these reference an original element
35
+ const LINKED_COPY_MAP = {
36
+ event: { fn: sdk.placeEventStickyLinkedCopy, idKey: 'eventStickyId', originalKey: 'originalEventStickyId', resolveType: 'event', width: 160, height: 100 },
37
+ readmodel: { fn: sdk.placeReadModelStickyLinkedCopy, idKey: 'readModelStickyId', originalKey: 'originalReadModelStickyId', resolveType: 'readmodel', width: 160, height: 100 },
38
+ screen: { fn: sdk.placeScreenLinkedCopy, idKey: 'screenId', originalKey: 'originalScreenId', resolveType: 'screen', width: 180, height: 120 },
39
+ 'external-event': { fn: sdk.placeExternalEventLinkedCopy, idKey: 'copyId', originalKey: 'originalExternalEventId', resolveType: 'external-event', width: 160, height: 100 },
40
+ };
41
+ // Linked copy move — different SDK functions from regular move
42
+ const MOVE_LINKED_COPY_MAP = {
43
+ event: sdk.moveEventStickyLinkedCopy,
44
+ readmodel: sdk.moveReadModelStickyLinkedCopy,
45
+ screen: sdk.moveScreenLinkedCopy,
46
+ 'external-event': sdk.moveExternalEventLinkedCopy,
47
+ };
48
+ // Linked copy remove
49
+ const REMOVE_LINKED_COPY_MAP = {
50
+ event: sdk.removeEventStickyLinkedCopy,
51
+ readmodel: sdk.removeReadModelStickyLinkedCopy,
52
+ screen: sdk.removeScreenLinkedCopy,
53
+ 'external-event': sdk.removeExternalEventLinkedCopy,
54
+ };
55
+ export function registerCreateCommands(program) {
56
+ const create = program.command('create').description('Create new elements');
57
+ // create model <name>
58
+ create.command('model <name>')
59
+ .description('Create a new event model')
60
+ .action(async (name) => {
61
+ const result = unwrap(await sdk.createModel({ body: { name } }));
62
+ const modelId = result.modelId;
63
+ console.log(`Created model "${name}".`);
64
+ console.log(`Model ID: ${modelId}`);
65
+ console.log(`Run: eventmodeler init --model ${modelId}`);
66
+ });
67
+ create.command('slice <json>')
68
+ .description('Create a full slice from JSON specification')
69
+ .action(async (json) => {
70
+ const modelId = requireModelId();
71
+ const spec = JSON.parse(json);
72
+ unwrap(await sdk.createSlice({ body: { modelId, sliceId: randomUUID(), ...spec } }));
73
+ console.log('Created slice.');
74
+ });
75
+ create.command('flow')
76
+ .description('Create a flow between two elements')
77
+ .requiredOption('--from <source>', 'Source element name')
78
+ .requiredOption('--to <target>', 'Target element name')
79
+ .option('--source-handle <handle>', 'Source handle ID (default: auto based on flow type)')
80
+ .option('--target-handle <handle>', 'Target handle ID (default: auto based on flow type)')
81
+ .action(async (opts) => {
82
+ const modelId = requireModelId();
83
+ const source = await resolveAnyElement(modelId, opts.from);
84
+ const target = await resolveAnyElement(modelId, opts.to);
85
+ const flowKey = `${source.elementType}->${target.elementType}`;
86
+ const config = FLOW_DISPATCH[flowKey];
87
+ if (!config)
88
+ throw new Error(`No flow type for ${flowKey}. Valid: ${Object.keys(FLOW_DISPATCH).join(', ')}`);
89
+ const sourceHandle = opts.sourceHandle ?? config.sourceHandle;
90
+ const targetHandle = opts.targetHandle ?? config.targetHandle;
91
+ const sourceKey = elementIdKey(source.elementType);
92
+ const targetKey = elementIdKey(target.elementType);
93
+ unwrap(await config.fn({
94
+ body: {
95
+ modelId,
96
+ flowId: randomUUID(),
97
+ [sourceKey]: source.elementId,
98
+ [targetKey]: target.elementId,
99
+ sourceHandle,
100
+ targetHandle,
101
+ }
102
+ }));
103
+ console.log(`Created ${flowKey} flow (${sourceHandle} → ${targetHandle})`);
104
+ });
105
+ // place <type> <name> --x <n> --y <n>
106
+ // For aggregate: also --id-field-name and --id-field-type
107
+ const validTypes = Object.keys(PLACE_MAP).join(', ');
108
+ create.command('place <type> <name>')
109
+ .description(`Place a new element on the canvas. Types: ${validTypes}`)
110
+ .requiredOption('--x <n>', 'X coordinate', Number)
111
+ .requiredOption('--y <n>', 'Y coordinate', Number)
112
+ .option('--id-field-name <name>', 'Aggregate ID field name (aggregates only)')
113
+ .option('--id-field-type <type>', 'Aggregate ID field type (aggregates only)', 'UUID')
114
+ .action(async (type, name, opts) => {
115
+ const modelId = requireModelId();
116
+ const config = PLACE_MAP[type];
117
+ if (!config)
118
+ throw new Error(`Unknown type: ${type}. Valid: ${validTypes}`);
119
+ const body = {
120
+ modelId,
121
+ [config.idKey]: randomUUID(),
122
+ [config.nameKey]: name,
123
+ x: opts.x,
124
+ y: opts.y,
125
+ width: config.width,
126
+ height: config.height,
127
+ };
128
+ // Aggregate needs ID field info
129
+ if (type === 'aggregate') {
130
+ body.aggregateIdFieldName = opts.idFieldName ?? 'id';
131
+ body.aggregateIdFieldType = opts.idFieldType ?? 'UUID';
132
+ }
133
+ unwrap(await config.fn({ body }));
134
+ console.log(`Placed ${type} "${name}" at (${opts.x}, ${opts.y}).`);
135
+ });
136
+ // place linked-copy <type> <originalName> --x <n> --y <n>
137
+ const linkableTypes = Object.keys(LINKED_COPY_MAP).join(', ');
138
+ create.command('place-copy <type> [originalName]')
139
+ .description(`Place a linked copy of an element. Types: ${linkableTypes}`)
140
+ .requiredOption('--x <n>', 'X coordinate', Number)
141
+ .requiredOption('--y <n>', 'Y coordinate', Number)
142
+ .action(async (type, originalName, opts) => {
143
+ const modelId = requireModelId();
144
+ const config = LINKED_COPY_MAP[type];
145
+ if (!config)
146
+ throw new Error(`Cannot create linked copies of type: ${type}. Valid: ${linkableTypes}`);
147
+ const originalId = await resolve(modelId, config.resolveType, originalName ?? '', getGlobalId());
148
+ const body = {
149
+ modelId,
150
+ [config.idKey]: randomUUID(),
151
+ [config.originalKey]: originalId,
152
+ x: opts.x,
153
+ y: opts.y,
154
+ };
155
+ // External event linked copy has no width/height in its command
156
+ if (type !== 'external-event') {
157
+ body.width = config.width;
158
+ body.height = config.height;
159
+ }
160
+ unwrap(await config.fn({ body }));
161
+ console.log(`Placed linked copy of ${type} at (${opts.x}, ${opts.y}).`);
162
+ });
163
+ // move-copy <type> [name] --x <n> --y <n> (uses --id for targeting specific copy)
164
+ create.command('move-copy <type> [name]')
165
+ .description(`Move a linked copy. Types: ${linkableTypes}`)
166
+ .requiredOption('--x <n>', 'X coordinate', Number)
167
+ .requiredOption('--y <n>', 'Y coordinate', Number)
168
+ .action(async (type, name, opts) => {
169
+ const modelId = requireModelId();
170
+ const fn = MOVE_LINKED_COPY_MAP[type];
171
+ if (!fn)
172
+ throw new Error(`Cannot move linked copies of type: ${type}. Valid: ${linkableTypes}`);
173
+ const id = await resolve(modelId, type, name ?? '', getGlobalId());
174
+ // External event linked copies use 'copyId', others use standard elementIdKey
175
+ const key = type === 'external-event' ? 'copyId' : elementIdKey(type);
176
+ unwrap(await fn({ body: { modelId, [key]: id, x: opts.x, y: opts.y } }));
177
+ console.log(`Moved linked copy to (${opts.x}, ${opts.y}).`);
178
+ });
179
+ // remove-copy <type> [name] (uses --id for targeting specific copy)
180
+ create.command('remove-copy <type> [name]')
181
+ .description(`Remove a linked copy. Types: ${linkableTypes}`)
182
+ .action(async (type, name) => {
183
+ const modelId = requireModelId();
184
+ const fn = REMOVE_LINKED_COPY_MAP[type];
185
+ if (!fn)
186
+ throw new Error(`Cannot remove linked copies of type: ${type}. Valid: ${linkableTypes}`);
187
+ const id = await resolve(modelId, type, name ?? '', getGlobalId());
188
+ const key = type === 'external-event' ? 'copyId' : elementIdKey(type);
189
+ unwrap(await fn({ body: { modelId, [key]: id } }));
190
+ console.log(`Removed linked copy.`);
191
+ });
192
+ }
@@ -0,0 +1,108 @@
1
+ import { getGlobalId } from '../lib/globals';
2
+ import { requireModelId } from '../lib/model';
3
+ import { resolve, unwrap } from '../lib/resolve';
4
+ import { ExcalidrawDesign, formatZodError } from '../lib/excalidraw-schema';
5
+ import * as sdk from '../api/generated/sdk.gen';
6
+ const DESIGN_HELP = `
7
+ INPUT FORMAT
8
+ A JSON array of Excalidraw element objects — NOT the full Excalidraw
9
+ document ({ type, version, elements, appState, files }). Pass only the
10
+ inner "elements" array.
11
+
12
+ ELEMENT TYPES
13
+ rectangle, ellipse, diamond, line, arrow, text, freedraw, image,
14
+ frame, magicframe, iframe, embeddable
15
+
16
+ REQUIRED FIELDS (all types)
17
+ id string Unique within the array
18
+ type string One of the element types above
19
+ x, y number Top-left position
20
+ width number Box width
21
+ height number Box height
22
+
23
+ REQUIRED FIELDS (text elements)
24
+ id, type, x, y As above (width/height optional for text)
25
+ text string The rendered text
26
+ fontSize number Pixels
27
+
28
+ OPTIONAL STYLE FIELDS
29
+ strokeColor string Any color string (hex "#000", "transparent", or
30
+ named — Excalidraw accepts any string here)
31
+ backgroundColor string As above; default "transparent"
32
+ fillStyle enum "solid" | "hachure" | "cross-hatch" | "zigzag"
33
+ strokeStyle enum "solid" | "dashed" | "dotted"
34
+ strokeWidth number Non-negative (default 2, typical 1 / 2 / 4)
35
+ roughness number Non-negative (0 clean, 1 artist [default], 2 sketchy)
36
+ opacity number 0–100 (default 100)
37
+ angle number Rotation in radians
38
+ fontFamily number Positive int. Known codes:
39
+ 1 Virgil · 2 Helvetica · 3 Cascadia ·
40
+ 5 Excalifont (default) · 6 Nunito ·
41
+ 7 Lilita One · 8 Comic Shanns ·
42
+ 9 Liberation Sans · 10 Assistant
43
+ textAlign enum "left" | "center" | "right"
44
+ verticalAlign enum "top" | "middle" | "bottom"
45
+
46
+ EXAMPLE
47
+ eventmodeler design "Login" '[
48
+ {"id":"bg","type":"rectangle","x":0,"y":0,"width":320,"height":200,
49
+ "strokeColor":"#000","backgroundColor":"#fff","fillStyle":"solid",
50
+ "strokeWidth":1,"roughness":0,"opacity":100},
51
+ {"id":"title","type":"text","x":20,"y":20,"width":280,"height":30,
52
+ "text":"Sign in","fontSize":20,"fontFamily":2,"textAlign":"left",
53
+ "strokeColor":"#000","opacity":100},
54
+ {"id":"btn","type":"rectangle","x":20,"y":150,"width":100,"height":32,
55
+ "strokeColor":"#000","backgroundColor":"#4a90e2","fillStyle":"solid",
56
+ "strokeWidth":1,"roughness":0,"opacity":100}
57
+ ]'
58
+
59
+ NOTES
60
+ - An empty array ([]) clears the screen's design.
61
+ - The screen must already exist (create it first with the place command).
62
+ `;
63
+ export function registerDesignCommands(program) {
64
+ program.command('design')
65
+ .description('Update screen design (Excalidraw JSON array)')
66
+ .argument('<names...>', 'Screen name followed by JSON array')
67
+ .addHelpText('after', DESIGN_HELP)
68
+ .action(async (names) => {
69
+ const modelId = requireModelId();
70
+ const idOverride = getGlobalId();
71
+ const jsonIdx = names.findIndex(a => a.startsWith('['));
72
+ if (jsonIdx === -1) {
73
+ console.error('Error: Design must be a JSON array of Excalidraw elements');
74
+ console.error('');
75
+ console.error('Example:');
76
+ console.error(' eventmodeler design "My Screen" \'[');
77
+ console.error(' {"id":"1","type":"rectangle","x":10,"y":10,"width":150,"height":30,"strokeColor":"#000","backgroundColor":"#eee","fillStyle":"solid","strokeWidth":1,"roughness":0,"opacity":100},');
78
+ console.error(' {"id":"2","type":"text","x":20,"y":15,"width":80,"height":20,"text":"Hello","fontSize":14,"fontFamily":1,"textAlign":"left","strokeColor":"#000","opacity":100}');
79
+ console.error(' ]\'');
80
+ process.exit(1);
81
+ }
82
+ const screenName = names.slice(0, jsonIdx).join(' ');
83
+ const json = names.slice(jsonIdx).join(' ');
84
+ let parsed;
85
+ try {
86
+ parsed = JSON.parse(json);
87
+ }
88
+ catch (e) {
89
+ console.error(`Invalid JSON: ${e.message}`);
90
+ process.exit(1);
91
+ }
92
+ const result = ExcalidrawDesign.safeParse(parsed);
93
+ if (!result.success) {
94
+ console.error('Invalid Excalidraw design:');
95
+ for (const e of formatZodError(result.error))
96
+ console.error(` ${e}`);
97
+ console.error('');
98
+ console.error('Run "eventmodeler design --help" for the full schema.');
99
+ process.exit(1);
100
+ }
101
+ if (result.data.length === 0) {
102
+ console.error('Design array is empty — this will clear the screen design');
103
+ }
104
+ const screenId = await resolve(modelId, 'screen', screenName, idOverride);
105
+ unwrap(await sdk.updateScreenDesign({ body: { modelId, screenId, design: json } }));
106
+ console.log(`Updated design on "${idOverride ? screenId : screenName}" (${result.data.length} elements)`);
107
+ });
108
+ }
@@ -0,0 +1,15 @@
1
+ import { runHelp } from '../slices/help/index';
2
+ export function registerGuideCommands(program) {
3
+ program.command('help [query...]')
4
+ .description('Show task-oriented help for working with event models')
5
+ .action((queryParts) => {
6
+ const query = queryParts.length > 0 ? queryParts.join(' ') : undefined;
7
+ runHelp(query);
8
+ });
9
+ program.command('guide [query...]')
10
+ .description('Alias for "help"')
11
+ .action((queryParts) => {
12
+ const query = queryParts.length > 0 ? queryParts.join(' ') : undefined;
13
+ runHelp(query);
14
+ });
15
+ }