eventmodeler 0.6.0 → 0.6.2

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 (73) hide show
  1. package/dist/index.js +7133 -34
  2. package/package.json +5 -4
  3. package/dist/api/client-config.js +0 -10
  4. package/dist/api/generated/client/client.gen.js +0 -235
  5. package/dist/api/generated/client/index.js +0 -6
  6. package/dist/api/generated/client/types.gen.js +0 -2
  7. package/dist/api/generated/client/utils.gen.js +0 -228
  8. package/dist/api/generated/client.gen.js +0 -4
  9. package/dist/api/generated/core/auth.gen.js +0 -14
  10. package/dist/api/generated/core/bodySerializer.gen.js +0 -57
  11. package/dist/api/generated/core/params.gen.js +0 -100
  12. package/dist/api/generated/core/pathSerializer.gen.js +0 -106
  13. package/dist/api/generated/core/queryKeySerializer.gen.js +0 -92
  14. package/dist/api/generated/core/serverSentEvents.gen.js +0 -133
  15. package/dist/api/generated/core/types.gen.js +0 -2
  16. package/dist/api/generated/core/utils.gen.js +0 -87
  17. package/dist/api/generated/index.js +0 -2
  18. package/dist/api/generated/sdk.gen.js +0 -4222
  19. package/dist/api/generated/types.gen.js +0 -2
  20. package/dist/api/generated/zod.gen.js +0 -7217
  21. package/dist/commands/add.js +0 -315
  22. package/dist/commands/auth.js +0 -14
  23. package/dist/commands/create.js +0 -192
  24. package/dist/commands/design.js +0 -108
  25. package/dist/commands/guide.js +0 -15
  26. package/dist/commands/init.js +0 -21
  27. package/dist/commands/list-schemas.js +0 -177
  28. package/dist/commands/list.js +0 -39
  29. package/dist/commands/loop.js +0 -101
  30. package/dist/commands/map.js +0 -40
  31. package/dist/commands/mark.js +0 -27
  32. package/dist/commands/move.js +0 -35
  33. package/dist/commands/remove.js +0 -170
  34. package/dist/commands/rename.js +0 -53
  35. package/dist/commands/resize.js +0 -30
  36. package/dist/commands/search.js +0 -14
  37. package/dist/commands/set.js +0 -199
  38. package/dist/commands/show-schemas.js +0 -259
  39. package/dist/commands/show.js +0 -56
  40. package/dist/commands/summary.js +0 -13
  41. package/dist/commands/update.js +0 -240
  42. package/dist/lib/auth.js +0 -331
  43. package/dist/lib/config.js +0 -80
  44. package/dist/lib/excalidraw-schema.js +0 -66
  45. package/dist/lib/globals.js +0 -8
  46. package/dist/lib/model.js +0 -11
  47. package/dist/lib/project-config.js +0 -103
  48. package/dist/lib/resolve.js +0 -59
  49. package/dist/lib/scenario.js +0 -15
  50. package/dist/slices/add-scenario/index.js +0 -103
  51. package/dist/slices/guide/guides/codegen.js +0 -339
  52. package/dist/slices/guide/guides/connect-slices.js +0 -202
  53. package/dist/slices/guide/guides/create-slices.js +0 -273
  54. package/dist/slices/guide/guides/explore.js +0 -238
  55. package/dist/slices/guide/guides/information-flow.js +0 -304
  56. package/dist/slices/guide/guides/scenarios.js +0 -214
  57. package/dist/slices/guide/index.js +0 -40
  58. package/dist/slices/help/index.js +0 -96
  59. package/dist/slices/help/topics/build-codegen.js +0 -109
  60. package/dist/slices/help/topics/build-slice.js +0 -147
  61. package/dist/slices/help/topics/check-completeness.js +0 -57
  62. package/dist/slices/help/topics/connect-slices.js +0 -99
  63. package/dist/slices/help/topics/explore-model.js +0 -112
  64. package/dist/slices/help/topics/json-reference.js +0 -188
  65. package/dist/slices/help/topics/linked-copies.js +0 -89
  66. package/dist/slices/help/topics/manipulate-canvas.js +0 -150
  67. package/dist/slices/help/topics/write-scenarios.js +0 -162
  68. package/dist/slices/init/index.js +0 -86
  69. package/dist/slices/init/loop.js +0 -60
  70. package/dist/slices/login/index.js +0 -20
  71. package/dist/slices/logout/index.js +0 -14
  72. package/dist/slices/open-app/index.js +0 -36
  73. package/dist/slices/whoami/index.js +0 -19
@@ -1,315 +0,0 @@
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
- }
@@ -1,14 +0,0 @@
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
- }
@@ -1,192 +0,0 @@
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
- }
@@ -1,108 +0,0 @@
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
- }
@@ -1,15 +0,0 @@
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
- }