@auto-engineer/server-generator-apollo-emmett 1.70.0 → 1.72.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @auto-engineer/server-generator-apollo-emmett@1.70.0 build /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
2
+ > @auto-engineer/server-generator-apollo-emmett@1.72.0 build /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
3
3
  > tsc && tsx ../../scripts/fix-esm-imports.ts && rm -rf dist/src/codegen/templates && mkdir -p dist/src/codegen && cp -r src/codegen/templates dist/src/codegen/templates && cp src/server.ts dist/src && cp -r src/utils dist/src && cp -r src/domain dist/src
4
4
 
5
5
  Fixed ESM imports in dist/
@@ -1,14 +1,14 @@
1
1
 
2
- > @auto-engineer/server-generator-apollo-emmett@1.69.0 test /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
2
+ > @auto-engineer/server-generator-apollo-emmett@1.71.0 test /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
3
3
  > vitest run --reporter=dot
4
4
 
5
5
 
6
6
   RUN  v3.2.4 /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
7
7
 
8
- ·························································································································································-·················
8
+ ···························································································································································-·················
9
9
 
10
10
   Test Files  30 passed | 1 skipped (31)
11
-  Tests  170 passed | 1 skipped (171)
12
-  Start at  06:29:43
13
-  Duration  37.68s (transform 6.24s, setup 0ms, collect 76.15s, tests 19.55s, environment 13ms, prepare 6.57s)
11
+  Tests  172 passed | 1 skipped (173)
12
+  Start at  15:38:59
13
+  Duration  25.09s (transform 3.81s, setup 0ms, collect 53.47s, tests 9.11s, environment 7ms, prepare 4.77s)
14
14
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @auto-engineer/server-generator-apollo-emmett@1.69.0 type-check /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
2
+ > @auto-engineer/server-generator-apollo-emmett@1.71.0 type-check /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
3
3
  > tsc --noEmit --project tsconfig.json
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # @auto-engineer/server-generator-apollo-emmett
2
2
 
3
+ ## 1.72.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`cb7de6a`](https://github.com/BeOnAuto/auto-engineer/commit/cb7de6a5a8c42bf1849a7b4f602d8334d5399895) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: wire command gate into processCommand with signal suppression
8
+
9
+ - [`cddce02`](https://github.com/BeOnAuto/auto-engineer/commit/cddce020109adcbb7aa422ab89bc14805ef9f39a) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: generate real commandSender.send() in react scaffold
10
+ - **server-generator-apollo-emmett**: generate aggregateStream for Given states
11
+ - **pipeline**: add command gate concurrency control to ketchup plan
12
+ - **server-generator-apollo-emmett**: mark Burst 3 done in ketchup plan
13
+ - **server-generator-apollo-emmett**: add Burst 3 to ketchup plan for commandSender.send generation
14
+
15
+ - [`ebc98fa`](https://github.com/BeOnAuto/auto-engineer/commit/ebc98fa1e38404bcb4dbaad28b9281ed25c9b27b) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: add cancel-in-progress strategy to command gate
16
+
17
+ - [`27f3e77`](https://github.com/BeOnAuto/auto-engineer/commit/27f3e777b871a4cc5679331e445fe991cd2bfc8f) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: add signal to PipelineContext and registerConcurrency to server
18
+
19
+ - [`c00b253`](https://github.com/BeOnAuto/auto-engineer/commit/c00b25354839afb1b506c27d19795da3fdc24280) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: create command gate with registration and passthrough
20
+
21
+ - [`59b9032`](https://github.com/BeOnAuto/auto-engineer/commit/59b903255b34a3beb6579d91f656530b05fef807) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: add queue strategy to command gate
22
+
23
+ - [`80f544e`](https://github.com/BeOnAuto/auto-engineer/commit/80f544ebc7617ffe83d880cfb34c11f31ca8a340) Thanks [@github-actions[bot]](https://github.com/github-actions%5Bbot%5D)! - - **server-generator-apollo-emmett**: generate real commandSender.send() in react scaffold
24
+ - **server-generator-apollo-emmett**: generate aggregateStream for Given states
25
+ - **server-generator-apollo-emmett**: seed event store with Given state data in react specs template
26
+ - **global**: version packages
27
+ - **server-generator-apollo-emmett**: mark Burst 3 done in ketchup plan
28
+
29
+ ### Patch Changes
30
+
31
+ - [`c5ce884`](https://github.com/BeOnAuto/auto-engineer/commit/c5ce884e233a8d9eec5c76db41b166bb86b09e70) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **global**: wire config-level concurrency for command gate
32
+
33
+ - [`090b856`](https://github.com/BeOnAuto/auto-engineer/commit/090b8569ce5eb62b680b48fbc909cbbad12ff377) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: mark command gate concurrency control done in ketchup plan
34
+
35
+ - [`5e8b6a8`](https://github.com/BeOnAuto/auto-engineer/commit/5e8b6a8ec6502c6e9b955eab93d4d332c5a8def9) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: verify cancel-in-progress suppresses events through server
36
+
37
+ - [`e4b956c`](https://github.com/BeOnAuto/auto-engineer/commit/e4b956cd2751ea78e9b8a4725f9be755abf2bb24) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **pipeline**: verify queue concurrency and sendCommand gate integration
38
+
39
+ - [`1b7cb23`](https://github.com/BeOnAuto/auto-engineer/commit/1b7cb230b570c763e24c6a0eb99b39901e671500) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: call start/close lifecycle on reactor in ReactorSpecification
40
+
41
+ - Updated dependencies [[`cb7de6a`](https://github.com/BeOnAuto/auto-engineer/commit/cb7de6a5a8c42bf1849a7b4f602d8334d5399895), [`cddce02`](https://github.com/BeOnAuto/auto-engineer/commit/cddce020109adcbb7aa422ab89bc14805ef9f39a), [`ebc98fa`](https://github.com/BeOnAuto/auto-engineer/commit/ebc98fa1e38404bcb4dbaad28b9281ed25c9b27b), [`27f3e77`](https://github.com/BeOnAuto/auto-engineer/commit/27f3e777b871a4cc5679331e445fe991cd2bfc8f), [`c5ce884`](https://github.com/BeOnAuto/auto-engineer/commit/c5ce884e233a8d9eec5c76db41b166bb86b09e70), [`c00b253`](https://github.com/BeOnAuto/auto-engineer/commit/c00b25354839afb1b506c27d19795da3fdc24280), [`59b9032`](https://github.com/BeOnAuto/auto-engineer/commit/59b903255b34a3beb6579d91f656530b05fef807), [`090b856`](https://github.com/BeOnAuto/auto-engineer/commit/090b8569ce5eb62b680b48fbc909cbbad12ff377), [`5e8b6a8`](https://github.com/BeOnAuto/auto-engineer/commit/5e8b6a8ec6502c6e9b955eab93d4d332c5a8def9), [`80f544e`](https://github.com/BeOnAuto/auto-engineer/commit/80f544ebc7617ffe83d880cfb34c11f31ca8a340), [`e4b956c`](https://github.com/BeOnAuto/auto-engineer/commit/e4b956cd2751ea78e9b8a4725f9be755abf2bb24), [`1b7cb23`](https://github.com/BeOnAuto/auto-engineer/commit/1b7cb230b570c763e24c6a0eb99b39901e671500)]:
42
+ - @auto-engineer/message-bus@1.72.0
43
+ - @auto-engineer/narrative@1.72.0
44
+
45
+ ## 1.71.0
46
+
47
+ ### Minor Changes
48
+
49
+ - [`82543ae`](https://github.com/BeOnAuto/auto-engineer/commit/82543aec90a4398696e148f9ea26e5f4df51eb91) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: seed event store with Given state data in react specs template
50
+
51
+ - [`6a7a3f2`](https://github.com/BeOnAuto/auto-engineer/commit/6a7a3f237c1267e1835168afaccf21e482e48278) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: generate aggregateStream for Given states
52
+ - **server-generator-apollo-emmett**: add Burst 3 to ketchup plan for commandSender.send generation
53
+ - **server-generator-apollo-emmett**: mark all react Given states bursts done
54
+
55
+ - [`560d192`](https://github.com/BeOnAuto/auto-engineer/commit/560d19212be145f358ab2176c2424f6a8849070f) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: generate real commandSender.send() in react scaffold
56
+
57
+ - [`b5f823a`](https://github.com/BeOnAuto/auto-engineer/commit/b5f823a8b6e08c05518220aedad5b686e8bf37ac) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: generate aggregateStream for Given states
58
+
59
+ ### Patch Changes
60
+
61
+ - [`dde21b9`](https://github.com/BeOnAuto/auto-engineer/commit/dde21b9f06c8aaba5d18cfa4f1a68eed3bf4c190) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: mark all react Given states bursts done
62
+
63
+ - [`28efa44`](https://github.com/BeOnAuto/auto-engineer/commit/28efa44f51373af11febb3fff00d85d1b3350784) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: mark Burst 3 done in ketchup plan
64
+
65
+ - [`8b1238a`](https://github.com/BeOnAuto/auto-engineer/commit/8b1238ab2c98ad7bf396987edabf642b43b730bd) Thanks [@rami-hatoum](https://github.com/rami-hatoum)! - - **server-generator-apollo-emmett**: add ketchup plan for react slice Given states fix
66
+
67
+ - [`e7a4ddf`](https://github.com/BeOnAuto/auto-engineer/commit/e7a4ddff607260651ecbf504a881a21db4657f19) Thanks [@github-actions[bot]](https://github.com/github-actions%5Bbot%5D)! - - **job-graph-processor**: increase time tolerance for parallel job dispatch test
68
+ - **root**: update @event-driven-io/emmett and @event-driven-io/emmett-sqlite versions to 0.42.0
69
+ - **ci**: remove --reporter=append to show pnpm install errors
70
+ - **generate-react-client**: update landing page header text
71
+ - **pipeline**: emit to event store before SSE broadcast in broadcastPipelineRunStarted
72
+ - Updated dependencies [[`82543ae`](https://github.com/BeOnAuto/auto-engineer/commit/82543aec90a4398696e148f9ea26e5f4df51eb91), [`dde21b9`](https://github.com/BeOnAuto/auto-engineer/commit/dde21b9f06c8aaba5d18cfa4f1a68eed3bf4c190), [`28efa44`](https://github.com/BeOnAuto/auto-engineer/commit/28efa44f51373af11febb3fff00d85d1b3350784), [`6a7a3f2`](https://github.com/BeOnAuto/auto-engineer/commit/6a7a3f237c1267e1835168afaccf21e482e48278), [`560d192`](https://github.com/BeOnAuto/auto-engineer/commit/560d19212be145f358ab2176c2424f6a8849070f), [`8b1238a`](https://github.com/BeOnAuto/auto-engineer/commit/8b1238ab2c98ad7bf396987edabf642b43b730bd), [`e7a4ddf`](https://github.com/BeOnAuto/auto-engineer/commit/e7a4ddff607260651ecbf504a881a21db4657f19), [`b5f823a`](https://github.com/BeOnAuto/auto-engineer/commit/b5f823a8b6e08c05518220aedad5b686e8bf37ac)]:
73
+ - @auto-engineer/message-bus@1.71.0
74
+ - @auto-engineer/narrative@1.71.0
75
+
3
76
  ## 1.70.0
4
77
 
5
78
  ### Minor Changes
@@ -368,4 +368,203 @@ describe('react.specs.ts.ejs (react slice)', () => {
368
368
  expect(specFile?.contents).not.toContain("tipAmount: '5.00'");
369
369
  expect(specFile?.contents).not.toContain('tipAmount: "5.00"');
370
370
  });
371
+
372
+ it('should seed event store with Given state data via appendToStream', async () => {
373
+ const spec: SpecsSchema = {
374
+ variant: 'specs',
375
+ narratives: [
376
+ {
377
+ name: 'barbershop flow',
378
+ slices: [
379
+ {
380
+ type: 'command',
381
+ name: 'cancel appointment',
382
+ client: { specs: [] },
383
+ server: {
384
+ description: '',
385
+ specs: [
386
+ {
387
+ type: 'gherkin',
388
+ feature: 'Cancel appointment command',
389
+ rules: [
390
+ {
391
+ name: 'Should cancel appointment',
392
+ examples: [
393
+ {
394
+ name: 'Appointment cancelled',
395
+ steps: [
396
+ {
397
+ keyword: 'When',
398
+ text: 'CancelAppointment',
399
+ docString: { appointmentId: 'apt_001', barberId: 'barber_001' },
400
+ },
401
+ {
402
+ keyword: 'Then',
403
+ text: 'AppointmentCancelled',
404
+ docString: { appointmentId: 'apt_001', barberId: 'barber_001' },
405
+ },
406
+ ],
407
+ },
408
+ ],
409
+ },
410
+ ],
411
+ },
412
+ ],
413
+ },
414
+ },
415
+ {
416
+ type: 'react',
417
+ name: 'notify barber of cancellation',
418
+ server: {
419
+ description: 'Notifies barber when appointment is cancelled',
420
+ data: {
421
+ items: [
422
+ {
423
+ target: { type: 'Command', name: 'NotifyBarber' },
424
+ destination: { type: 'stream', pattern: 'barber-${barberId}' },
425
+ },
426
+ ],
427
+ },
428
+ specs: [
429
+ {
430
+ type: 'gherkin',
431
+ feature: 'Notify barber of cancellation reaction',
432
+ rules: [
433
+ {
434
+ name: 'Should notify barber on cancellation',
435
+ examples: [
436
+ {
437
+ name: 'Barber notified after cancellation',
438
+ steps: [
439
+ {
440
+ keyword: 'Given',
441
+ text: 'Appointment',
442
+ docString: { barberId: 'barber_001', clientName: 'John' },
443
+ },
444
+ {
445
+ keyword: 'When',
446
+ text: 'AppointmentCancelled',
447
+ docString: { appointmentId: 'apt_001', barberId: 'barber_001' },
448
+ },
449
+ {
450
+ keyword: 'Then',
451
+ text: 'NotifyBarber',
452
+ docString: { barberId: 'barber_001', message: 'Appointment cancelled' },
453
+ },
454
+ ],
455
+ },
456
+ ],
457
+ },
458
+ ],
459
+ },
460
+ ],
461
+ },
462
+ },
463
+ ],
464
+ },
465
+ ],
466
+ messages: [
467
+ {
468
+ type: 'command',
469
+ name: 'CancelAppointment',
470
+ fields: [
471
+ { name: 'appointmentId', type: 'string', required: true },
472
+ { name: 'barberId', type: 'string', required: true },
473
+ ],
474
+ },
475
+ {
476
+ type: 'event',
477
+ name: 'AppointmentCancelled',
478
+ source: 'internal',
479
+ fields: [
480
+ { name: 'appointmentId', type: 'string', required: true },
481
+ { name: 'barberId', type: 'string', required: true },
482
+ ],
483
+ },
484
+ {
485
+ type: 'command',
486
+ name: 'NotifyBarber',
487
+ fields: [
488
+ { name: 'barberId', type: 'string', required: true },
489
+ { name: 'message', type: 'string', required: true },
490
+ ],
491
+ },
492
+ {
493
+ type: 'state',
494
+ name: 'Appointment',
495
+ fields: [
496
+ { name: 'barberId', type: 'string', required: true },
497
+ { name: 'clientName', type: 'string', required: true },
498
+ ],
499
+ },
500
+ ],
501
+ };
502
+
503
+ const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
504
+
505
+ const specFile = plans.find((p) => p.outputPath.endsWith('notify-barber-of-cancellation/react.specs.ts'));
506
+ expect(specFile?.contents).toMatchInlineSnapshot(`
507
+ "import { describe, it, beforeEach } from 'vitest';
508
+ import 'reflect-metadata';
509
+ import { getInMemoryEventStore, type InMemoryEventStore, type CommandSender } from '@event-driven-io/emmett';
510
+ import { type ReactorContext, ReactorSpecification } from '../../../shared';
511
+ import { react } from './react';
512
+ import type { AppointmentCancelled } from '../cancel-appointment/events';
513
+ import type { NotifyBarber } from './commands';
514
+
515
+ type ReactorEvent = AppointmentCancelled;
516
+ type ReactorCommand = NotifyBarber;
517
+
518
+ describe('Should notify barber on cancellation', () => {
519
+ let eventStore: InMemoryEventStore;
520
+ let given: ReactorSpecification<ReactorEvent, ReactorCommand, ReactorContext>;
521
+ let messageBus: CommandSender;
522
+
523
+ beforeEach(() => {
524
+ eventStore = getInMemoryEventStore({});
525
+ given = ReactorSpecification.for<ReactorEvent, ReactorCommand, ReactorContext>(
526
+ () => react({ eventStore, commandSender: messageBus, database: eventStore.database }),
527
+ (commandSender) => {
528
+ messageBus = commandSender;
529
+ return {
530
+ eventStore,
531
+ commandSender,
532
+ database: eventStore.database,
533
+ };
534
+ },
535
+ );
536
+ });
537
+
538
+ it('Barber notified after cancellation', async () => {
539
+ await eventStore.appendToStream('Appointment-barber_001', [
540
+ {
541
+ type: 'AppointmentInitialized',
542
+ data: {
543
+ barberId: 'barber_001',
544
+ clientName: 'John',
545
+ },
546
+ },
547
+ ]);
548
+ await given([])
549
+ .when({
550
+ type: 'AppointmentCancelled',
551
+ data: {
552
+ appointmentId: 'apt_001',
553
+ barberId: 'barber_001',
554
+ },
555
+ })
556
+
557
+ .then({
558
+ type: 'NotifyBarber',
559
+ kind: 'Command',
560
+ data: {
561
+ barberId: 'barber_001',
562
+ message: 'Appointment cancelled',
563
+ },
564
+ });
565
+ });
566
+ });
567
+ "
568
+ `);
569
+ });
371
570
  });
@@ -248,36 +248,28 @@ describe('handle.ts.ejs (react slice)', () => {
248
248
  /**
249
249
  * ## IMPLEMENTATION INSTRUCTIONS ##
250
250
  *
251
- * - Inspect event data to determine if the command should be sent.
252
- * - Replace the placeholder logic and \\\`throw\\\` below with real implementation.
253
- * - Send one or more commands via: commandSender.send({...})
254
- * - Optionally return a MessageHandlerResult for SKIP or error cases.
251
+ * - Review the generated send call below and adjust if needed.
252
+ * - Add business logic (validation, conditional sends) as required.
255
253
  */
256
254
 
257
255
  // Event (BookingRequested) fields: bookingId: string, hostId: string, message: string
258
256
 
259
257
  // Command (NotifyHost) fields: hostId: string, notificationType: string, priority: string, channels: string[], message: string, actionRequired: boolean
260
258
 
261
- throw new IllegalStateError('Not yet implemented: react in response to BookingRequested');
262
-
263
- // Example:
264
- // if (event.data.status !== 'expected') {
265
- // return {
266
- // type: 'SKIP',
267
- // reason: 'Condition not met',
268
- // };
269
- // }
270
-
271
- // await commandSender.send({
272
- // type: 'NotifyHost',
273
- // kind: 'Command',
274
- // data: {
275
- // // Map event fields to command fields here
276
- // // e.g., userId: event.data.userId,
277
- // },
278
- // });
259
+ await commandSender.send({
260
+ type: 'NotifyHost',
261
+ kind: 'Command',
262
+ data: {
263
+ hostId: event.data.hostId,
264
+ notificationType: undefined, // TODO: source unknown
265
+ priority: undefined, // TODO: source unknown
266
+ channels: undefined, // TODO: source unknown
267
+ message: event.data.message,
268
+ actionRequired: undefined, // TODO: source unknown
269
+ },
270
+ });
279
271
 
280
- // return;
272
+ return;
281
273
  },
282
274
  });
283
275
  "
@@ -560,6 +552,7 @@ describe('handle.ts.ejs (react slice)', () => {
560
552
  expect(reactFile?.contents).toContain("canHandle: ['PaymentProcessed']");
561
553
  expect(reactFile?.contents).toContain("from '../../order-management/process-payment/events'");
562
554
  expect(reactFile?.contents).not.toContain('ReactToPaymentProcessed');
563
- expect(reactFile?.contents).toContain('Context state available: InventoryReservation (readable from database)');
555
+ expect(reactFile?.contents).not.toContain('readable from database');
556
+ expect(reactFile?.contents).not.toContain('aggregateStream');
564
557
  });
565
558
  });
@@ -115,6 +115,26 @@ describe('<%= ruleDescription %>', () => {
115
115
  `should send ${thenCommands.map(c => c.commandRef).join(', ')} when ${exampleEvent.eventRef} is received`;
116
116
  %>
117
117
  it('<%= description %>', async () => {
118
+ <%
119
+ const givenStates = testCase.given.filter(g =>
120
+ messages.some(m => m.type === 'state' && m.name === g.eventRef)
121
+ );
122
+ for (const gs of givenStates) {
123
+ const stateSchema = states.find(s => s.type === gs.eventRef);
124
+ const eventDef = messages.find(m => m.name === exampleEvent.eventRef);
125
+ const stateFieldNames = (stateSchema?.fields || []).map(f => f.name);
126
+ const eventFieldNames = (eventDef?.fields || []).map(f => f.name);
127
+ const linkingField = stateFieldNames.find(f => eventFieldNames.includes(f));
128
+ if (linkingField) {
129
+ const linkingValue = gs.exampleData[linkingField];
130
+ -%>
131
+ await eventStore.appendToStream('<%= gs.eventRef %>-<%= linkingValue %>', [{
132
+ type: '<%= gs.eventRef %>Initialized',
133
+ data: <%- formatDataObject(gs.exampleData, stateSchema) %>
134
+ }]);
135
+ <% }
136
+ }
137
+ -%>
118
138
  await given([])
119
139
  .when({
120
140
  type: '<%= exampleEvent.eventRef %>',
@@ -42,17 +42,15 @@ eachMessage: async (event, context): Promise<MessageHandlerResult> => {
42
42
  /**
43
43
  * ## IMPLEMENTATION INSTRUCTIONS ##
44
44
  *
45
- * - Inspect event data to determine if the command should be sent.
46
- * - Replace the placeholder logic and \`throw\` below with real implementation.
47
- * - Send one or more commands via: commandSender.send({...})
48
- * - Optionally return a MessageHandlerResult for SKIP or error cases.
49
- <% if (states.length > 0) { -%>
50
- * - Context state available: <%= states.map(s => pascalCase(s.type)).join(', ') %> (readable from database)
51
- <% } -%>
45
+ * - Review the generated send call below and adjust if needed.
46
+ * - Add business logic (validation, conditional sends) as required.
52
47
  */
53
48
  <%
54
49
  const eventDef = messages.find(m => m.name === eventType);
55
50
  const commandDef = messages.find(m => m.name === commandType && m.type === 'command');
51
+ const commandFields = (commandDef?.fields || []);
52
+ const eventFieldSet = new Set((eventDef?.fields || []).map(f => f.name));
53
+ const stateFieldSources = {};
56
54
  -%>
57
55
  <% if (eventDef?.fields?.length) { %>
58
56
  // Event (<%= eventType %>) fields: <%= eventDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
@@ -60,26 +58,58 @@ const commandDef = messages.find(m => m.name === commandType && m.type === 'comm
60
58
  <% if (commandDef?.fields?.length) { %>
61
59
  // Command (<%= commandType %>) fields: <%= commandDef.fields.map(f => f.name + ': ' + (f.tsType || f.type)).join(', ') %>
62
60
  <% } -%>
61
+ <% if (states.length > 0) {
62
+ let hasAggregateStream = false;
63
+ for (const state of states) {
64
+ const stateFieldNames = state.fields.map(f => f.name);
65
+ const eventFieldNames = (eventDef?.fields || []).map(f => f.name);
66
+ const linkingField = stateFieldNames.find(f => eventFieldNames.includes(f));
67
+ if (linkingField) {
68
+ hasAggregateStream = true;
69
+ const varName = camelCase(state.type);
70
+ for (const f of state.fields) {
71
+ if (!stateFieldSources[f.name]) stateFieldSources[f.name] = varName;
72
+ }
73
+ -%>
63
74
 
64
- throw new IllegalStateError('Not yet implemented: react in response to <%= eventType %>');
75
+ const { state: <%= varName %> } = await eventStore.aggregateStream(
76
+ '<%= state.type %>-' + event.data.<%= linkingField %>,
77
+ {
78
+ evolve: (currentState, evt) => ({ ...currentState, ...evt.data }),
79
+ initialState: () => ({}),
80
+ },
81
+ );
82
+ // <%= state.type %> fields: <%= state.fields.map(f => f.name).join(', ') %>
83
+
84
+ <% }
85
+ }
86
+ if (!hasAggregateStream) {
87
+ -%>
65
88
 
66
- // Example:
67
- // if (event.data.status !== 'expected') {
68
- // return {
69
- // type: 'SKIP',
70
- // reason: 'Condition not met',
71
- // };
72
- // }
89
+ <% }
90
+ } else {
91
+ -%>
73
92
 
74
- // await commandSender.send({
75
- // type: '<%= commandType %>',
76
- // kind: 'Command',
77
- // data: {
78
- // // Map event fields to command fields here
79
- // // e.g., userId: event.data.userId,
80
- // },
81
- // });
93
+ <% } -%>
94
+ await commandSender.send({
95
+ type: '<%= commandType %>',
96
+ kind: 'Command',
97
+ data: {
98
+ <% for (const field of commandFields) {
99
+ const fieldName = field.name;
100
+ if (eventFieldSet.has(fieldName)) {
101
+ -%>
102
+ <%= fieldName %>: event.data.<%= fieldName %>,
103
+ <% } else if (stateFieldSources[fieldName]) { -%>
104
+ <%= fieldName %>: <%= stateFieldSources[fieldName] %>.<%= fieldName %>,
105
+ <% } else { -%>
106
+ <%= fieldName %>: undefined, // TODO: source unknown
107
+ <% }
108
+ }
109
+ -%>
110
+ },
111
+ });
82
112
 
83
- // return;
113
+ return;
84
114
  },
85
115
  });