@auto-engineer/server-generator-apollo-emmett 1.69.0 → 1.71.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.69.0 build /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
2
+ > @auto-engineer/server-generator-apollo-emmett@1.71.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.68.0 test /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
2
+ > @auto-engineer/server-generator-apollo-emmett@1.70.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  16:17:34
13
-  Duration  30.08s (transform 4.95s, setup 0ms, collect 64.51s, tests 12.33s, environment 7ms, prepare 4.71s)
11
+  Tests  172 passed | 1 skipped (173)
12
+  Start at  12:06:20
13
+  Duration  22.20s (transform 3.79s, setup 0ms, collect 45.99s, tests 8.68s, environment 6ms, prepare 4.29s)
14
14
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @auto-engineer/server-generator-apollo-emmett@1.68.0 type-check /home/runner/work/auto-engineer/auto-engineer/packages/server-generator-apollo-emmett
2
+ > @auto-engineer/server-generator-apollo-emmett@1.70.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,70 @@
1
1
  # @auto-engineer/server-generator-apollo-emmett
2
2
 
3
+ ## 1.71.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`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
8
+
9
+ - [`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
10
+ - **server-generator-apollo-emmett**: add Burst 3 to ketchup plan for commandSender.send generation
11
+ - **server-generator-apollo-emmett**: mark all react Given states bursts done
12
+
13
+ - [`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
14
+
15
+ - [`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
16
+
17
+ ### Patch Changes
18
+
19
+ - [`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
20
+
21
+ - [`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
22
+
23
+ - [`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
24
+
25
+ - [`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
26
+ - **root**: update @event-driven-io/emmett and @event-driven-io/emmett-sqlite versions to 0.42.0
27
+ - **ci**: remove --reporter=append to show pnpm install errors
28
+ - **generate-react-client**: update landing page header text
29
+ - **pipeline**: emit to event store before SSE broadcast in broadcastPipelineRunStarted
30
+ - 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)]:
31
+ - @auto-engineer/message-bus@1.71.0
32
+ - @auto-engineer/narrative@1.71.0
33
+
34
+ ## 1.70.0
35
+
36
+ ### Minor Changes
37
+
38
+ - [`d5b9b83`](https://github.com/BeOnAuto/auto-engineer/commit/d5b9b83ae2a40f0f34a9e5823fd7f582ffb578a5) Thanks [@github-actions[bot]](https://github.com/github-actions%5Bbot%5D)! - - **generate-react-client**: update landing page header text color and height
39
+ - **pipeline**: remove v1 SettledTracker, PhasedExecutor, and related projections
40
+ - **pipeline**: wire V2 bridges into PipelineServer
41
+ - **pipeline**: add phased bridge with V2 workflow processor integration
42
+ - **pipeline**: add V2RuntimeBridge for settled descriptor path
43
+
44
+ ### Patch Changes
45
+
46
+ - [`5c8110b`](https://github.com/BeOnAuto/auto-engineer/commit/5c8110b45fcb7597e348ffc00386f69e1e4fcc13) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - **pipeline**: emit to event store before SSE broadcast in broadcastPipelineRunStarted
47
+
48
+ - [`3f7324f`](https://github.com/BeOnAuto/auto-engineer/commit/3f7324fa09b306b6ce67d6c337501e2f521f2d60) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - Updated the landing page header text in the React client
49
+
50
+ - [`a7a0616`](https://github.com/BeOnAuto/auto-engineer/commit/a7a06161e3bd923116c827a816b4df809e1807f5) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - Updated Emmett and SQLite driver dependencies in the Apollo server generator
51
+
52
+ - [`4c39d83`](https://github.com/BeOnAuto/auto-engineer/commit/4c39d8395445b7fe9c36c4943214cd216471366a) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - **pipeline**: plan for fix emit-before-broadcast race
53
+
54
+ - [`a83d744`](https://github.com/BeOnAuto/auto-engineer/commit/a83d74432c94c79fc7fee0beaa0e9bccc5721dd0) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - **pipeline**: add settled correlationId fix bursts to plan
55
+
56
+ - [`30579ce`](https://github.com/BeOnAuto/auto-engineer/commit/30579ce1382f0fe1856b478f25828c90eeecc698) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - **ci**: remove --reporter=append to show pnpm install errors
57
+
58
+ - [`d4c314e`](https://github.com/BeOnAuto/auto-engineer/commit/d4c314eb75e3de81d90b198422cb3538946cd08b) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - Updated Emmett and Emmett SQLite dependencies to version 0.42.0
59
+
60
+ - [`6e9516a`](https://github.com/BeOnAuto/auto-engineer/commit/6e9516a004d29eac3bdc8585f7b3cc70f2b624ef) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - Improved stability of parallel job dispatch timing in the job graph processor
61
+
62
+ - [`46bf3e7`](https://github.com/BeOnAuto/auto-engineer/commit/46bf3e7a2ffbce7f5a840689091ddaa061457ccc) Thanks [@SamHatoum](https://github.com/SamHatoum)! - - **pipeline**: use sessionCorrelationId for settled bridge keying
63
+
64
+ - Updated dependencies [[`5c8110b`](https://github.com/BeOnAuto/auto-engineer/commit/5c8110b45fcb7597e348ffc00386f69e1e4fcc13), [`3f7324f`](https://github.com/BeOnAuto/auto-engineer/commit/3f7324fa09b306b6ce67d6c337501e2f521f2d60), [`a7a0616`](https://github.com/BeOnAuto/auto-engineer/commit/a7a06161e3bd923116c827a816b4df809e1807f5), [`4c39d83`](https://github.com/BeOnAuto/auto-engineer/commit/4c39d8395445b7fe9c36c4943214cd216471366a), [`a83d744`](https://github.com/BeOnAuto/auto-engineer/commit/a83d74432c94c79fc7fee0beaa0e9bccc5721dd0), [`d5b9b83`](https://github.com/BeOnAuto/auto-engineer/commit/d5b9b83ae2a40f0f34a9e5823fd7f582ffb578a5), [`30579ce`](https://github.com/BeOnAuto/auto-engineer/commit/30579ce1382f0fe1856b478f25828c90eeecc698), [`d4c314e`](https://github.com/BeOnAuto/auto-engineer/commit/d4c314eb75e3de81d90b198422cb3538946cd08b), [`6e9516a`](https://github.com/BeOnAuto/auto-engineer/commit/6e9516a004d29eac3bdc8585f7b3cc70f2b624ef), [`46bf3e7`](https://github.com/BeOnAuto/auto-engineer/commit/46bf3e7a2ffbce7f5a840689091ddaa061457ccc)]:
65
+ - @auto-engineer/message-bus@1.70.0
66
+ - @auto-engineer/narrative@1.70.0
67
+
3
68
  ## 1.69.0
4
69
 
5
70
  ### 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
  });