@auto-engineer/server-generator-apollo-emmett 1.103.0 → 1.105.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 (41) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +6 -6
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +119 -0
  5. package/dist/src/codegen/scaffoldFromSchema.d.ts +10 -0
  6. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
  7. package/dist/src/codegen/scaffoldFromSchema.js +55 -20
  8. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
  9. package/dist/src/codegen/templates/command/decide.specs.ts +16 -4
  10. package/dist/src/codegen/templates/command/decide.ts.ejs +4 -1
  11. package/dist/src/codegen/templates/command/evolve.specs.ts +16 -15
  12. package/dist/src/codegen/templates/command/evolve.ts.ejs +16 -15
  13. package/dist/src/codegen/templates/command/handle.specs.ts +146 -0
  14. package/dist/src/codegen/templates/command/handle.ts.ejs +21 -1
  15. package/dist/src/codegen/templates/query/projection.specs.ts +4 -1
  16. package/dist/src/codegen/templates/query/projection.ts.ejs +7 -1
  17. package/dist/src/codegen/templates/react/react.specs.specs.ts +0 -32
  18. package/dist/src/codegen/templates/react/react.ts.specs.ts +0 -49
  19. package/dist/src/commands/generate-server.d.ts +4 -0
  20. package/dist/src/commands/generate-server.d.ts.map +1 -1
  21. package/dist/src/commands/generate-server.js +37 -3
  22. package/dist/src/commands/generate-server.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/ketchup-plan.md +2 -0
  25. package/package.json +4 -4
  26. package/src/codegen/formatTsValue.specs.ts +12 -0
  27. package/src/codegen/formatTsValueSimple.specs.ts +1 -1
  28. package/src/codegen/scaffoldErrors.specs.ts +40 -0
  29. package/src/codegen/scaffoldFromSchema.ts +70 -31
  30. package/src/codegen/templates/command/decide.specs.ts +16 -4
  31. package/src/codegen/templates/command/decide.ts.ejs +4 -1
  32. package/src/codegen/templates/command/evolve.specs.ts +16 -15
  33. package/src/codegen/templates/command/evolve.ts.ejs +16 -15
  34. package/src/codegen/templates/command/handle.specs.ts +146 -0
  35. package/src/codegen/templates/command/handle.ts.ejs +21 -1
  36. package/src/codegen/templates/query/projection.specs.ts +4 -1
  37. package/src/codegen/templates/query/projection.ts.ejs +7 -1
  38. package/src/codegen/templates/react/react.specs.specs.ts +0 -32
  39. package/src/codegen/templates/react/react.ts.specs.ts +0 -49
  40. package/src/commands/generate-server.specs.ts +71 -0
  41. package/src/commands/generate-server.ts +45 -2
@@ -87,27 +87,28 @@ describe('evolve.ts.ejs', () => {
87
87
  /**
88
88
  * ## IMPLEMENTATION INSTRUCTIONS ##
89
89
  *
90
- * This function defines how the domain state evolves in response to events.
90
+ * Evolve domain state in response to events.
91
+ * Only track fields needed for future decisions in decide.ts.
91
92
  *
92
- * Guidelines:
93
- * - Apply only the **minimal** necessary changes for future decisions in \`decide.ts\`.
94
- * - Ignore any event fields not required for decision-making logic.
95
- * - If the event doesn’t change decision-relevant state, return the existing \`state\`.
96
- * - Prefer immutability: always return a **new state object**.
97
- * - Avoid spreading all of \`event.data\` unless all fields are relevant.
98
- * - If State is a discriminated union (e.g., NotInitialized | Initialized),
99
- * always include the discriminant field (e.g., \`status\`) in return values.
100
- * Do NOT spread \`...state\` and add variant-specific fields — construct a complete object:
101
- * \`return { status: 'initialized', field: event.data.field };\`
93
+ * RULES:
94
+ * - Return a new object literal for state transitions.
95
+ * For no-op (event doesn’t affect state), return the existing \`state\`.
96
+ * - If State is a discriminated union (check state.ts), EVERY return
97
+ * MUST include the discriminant field as a string literal.
98
+ * Example: \`return { status: ‘active’, field: event.data.field };\`
99
+ * - NEVER spread ...event.data or ...state list each field explicitly.
100
+ *
101
+ * VERIFY before finalizing:
102
+ * [ ] Every return (except \`return state;\`) matches a variant from state.ts
103
+ * [ ] Every return includes the discriminant field
104
+ * [ ] No spread operators in return statements
102
105
  */
103
106
 
104
107
  export const evolve = (state: State, event: ListingCreated): State => {
105
108
  switch (event.type) {
106
109
  case 'ListingCreated': {
107
- // TODO: Update state based on ListingCreated
108
- return {
109
- ...state,
110
- };
110
+ // TODO: Return { status: 'variant', field1: ..., field2: ... } matching state.ts.
111
+ return state;
111
112
  }
112
113
  default:
113
114
  return state;
@@ -7,18 +7,21 @@
7
7
  /**
8
8
  * ## IMPLEMENTATION INSTRUCTIONS ##
9
9
  *
10
- * This function defines how the domain state evolves in response to events.
10
+ * Evolve domain state in response to events.
11
+ * Only track fields needed for future decisions in decide.ts.
11
12
  *
12
- * Guidelines:
13
- * - Apply only the **minimal** necessary changes for future decisions in `decide.ts`.
14
- * - Ignore any event fields not required for decision-making logic.
15
- * - If the event doesn’t change decision-relevant state, return the existing `state`.
16
- * - Prefer immutability: always return a **new state object**.
17
- * - Avoid spreading all of `event.data` unless all fields are relevant.
18
- * - If State is a discriminated union (e.g., NotInitialized | Initialized),
19
- * always include the discriminant field (e.g., `status`) in return values.
20
- * Do NOT spread `...state` and add variant-specific fields — construct a complete object:
21
- * `return { status: 'initialized', field: event.data.field };`
13
+ * RULES:
14
+ * - Return a new object literal for state transitions.
15
+ * For no-op (event doesn’t affect state), return the existing `state`.
16
+ * - If State is a discriminated union (check state.ts), EVERY return
17
+ * MUST include the discriminant field as a string literal.
18
+ * Example: `return { status: ‘active’, field: event.data.field };`
19
+ * - NEVER spread ...event.data or ...state list each field explicitly.
20
+ *
21
+ * VERIFY before finalizing:
22
+ * [ ] Every return (except `return state;`) matches a variant from state.ts
23
+ * [ ] Every return includes the discriminant field
24
+ * [ ] No spread operators in return statements
22
25
  */
23
26
 
24
27
  export const evolve = (
@@ -28,10 +31,8 @@
28
31
  switch (event.type) {
29
32
  <% events.forEach(event => { -%>
30
33
  case '<%= event.type %>': {
31
- // TODO: Update state based on <%= event.type %>
32
- return {
33
- ...state
34
- };
34
+ // TODO: Return { status: 'variant', field1: ..., field2: ... } matching state.ts.
35
+ return state;
35
36
  }
36
37
  <% }); -%>
37
38
  default:
@@ -331,4 +331,150 @@ describe('generateScaffoldFilePlans', () => {
331
331
  "
332
332
  `);
333
333
  });
334
+ it('should generate stream guard when not all commands share stream pattern fields', async () => {
335
+ const spec: SpecsSchema = {
336
+ variant: 'specs',
337
+ narratives: [
338
+ {
339
+ name: 'Member tracks workouts',
340
+ slices: [
341
+ {
342
+ type: 'command',
343
+ name: 'Track workouts',
344
+ stream: 'workouts-${memberId}',
345
+ client: {
346
+ specs: [],
347
+ },
348
+ server: {
349
+ description: 'test',
350
+ specs: [
351
+ {
352
+ type: 'gherkin',
353
+ feature: 'Track workouts',
354
+ rules: [
355
+ {
356
+ name: 'Should log workout',
357
+ examples: [
358
+ {
359
+ name: 'Member logs a workout',
360
+ steps: [
361
+ {
362
+ keyword: 'When',
363
+ text: 'LogWorkout',
364
+ docString: { memberId: 'm1', exercise: 'squat' },
365
+ },
366
+ {
367
+ keyword: 'Then',
368
+ text: 'WorkoutLogged',
369
+ docString: { memberId: 'm1', exercise: 'squat' },
370
+ },
371
+ ],
372
+ },
373
+ ],
374
+ },
375
+ {
376
+ name: 'Should calculate points',
377
+ examples: [
378
+ {
379
+ name: 'Points are calculated',
380
+ steps: [
381
+ {
382
+ keyword: 'When',
383
+ text: 'CalculatePoints',
384
+ docString: { season: 'winter' },
385
+ },
386
+ {
387
+ keyword: 'Then',
388
+ text: 'PointsCalculated',
389
+ docString: { season: 'winter', points: 10 },
390
+ },
391
+ ],
392
+ },
393
+ ],
394
+ },
395
+ ],
396
+ },
397
+ ],
398
+ data: {
399
+ items: [
400
+ {
401
+ target: { type: 'Event', name: 'WorkoutLogged' },
402
+ destination: {
403
+ type: 'stream',
404
+ pattern: 'workouts-${memberId}',
405
+ },
406
+ },
407
+ ],
408
+ },
409
+ },
410
+ },
411
+ ],
412
+ },
413
+ ],
414
+ messages: [
415
+ {
416
+ type: 'command',
417
+ name: 'LogWorkout',
418
+ fields: [
419
+ { name: 'memberId', type: 'string', required: true },
420
+ { name: 'exercise', type: 'string', required: true },
421
+ ],
422
+ },
423
+ {
424
+ type: 'command',
425
+ name: 'CalculatePoints',
426
+ fields: [{ name: 'season', type: 'string', required: true }],
427
+ },
428
+ {
429
+ type: 'event',
430
+ name: 'WorkoutLogged',
431
+ source: 'internal',
432
+ fields: [
433
+ { name: 'memberId', type: 'string', required: true },
434
+ { name: 'exercise', type: 'string', required: true },
435
+ ],
436
+ },
437
+ {
438
+ type: 'event',
439
+ name: 'PointsCalculated',
440
+ source: 'internal',
441
+ fields: [
442
+ { name: 'season', type: 'string', required: true },
443
+ { name: 'points', type: 'number', required: true },
444
+ ],
445
+ },
446
+ ],
447
+ };
448
+
449
+ const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
450
+ const handleFile = plans.find((p) => p.outputPath.endsWith('handle.ts'));
451
+
452
+ expect(handleFile?.contents).toMatchInlineSnapshot(`
453
+ "import { CommandHandler, type EventStore, type MessageHandlerResult } from '@event-driven-io/emmett';
454
+ import { evolve } from './evolve';
455
+ import { initialState } from './state';
456
+ import { decide } from './decide';
457
+ import type { LogWorkout, CalculatePoints } from './commands';
458
+
459
+ const handler = CommandHandler({
460
+ evolve,
461
+ initialState,
462
+ });
463
+
464
+ export const handle = async (
465
+ eventStore: EventStore,
466
+ command: LogWorkout | CalculatePoints,
467
+ ): Promise<MessageHandlerResult> => {
468
+ const commandData = command.data;
469
+ if (!('memberId' in commandData)) {
470
+ throw new Error('Cannot determine stream: "memberId" not in command data');
471
+ }
472
+ const streamId = \`workouts-\${commandData.memberId}\`;
473
+
474
+ await handler(eventStore, streamId, (state) => decide(command, state));
475
+ return undefined;
476
+ };
477
+ "
478
+ `);
479
+ });
334
480
  });
@@ -53,6 +53,16 @@ const integrationCalls = integrationData.map((d, i) => {
53
53
 
54
54
  const resultVarName = integrationCalls.find(call => !!call.varName)?.varName;
55
55
  const needsReturnValue = typeof resultVarName === 'string';
56
+
57
+ const streamVars = [];
58
+ const streamPatternStr = stream?.pattern ?? '';
59
+ const svRegex = /\$\{([^}]+)\}/g;
60
+ let svMatch;
61
+ while ((svMatch = svRegex.exec(streamPatternStr)) !== null) {
62
+ streamVars.push(svMatch[1]);
63
+ }
64
+ const allCmdFieldSets = commands.map(c => new Set((c.fields ?? []).map(f => f.name)));
65
+ const needsStreamGuard = streamVars.length > 0 && !allCmdFieldSets.every(fs => streamVars.every(v => fs.has(v)));
56
66
  %>
57
67
 
58
68
  <% integrationSideEffectImports.forEach((importSource) => { %>
@@ -86,7 +96,17 @@ eventStore: EventStore,
86
96
  command: <%= commands.map(c => pascalCase(c.type)).join(' | ') %>
87
97
  ): Promise<MessageHandlerResult> => {
88
98
  <% if (stream?.pattern?.includes('${')) { -%>
89
- const streamId = `<%= stream.pattern.replace(/\$\{([^}]+)\}/g, (_, key) => `\${command.data.${key}}`) %>`;
99
+ <% if (needsStreamGuard) { -%>
100
+ const commandData = command.data;
101
+ <% for (const v of streamVars) { -%>
102
+ if (!('<%= v %>' in commandData)) {
103
+ throw new Error('Cannot determine stream: "<%= v %>" not in command data');
104
+ }
105
+ <% } -%>
106
+ const streamId = `<%= stream.pattern.replace(/\$\{([^}]+)\}/g, (_, key) => `\${commandData.${key}}`) %>`;
107
+ <% } else { -%>
108
+ const streamId = `<%= stream.pattern.replace(/\$\{([^}]+)\}/g, (_, key) => `\${command.data.${key}}`) %>`;
109
+ <% } -%>
90
110
  <% } else { -%>
91
111
  const streamId = '<%= stream?.pattern ?? 'unknown-stream' %>';
92
112
  <% } -%>
@@ -562,8 +562,11 @@ describe('projection.ts.ejs', () => {
562
562
  // SINGLETON AGGREGATION PATTERN
563
563
  // This projection maintains a single document that aggregates data from multiple entities.
564
564
  // Use internal state to track individual entity information for accurate calculations.
565
+
566
+ type EntityData = TodoAdded['data'];
567
+
565
568
  interface InternalTodoSummary extends TodoSummary {
566
- _entities?: Record<string, { status?: string; [key: string]: unknown }>;
569
+ _entities?: Record<string, EntityData>;
567
570
  }
568
571
 
569
572
  type AllEvents = TodoAdded;
@@ -45,8 +45,14 @@ import { <%= stateEnums.join(', ') %> } from '../../../shared';
45
45
  // SINGLETON AGGREGATION PATTERN
46
46
  // This projection maintains a single document that aggregates data from multiple entities.
47
47
  // Use internal state to track individual entity information for accurate calculations.
48
+ <% if (events.length === 1) { %>
49
+ type EntityData = <%= pascalCase(events[0].type) %>['data'];
50
+ <% } else { %>
51
+ type EntityData = Partial<<%= events.map(e => pascalCase(e.type) + "['data']").join(' & ') %>>;
52
+ <% } %>
53
+
48
54
  interface Internal<%= pascalCase(targetName || 'State') %> extends <%= pascalCase(targetName || 'State') %> {
49
- _entities?: Record<string, { status?: string; [key: string]: unknown }>;
55
+ _entities?: Record<string, EntityData>;
50
56
  }
51
57
  <% } %>
52
58
  type AllEvents = <%= allEventTypes %>;
@@ -72,14 +72,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
72
72
  name: 'Send notification to host',
73
73
  server: {
74
74
  description: 'Sends a host notification command in response to BookingRequested',
75
- data: {
76
- items: [
77
- {
78
- target: { type: 'Command', name: 'NotifyHost' },
79
- destination: { type: 'stream', pattern: 'booking-${hostId}' },
80
- },
81
- ],
82
- },
83
75
  specs: [
84
76
  {
85
77
  type: 'gherkin',
@@ -291,14 +283,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
291
283
  name: 'Process tip after completion',
292
284
  server: {
293
285
  description: 'Processes tip command after appointment completion',
294
- data: {
295
- items: [
296
- {
297
- target: { type: 'Command', name: 'ProcessTip' },
298
- destination: { type: 'stream', pattern: 'tips-${appointmentId}' },
299
- },
300
- ],
301
- },
302
286
  specs: [
303
287
  {
304
288
  type: 'gherkin',
@@ -409,14 +393,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
409
393
  name: 'notify milestone',
410
394
  server: {
411
395
  description: 'Notifies on milestone',
412
- data: {
413
- items: [
414
- {
415
- target: { type: 'Command', name: 'SendNotification' },
416
- destination: { type: 'stream', pattern: 'notif-${memberId}' },
417
- },
418
- ],
419
- },
420
396
  specs: [
421
397
  {
422
398
  type: 'gherkin',
@@ -532,14 +508,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
532
508
  name: 'notify barber of cancellation',
533
509
  server: {
534
510
  description: 'Notifies barber when appointment is cancelled',
535
- data: {
536
- items: [
537
- {
538
- target: { type: 'Command', name: 'NotifyBarber' },
539
- destination: { type: 'stream', pattern: 'barber-${barberId}' },
540
- },
541
- ],
542
- },
543
511
  specs: [
544
512
  {
545
513
  type: 'gherkin',
@@ -15,14 +15,6 @@ describe('react.ts.ejs', () => {
15
15
  name: 'missing-when-slice',
16
16
  server: {
17
17
  description: 'A react slice with no When step',
18
- data: {
19
- items: [
20
- {
21
- target: { type: 'Command', name: 'DoSomething' },
22
- destination: { type: 'stream', pattern: 'things-${id}' },
23
- },
24
- ],
25
- },
26
18
  specs: [
27
19
  {
28
20
  type: 'gherkin',
@@ -113,14 +105,6 @@ describe('react.ts.ejs', () => {
113
105
  name: 'Send order confirmation',
114
106
  server: {
115
107
  description: 'Sends confirmation after order placed',
116
- data: {
117
- items: [
118
- {
119
- target: { type: 'Command', name: 'SendConfirmation' },
120
- destination: { type: 'stream', pattern: 'confirmations-${orderId}' },
121
- },
122
- ],
123
- },
124
108
  specs: [
125
109
  {
126
110
  type: 'gherkin',
@@ -238,14 +222,6 @@ describe('react.ts.ejs', () => {
238
222
  name: 'Notify Barber of Cancellation',
239
223
  server: {
240
224
  description: 'Notifies barber when appointment is cancelled',
241
- data: {
242
- items: [
243
- {
244
- target: { type: 'Command', name: 'NotifyBarber' },
245
- destination: { type: 'stream', pattern: 'barber-${barberId}' },
246
- },
247
- ],
248
- },
249
225
  specs: [
250
226
  {
251
227
  type: 'gherkin',
@@ -335,7 +311,6 @@ describe('react.ts.ejs', () => {
335
311
  items: [
336
312
  {
337
313
  target: { type: 'Event', name: 'BarberNotified' },
338
- destination: { type: 'stream', pattern: 'barber-${barberId}' },
339
314
  },
340
315
  ],
341
316
  },
@@ -466,14 +441,6 @@ export type BarberNotified = Event<
466
441
  name: 'notify barber of cancellation',
467
442
  server: {
468
443
  description: 'Notifies barber when appointment is cancelled',
469
- data: {
470
- items: [
471
- {
472
- target: { type: 'Command', name: 'NotifyBarber' },
473
- destination: { type: 'stream', pattern: 'barber-${barberId}' },
474
- },
475
- ],
476
- },
477
444
  specs: [
478
445
  {
479
446
  type: 'gherkin',
@@ -607,14 +574,6 @@ export type BarberNotified = Event<
607
574
  name: 'notify milestone',
608
575
  server: {
609
576
  description: 'Notifies on milestone',
610
- data: {
611
- items: [
612
- {
613
- target: { type: 'Command', name: 'SendNotification' },
614
- destination: { type: 'stream', pattern: 'notif-${memberId}' },
615
- },
616
- ],
617
- },
618
577
  specs: [
619
578
  {
620
579
  type: 'gherkin',
@@ -728,14 +687,6 @@ export type BarberNotified = Event<
728
687
  name: 'check record updates',
729
688
  server: {
730
689
  description: 'Updates records after workout',
731
- data: {
732
- items: [
733
- {
734
- target: { type: 'Command', name: 'UpdateRecord' },
735
- destination: { type: 'stream', pattern: 'records-${memberId}' },
736
- },
737
- ],
738
- },
739
690
  specs: [
740
691
  {
741
692
  type: 'gherkin',
@@ -40,6 +40,9 @@ export type ServerGenerationFailedEvent = Event<'ServerGenerationFailed', {
40
40
  destination: string;
41
41
  error: string;
42
42
  model: Model;
43
+ flowName?: string;
44
+ sliceName?: string;
45
+ sliceType?: string;
43
46
  }>;
44
47
  export type SliceGeneratedEvent = Event<'SliceGenerated', {
45
48
  flowName: string;
@@ -67,6 +70,7 @@ export declare function deriveModelPath(destination: string): string;
67
70
  export declare function writeModelToDisk(model: Model, destination: string): Promise<void>;
68
71
  export declare function cleanServerDir(serverDir: string): Promise<void>;
69
72
  export declare function writeHealthResolver(serverDir: string): Promise<void>;
73
+ export declare function createServerFailureEvent(command: GenerateServerCommand, error: unknown): ServerGenerationFailedEvent;
70
74
  export declare function createSliceGeneratedEvent(flow: Narrative, slice: Slice, command: GenerateServerCommand): SliceGeneratedEvent;
71
75
  export declare function emitSliceGenerationFailedForDuplicates(duplicateCommands: DuplicateCommandInfo[], spec: Model, command: GenerateServerCommand, events: GenerateServerEvents[]): void;
72
76
  export declare function emitSliceGenerationFailedForFieldIssues(fieldIssues: FieldIssue[], spec: Model, command: GenerateServerCommand, events: GenerateServerEvents[]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"generate-server.d.ts","sourceRoot":"","sources":["../../../src/commands/generate-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generate-server.d.ts","sourceRoot":"","sources":["../../../src/commands/generate-server.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import { defineCommandHandler } from '@auto-engineer/message-bus';
6
6
  import createDebug from 'debug';
7
7
  import { execa } from 'execa';
8
8
  import fs from 'fs-extra';
9
- import { generateScaffoldFilePlans, writeScaffoldFilePlans, } from '../codegen/scaffoldFromSchema.js';
9
+ import { generateScaffoldFilePlans, ScaffoldError, TemplateRenderError, writeScaffoldFilePlans, } from '../codegen/scaffoldFromSchema.js';
10
10
  import { ensureDirExists, ensureDirPath, toKebabCase } from '../codegen/utils/path.js';
11
11
  const debug = createDebug('auto:server-generator-apollo-emmett');
12
12
  const debugFiles = createDebug('auto:server-generator-apollo-emmett:files');
@@ -167,9 +167,33 @@ function stripAnsiCodes(text) {
167
167
  // biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching ANSI escape sequences
168
168
  return text.replace(/\x1B\[[0-9;]*m/g, '');
169
169
  }
170
- function createServerFailureEvent(command, error) {
170
+ export function createServerFailureEvent(command, error) {
171
171
  debug('Server generation failed with error: %O', error);
172
- const errorMessage = error instanceof Error ? stripAnsiCodes(error.stack ?? error.message) : 'Unknown error occurred';
172
+ let flowName;
173
+ let sliceName;
174
+ let sliceType;
175
+ let templateFile;
176
+ let errorMessage;
177
+ if (error instanceof Error) {
178
+ let current = error;
179
+ while (current instanceof Error) {
180
+ if (current instanceof ScaffoldError) {
181
+ flowName = current.flowName;
182
+ sliceName = current.sliceName;
183
+ sliceType = current.sliceType;
184
+ }
185
+ if (current instanceof TemplateRenderError) {
186
+ templateFile = current.templateFile;
187
+ }
188
+ current = current.cause;
189
+ }
190
+ const rootError = findRootCause(error);
191
+ const baseMessage = stripAnsiCodes(rootError.stack ?? rootError.message);
192
+ errorMessage = templateFile !== undefined ? `[template=${templateFile}] ${baseMessage}` : baseMessage;
193
+ }
194
+ else {
195
+ errorMessage = 'Unknown error occurred';
196
+ }
173
197
  return {
174
198
  type: 'ServerGenerationFailed',
175
199
  data: {
@@ -177,12 +201,22 @@ function createServerFailureEvent(command, error) {
177
201
  destination: command.data.destination,
178
202
  error: errorMessage,
179
203
  model: command.data.model,
204
+ flowName,
205
+ sliceName,
206
+ sliceType,
180
207
  },
181
208
  timestamp: new Date(),
182
209
  requestId: command.requestId,
183
210
  correlationId: command.correlationId,
184
211
  };
185
212
  }
213
+ function findRootCause(error) {
214
+ let current = error;
215
+ while (current.cause instanceof Error) {
216
+ current = current.cause;
217
+ }
218
+ return current;
219
+ }
186
220
  export function createSliceGeneratedEvent(flow, slice, command) {
187
221
  return {
188
222
  type: 'SliceGenerated',