@auto-engineer/pipeline 1.108.0 → 1.109.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 (34) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-test.log +5 -5
  3. package/.turbo/turbo-type-check.log +1 -1
  4. package/CHANGELOG.md +49 -0
  5. package/dist/src/builder/define.d.ts +5 -2
  6. package/dist/src/builder/define.d.ts.map +1 -1
  7. package/dist/src/builder/define.js +16 -2
  8. package/dist/src/builder/define.js.map +1 -1
  9. package/dist/src/core/descriptors.d.ts +3 -1
  10. package/dist/src/core/descriptors.d.ts.map +1 -1
  11. package/dist/src/server/pipeline-server-v2.d.ts +4 -41
  12. package/dist/src/server/pipeline-server-v2.d.ts.map +1 -1
  13. package/dist/src/server/pipeline-server-v2.js.map +1 -1
  14. package/dist/src/server/pipeline-server.d.ts +1 -0
  15. package/dist/src/server/pipeline-server.d.ts.map +1 -1
  16. package/dist/src/server/pipeline-server.js +20 -2
  17. package/dist/src/server/pipeline-server.js.map +1 -1
  18. package/dist/src/server/v2-runtime-bridge.d.ts +1 -0
  19. package/dist/src/server/v2-runtime-bridge.d.ts.map +1 -1
  20. package/dist/src/server/v2-runtime-bridge.js +31 -3
  21. package/dist/src/server/v2-runtime-bridge.js.map +1 -1
  22. package/dist/src/testing/fixtures/kanban-todo.config.js +1 -1
  23. package/dist/src/testing/fixtures/kanban-todo.config.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +3 -3
  26. package/src/builder/define.specs.ts +47 -0
  27. package/src/builder/define.ts +31 -1
  28. package/src/core/descriptors.ts +4 -0
  29. package/src/plugins/plugin-loader.specs.ts +5 -5
  30. package/src/server/pipeline-server-v2.ts +6 -1
  31. package/src/server/pipeline-server.ts +21 -2
  32. package/src/server/v2-runtime-bridge.specs.ts +104 -1
  33. package/src/server/v2-runtime-bridge.ts +67 -3
  34. package/src/testing/fixtures/kanban-todo.config.ts +1 -1
package/package.json CHANGED
@@ -14,8 +14,8 @@
14
14
  "get-port": "^7.1.0",
15
15
  "jose": "^5.9.6",
16
16
  "nanoid": "^5.0.0",
17
- "@auto-engineer/file-store": "1.108.0",
18
- "@auto-engineer/message-bus": "1.108.0"
17
+ "@auto-engineer/file-store": "1.109.0",
18
+ "@auto-engineer/message-bus": "1.109.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/cors": "^2.8.17",
@@ -24,7 +24,7 @@
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },
27
- "version": "1.108.0",
27
+ "version": "1.109.0",
28
28
  "scripts": {
29
29
  "build": "tsc && tsx ../../scripts/fix-esm-imports.ts",
30
30
  "test": "vitest run --reporter=dot",
@@ -110,6 +110,14 @@ describe('settled()', () => {
110
110
  expect(descriptor.handler).toBe(handler);
111
111
  });
112
112
 
113
+ it('should accept handler with emit parameter', () => {
114
+ const handler = vi.fn((_events, _send, _emit) => undefined);
115
+ const pipeline = define('test').settled(['CheckTests']).dispatch({ dispatches: [] }, handler).build();
116
+
117
+ const descriptor = pipeline.descriptor.handlers[0] as SettledHandlerDescriptor;
118
+ expect(descriptor.handler).toBe(handler);
119
+ });
120
+
113
121
  it('should chain multiple settled handlers', () => {
114
122
  const handler1 = vi.fn();
115
123
  const handler2 = vi.fn();
@@ -165,6 +173,28 @@ describe('settled()', () => {
165
173
  expect(descriptor.sourceEventTypes).toEqual(['SliceImplemented']);
166
174
  });
167
175
 
176
+ it('supports maxRetries option on SettledBuilder', () => {
177
+ const pipeline = define('test')
178
+ .settled(['CheckTests'])
179
+ .maxRetries(0)
180
+ .dispatch({ dispatches: [] }, () => {})
181
+ .build();
182
+
183
+ const descriptor = pipeline.descriptor.handlers[0] as SettledHandlerDescriptor;
184
+ expect(descriptor.maxRetries).toBe(0);
185
+ });
186
+
187
+ it('supports maxRetries option on SettledChain', () => {
188
+ const pipeline = define('test')
189
+ .settled(['CheckTests'])
190
+ .dispatch({ dispatches: [] }, () => {})
191
+ .maxRetries(5)
192
+ .build();
193
+
194
+ const descriptor = pipeline.descriptor.handlers[0] as SettledHandlerDescriptor;
195
+ expect(descriptor.maxRetries).toBe(5);
196
+ });
197
+
168
198
  it('has no sourceEventTypes for top-level settled', () => {
169
199
  const pipeline = define('test')
170
200
  .settled(['CheckTests'])
@@ -664,4 +694,21 @@ describe('declare().accepts()', () => {
664
694
  accepts: ['ImplementComponent'],
665
695
  });
666
696
  });
697
+
698
+ it('should chain settled() from HandleChain', () => {
699
+ const handler = vi.fn();
700
+ const pipeline = define('test')
701
+ .on('ComponentImplemented')
702
+ .handle(async () => {})
703
+ .settled(['CheckTests', 'CheckTypes'])
704
+ .dispatch({ dispatches: [] }, handler)
705
+ .build();
706
+
707
+ expect(pipeline.descriptor.handlers).toHaveLength(2);
708
+ expect(pipeline.descriptor.handlers[0].type).toBe('custom');
709
+ expect(pipeline.descriptor.handlers[1].type).toBe('settled');
710
+ const settled = pipeline.descriptor.handlers[1] as SettledHandlerDescriptor;
711
+ expect(settled.commandTypes).toEqual(['CheckTests', 'CheckTypes']);
712
+ expect(settled.sourceEventTypes).toEqual(['ComponentImplemented']);
713
+ });
667
714
  });
@@ -11,6 +11,7 @@ import type {
11
11
  KeyExtractor,
12
12
  PipelineDescriptor,
13
13
  RunAwaitHandlerDescriptor,
14
+ SettledEmitFunction,
14
15
  SettledHandler,
15
16
  SettledHandlerDescriptor,
16
17
  SuccessContext,
@@ -43,11 +44,13 @@ export interface DispatchOptions<D extends readonly string[] = readonly string[]
43
44
  }
44
45
 
45
46
  export interface SettledBuilder {
47
+ maxRetries(n: number): SettledBuilder;
46
48
  dispatch<const D extends readonly string[]>(
47
49
  options: DispatchOptions<D>,
48
50
  handler: (
49
51
  events: Record<string, Event[]>,
50
52
  send: (commandType: D[number], data: unknown) => void,
53
+ emit: SettledEmitFunction,
51
54
  ) => undefined | { persist: boolean },
52
55
  ): SettledChain;
53
56
  }
@@ -56,6 +59,7 @@ export interface SettledChain {
56
59
  declare(commandType: string): DeclareBuilder;
57
60
  on(eventType: string): TriggerBuilder;
58
61
  settled(commandTypes: readonly string[], label?: string): SettledBuilder;
62
+ maxRetries(n: number): SettledChain;
59
63
  build(): Pipeline;
60
64
  }
61
65
 
@@ -140,6 +144,7 @@ export interface EmitChain {
140
144
  export interface HandleChain {
141
145
  declare(commandType: string): DeclareBuilder;
142
146
  on(eventType: string): TriggerBuilder;
147
+ settled(commandTypes: readonly string[], label?: string): SettledBuilder;
143
148
  build(): Pipeline;
144
149
  }
145
150
 
@@ -450,6 +455,11 @@ class HandleChainImpl implements HandleChain {
450
455
  return new TriggerBuilderImpl(this.parent, eventType);
451
456
  }
452
457
 
458
+ settled(commandTypes: readonly string[], label?: string): SettledBuilder {
459
+ this.finalizeHandler();
460
+ return new SettledBuilderImpl(this.parent, commandTypes, this.eventType, label);
461
+ }
462
+
453
463
  build(): Pipeline {
454
464
  this.finalizeHandler();
455
465
  return this.parent.build();
@@ -710,6 +720,8 @@ class PhasedTerminalImpl implements PhasedTerminal {
710
720
  }
711
721
 
712
722
  class SettledBuilderImpl implements SettledBuilder {
723
+ private maxRetriesValue?: number;
724
+
713
725
  constructor(
714
726
  private readonly parent: PipelineBuilderImpl,
715
727
  private readonly commandTypes: readonly string[],
@@ -717,11 +729,17 @@ class SettledBuilderImpl implements SettledBuilder {
717
729
  private readonly customLabel?: string,
718
730
  ) {}
719
731
 
732
+ maxRetries(n: number): SettledBuilder {
733
+ this.maxRetriesValue = n;
734
+ return this;
735
+ }
736
+
720
737
  dispatch<const D extends readonly string[]>(
721
738
  options: DispatchOptions<D>,
722
739
  handler: (
723
740
  events: Record<string, Event[]>,
724
741
  send: (commandType: D[number], data: unknown) => void,
742
+ emit: SettledEmitFunction,
725
743
  ) => undefined | { persist: boolean },
726
744
  ): SettledChain {
727
745
  return new SettledChainImpl(
@@ -731,11 +749,14 @@ class SettledBuilderImpl implements SettledBuilder {
731
749
  options.dispatches,
732
750
  this.sourceEventType,
733
751
  this.customLabel,
752
+ this.maxRetriesValue,
734
753
  );
735
754
  }
736
755
  }
737
756
 
738
757
  class SettledChainImpl implements SettledChain {
758
+ private maxRetriesValue?: number;
759
+
739
760
  constructor(
740
761
  private readonly parent: PipelineBuilderImpl,
741
762
  private readonly commandTypes: readonly string[],
@@ -743,7 +764,10 @@ class SettledChainImpl implements SettledChain {
743
764
  private readonly dispatches?: readonly string[],
744
765
  private readonly sourceEventType?: string,
745
766
  private readonly customLabel?: string,
746
- ) {}
767
+ maxRetriesFromBuilder?: number,
768
+ ) {
769
+ this.maxRetriesValue = maxRetriesFromBuilder;
770
+ }
747
771
 
748
772
  declare(commandType: string): DeclareBuilder {
749
773
  this.finalizeHandler();
@@ -760,6 +784,11 @@ class SettledChainImpl implements SettledChain {
760
784
  return new SettledBuilderImpl(this.parent, commandTypes, undefined, label);
761
785
  }
762
786
 
787
+ maxRetries(n: number): SettledChain {
788
+ this.maxRetriesValue = n;
789
+ return this;
790
+ }
791
+
763
792
  build(): Pipeline {
764
793
  this.finalizeHandler();
765
794
  return this.parent.build();
@@ -776,6 +805,7 @@ class SettledChainImpl implements SettledChain {
776
805
  settledId: this.parent.nextSettledId(),
777
806
  label: settledLabel,
778
807
  sourceEventTypes: this.sourceEventType ? [this.sourceEventType] : undefined,
808
+ maxRetries: this.maxRetriesValue,
779
809
  };
780
810
  this.parent.addHandler(descriptor);
781
811
  }
@@ -75,9 +75,12 @@ export interface CustomHandlerDescriptor {
75
75
 
76
76
  type SettledSendFunction = (commandType: string, data: unknown) => void;
77
77
 
78
+ export type SettledEmitFunction = (eventType: string, data: unknown, correlationId?: string) => void;
79
+
78
80
  export type SettledHandler = (
79
81
  events: Record<string, Event[]>,
80
82
  send: SettledSendFunction,
83
+ emit: SettledEmitFunction,
81
84
  ) => undefined | { persist: boolean };
82
85
 
83
86
  export interface SettledHandlerDescriptor {
@@ -88,6 +91,7 @@ export interface SettledHandlerDescriptor {
88
91
  settledId?: string;
89
92
  label?: string;
90
93
  sourceEventTypes?: readonly string[];
94
+ maxRetries?: number;
91
95
  }
92
96
 
93
97
  export interface AcceptsDescriptor {
@@ -25,12 +25,12 @@ describe('PluginLoader', () => {
25
25
  it('should load COMMANDS from workspace package', async () => {
26
26
  const mockHandler = createMockHandler('CheckTests', ['TestsCheckPassed', 'TestsCheckFailed']);
27
27
  const deps = createMockDeps({
28
- existsSync: vi.fn().mockImplementation((path: string) => path.includes('packages/server-checks')),
28
+ existsSync: vi.fn().mockImplementation((path: string) => path.includes('packages/checks')),
29
29
  importModule: vi.fn().mockResolvedValue({ COMMANDS: [mockHandler] }),
30
30
  });
31
31
 
32
32
  const loader = new PluginLoader('/workspace', deps);
33
- const handlers = await loader.loadPlugin('@auto-engineer/server-checks');
33
+ const handlers = await loader.loadPlugin('@auto-engineer/checks');
34
34
 
35
35
  expect(handlers).toHaveLength(1);
36
36
  expect(handlers[0].name).toBe('CheckTests');
@@ -94,7 +94,7 @@ describe('PluginLoader', () => {
94
94
  });
95
95
 
96
96
  const loader = new PluginLoader('/workspace', deps);
97
- const handlers = await loader.loadPlugin('@auto-engineer/server-checks');
97
+ const handlers = await loader.loadPlugin('@auto-engineer/checks');
98
98
 
99
99
  expect(handlers[0]).toEqual({
100
100
  name: 'CheckTypes',
@@ -257,7 +257,7 @@ describe('PluginLoader', () => {
257
257
  describe('integration', { timeout: 30000 }, () => {
258
258
  it('should load real package using default deps', async () => {
259
259
  const loader = new PluginLoader();
260
- const handlers = await loader.loadPlugin('@auto-engineer/server-checks');
260
+ const handlers = await loader.loadPlugin('@auto-engineer/checks');
261
261
  const handlerNames = handlers.map((h) => h.name);
262
262
  expect(handlerNames).toContain('CheckTests');
263
263
  expect(handlerNames).toContain('CheckTypes');
@@ -267,7 +267,7 @@ describe('PluginLoader', () => {
267
267
  it('should load handlers from multiple real packages', async () => {
268
268
  const loader = new PluginLoader();
269
269
  const handlers = await loader.loadPlugins([
270
- '@auto-engineer/server-checks',
270
+ '@auto-engineer/checks',
271
271
  '@auto-engineer/server-generator-apollo-emmett',
272
272
  ]);
273
273
  const handlerNames = handlers.map((h) => h.name);
@@ -3,7 +3,12 @@ import cors from 'cors';
3
3
  import express from 'express';
4
4
  import { createPipelineEngine } from '../engine/pipeline-engine.js';
5
5
 
6
- export async function createPipelineServerV2(config?: { port?: number }) {
6
+ export async function createPipelineServerV2(config?: { port?: number }): Promise<{
7
+ engine: Awaited<ReturnType<typeof createPipelineEngine>>;
8
+ app: ReturnType<typeof express>;
9
+ start(): Promise<number>;
10
+ stop(): Promise<void>;
11
+ }> {
7
12
  const engine = await createPipelineEngine();
8
13
  const app = express();
9
14
  app.use(cors());
@@ -92,6 +92,9 @@ export class PipelineServer {
92
92
  onDispatch: (commandType, data, correlationId) => {
93
93
  void this.dispatchFromSettled(commandType, data, correlationId);
94
94
  },
95
+ onEmit: (eventType, data, correlationId) => {
96
+ void this.emitFromSettled(eventType, data, correlationId);
97
+ },
95
98
  });
96
99
  this.phasedBridge = createPhasedBridge({
97
100
  onDispatch: (commandType, data, correlationId) => {
@@ -1142,7 +1145,8 @@ export class PipelineServer {
1142
1145
  await this.updateNodeStatus(this.currentSessionId, command.type, 'running');
1143
1146
  const sourceEventType = this.requestIdToSourceEvent.get(command.requestId);
1144
1147
  this.requestIdToSourceEvent.delete(command.requestId);
1145
- this.settledBridge.onCommandStarted(command, this.currentSessionId, sourceEventType);
1148
+ const settledCorrelationId = this.currentSessionId;
1149
+ this.settledBridge.onCommandStarted(command, settledCorrelationId, sourceEventType);
1146
1150
 
1147
1151
  const ctx = this.createContext(command.correlationId, signal);
1148
1152
  let events: Event[];
@@ -1200,7 +1204,8 @@ export class PipelineServer {
1200
1204
  const sourceCommand = this.eventCommandMapper.getSourceCommand(eventWithIds.type);
1201
1205
  if (sourceCommand !== undefined) {
1202
1206
  const result = eventWithIds.type.includes('Failed') ? 'failure' : 'success';
1203
- this.settledBridge.onEventReceived(eventWithIds, sourceCommand, result, this.currentSessionId, sourceEventType);
1207
+ const settledCorrelationId = this.currentSessionId;
1208
+ this.settledBridge.onEventReceived(eventWithIds, sourceCommand, result, settledCorrelationId, sourceEventType);
1204
1209
  }
1205
1210
 
1206
1211
  this.routeEventToPhasedExecutor(eventWithIds);
@@ -1231,6 +1236,20 @@ export class PipelineServer {
1231
1236
  await this.processCommand(command);
1232
1237
  }
1233
1238
 
1239
+ /* v8 ignore next 10 - integration callback tested via v2-runtime-bridge.specs.ts */
1240
+ private async emitFromSettled(eventType: string, data: unknown, correlationId: string): Promise<void> {
1241
+ const requestId = `req-${nanoid()}`;
1242
+ const eventWithIds: EventWithCorrelation = {
1243
+ type: eventType,
1244
+ data: data as Record<string, unknown>,
1245
+ correlationId,
1246
+ };
1247
+ await this.emitDomainEventEmitted(correlationId, requestId, eventType, data as Record<string, unknown>);
1248
+ this.sseManager.broadcast(eventWithIds);
1249
+ await this.messageBus.publishEvent(eventWithIds);
1250
+ await this.routeEventToPipelines(eventWithIds);
1251
+ }
1252
+
1234
1253
  /* v8 ignore next 10 - integration callback tested via phased-bridge.specs.ts */
1235
1254
  private async handlePhasedComplete(event: Event, correlationId: string): Promise<void> {
1236
1255
  const requestId = `req-${nanoid()}`;
@@ -189,6 +189,56 @@ describe('V2RuntimeBridge', () => {
189
189
 
190
190
  expect(dispatched).toEqual([{ commandType: 'NextCommand', data: { payload: true }, correlationId: 'corr-1' }]);
191
191
  });
192
+
193
+ it('passes emit function that calls onEmit with correlationId', () => {
194
+ const emitted: Array<{ eventType: string; data: unknown; correlationId: string }> = [];
195
+ const bridge = createV2RuntimeBridge({
196
+ onDispatch: () => {},
197
+ onEmit: (eventType, data, correlationId) => {
198
+ emitted.push({ eventType, data, correlationId });
199
+ },
200
+ });
201
+
202
+ bridge.registerSettled({
203
+ type: 'settled',
204
+ commandTypes: ['A'],
205
+ handler: (_events, _send, emit) => {
206
+ emit('ChecksPassed', { component: 'Foo' }, 'graph:g1:job-a');
207
+ return undefined;
208
+ },
209
+ });
210
+
211
+ bridge.onCommandStarted(makeCommand('A', 'corr-1'));
212
+ bridge.onEventReceived(makeEvent('ADone', 'corr-1'), 'A');
213
+
214
+ expect(emitted).toEqual([
215
+ { eventType: 'ChecksPassed', data: { component: 'Foo' }, correlationId: 'graph:g1:job-a' },
216
+ ]);
217
+ });
218
+
219
+ it('emit uses settled correlationId when no explicit correlationId provided', () => {
220
+ const emitted: Array<{ eventType: string; data: unknown; correlationId: string }> = [];
221
+ const bridge = createV2RuntimeBridge({
222
+ onDispatch: () => {},
223
+ onEmit: (eventType, data, correlationId) => {
224
+ emitted.push({ eventType, data, correlationId });
225
+ },
226
+ });
227
+
228
+ bridge.registerSettled({
229
+ type: 'settled',
230
+ commandTypes: ['A'],
231
+ handler: (_events, _send, emit) => {
232
+ emit('ChecksPassed', { component: 'Bar' });
233
+ return undefined;
234
+ },
235
+ });
236
+
237
+ bridge.onCommandStarted(makeCommand('A', 'corr-1'));
238
+ bridge.onEventReceived(makeEvent('ADone', 'corr-1'), 'A');
239
+
240
+ expect(emitted).toEqual([{ eventType: 'ChecksPassed', data: { component: 'Bar' }, correlationId: 'corr-1' }]);
241
+ });
192
242
  });
193
243
 
194
244
  describe('RetryCommands', () => {
@@ -215,6 +265,59 @@ describe('V2RuntimeBridge', () => {
215
265
  });
216
266
  });
217
267
 
268
+ describe('SettledFailed', () => {
269
+ it('invokes handler on SettledFailed after max retries exhausted', () => {
270
+ const handlerCalls: Record<string, Event[]>[] = [];
271
+ const bridge = createV2RuntimeBridge({ onDispatch: () => {} });
272
+
273
+ bridge.registerSettled(
274
+ {
275
+ type: 'settled',
276
+ commandTypes: ['A'],
277
+ handler: (events) => {
278
+ handlerCalls.push(events);
279
+ return undefined;
280
+ },
281
+ },
282
+ { maxRetries: 0 },
283
+ );
284
+
285
+ bridge.onCommandStarted(makeCommand('A', 'corr-1'));
286
+ bridge.onEventReceived(makeEvent('AFailed', 'corr-1', { error: 'oops' }), 'A', 'failure');
287
+
288
+ expect(handlerCalls).toHaveLength(1);
289
+ expect(handlerCalls[0]).toEqual({
290
+ A: [{ type: 'AFailed', data: { error: 'oops' }, correlationId: 'corr-1' }],
291
+ });
292
+ });
293
+
294
+ it('passes send function to handler on SettledFailed', () => {
295
+ const dispatched: Array<{ commandType: string; data: unknown; correlationId: string }> = [];
296
+ const bridge = createV2RuntimeBridge({
297
+ onDispatch: (commandType, data, correlationId) => {
298
+ dispatched.push({ commandType, data, correlationId });
299
+ },
300
+ });
301
+
302
+ bridge.registerSettled(
303
+ {
304
+ type: 'settled',
305
+ commandTypes: ['A'],
306
+ handler: (_events, send) => {
307
+ send('RetryCommand', { retry: true });
308
+ return undefined;
309
+ },
310
+ },
311
+ { maxRetries: 0 },
312
+ );
313
+
314
+ bridge.onCommandStarted(makeCommand('A', 'corr-1'));
315
+ bridge.onEventReceived(makeEvent('AFailed', 'corr-1'), 'A', 'failure');
316
+
317
+ expect(dispatched).toEqual([{ commandType: 'RetryCommand', data: { retry: true }, correlationId: 'corr-1' }]);
318
+ });
319
+ });
320
+
218
321
  describe('persist: true resets instance', () => {
219
322
  it('allows receiving new commands after reset', () => {
220
323
  const bridge = createV2RuntimeBridge({ onDispatch: () => {} });
@@ -334,7 +437,7 @@ describe('V2RuntimeBridge', () => {
334
437
  bridge.onEventReceived(makeEvent('AFailed', 'corr-1'), 'A', 'failure');
335
438
 
336
439
  const stats = bridge.getSettledStats('corr-1', 'template-A');
337
- expect(stats).toEqual({ status: 'error', pendingCount: 0, endedCount: 0 });
440
+ expect(stats).toEqual({ status: 'error', pendingCount: 0, endedCount: 1 });
338
441
  });
339
442
 
340
443
  it('returns idle for unknown templateId', () => {
@@ -1,8 +1,11 @@
1
1
  import type { Command, Event } from '@auto-engineer/message-bus';
2
+ import createDebug from 'debug';
2
3
  import type { SettledHandlerDescriptor } from '../core/descriptors.js';
3
4
  import type { SettledInput, SettledOutput, SettledState } from '../engine/workflows/settled-workflow.js';
4
5
  import { decide, evolve, initialState } from '../engine/workflows/settled-workflow.js';
5
6
 
7
+ const debug = createDebug('auto:pipeline:settled-bridge');
8
+
6
9
  type NodeStatus = 'idle' | 'running' | 'success' | 'error';
7
10
 
8
11
  export interface SettledStats {
@@ -13,6 +16,7 @@ export interface SettledStats {
13
16
 
14
17
  interface V2RuntimeBridgeOptions {
15
18
  onDispatch: (commandType: string, data: unknown, correlationId: string) => void;
19
+ onEmit?: (eventType: string, data: unknown, correlationId: string) => void;
16
20
  }
17
21
 
18
22
  interface RegisteredSettled {
@@ -106,15 +110,33 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
106
110
  }
107
111
 
108
112
  function handleOutputs(outputs: SettledOutput[], registration: RegisteredSettled, correlationId: string): void {
113
+ debug(
114
+ 'handleOutputs: templateId=%s, correlationId=%s, outputs=%o',
115
+ registration.templateId,
116
+ correlationId,
117
+ outputs.map((o) => o.type),
118
+ );
109
119
  for (const output of outputs) {
110
- if (output.type === 'AllSettled') {
120
+ if (output.type === 'AllSettled' || output.type === 'SettledFailed') {
121
+ debug(' %s triggered for %s', output.type, registration.templateId);
111
122
  const events = collectBufferedEvents(registration.templateId, correlationId, registration.commandTypes);
123
+ debug(
124
+ ' collected events: %o',
125
+ Object.keys(events).map((k) => `${k}:${events[k].length}`),
126
+ );
112
127
 
113
128
  const send = (commandType: string, data: unknown) => {
129
+ debug(' dispatching %s with data keys: %o', commandType, Object.keys(data as object));
114
130
  options.onDispatch(commandType, data, correlationId);
115
131
  };
116
132
 
117
- const handlerResult = registration.descriptor.handler(events, send);
133
+ const emit = (eventType: string, data: unknown, emitCorrelationId?: string) => {
134
+ debug(' emitting %s', eventType);
135
+ options.onEmit?.(eventType, data, emitCorrelationId ?? correlationId);
136
+ };
137
+
138
+ const handlerResult = registration.descriptor.handler(events, send, emit);
139
+ debug(' handler returned: %o', handlerResult);
118
140
  const persist =
119
141
  handlerResult !== null &&
120
142
  handlerResult !== undefined &&
@@ -132,6 +154,7 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
132
154
  }
133
155
 
134
156
  if (output.type === 'RetryCommands') {
157
+ debug(' RetryCommands: %o', output.data.commandTypes);
135
158
  for (const ct of output.data.commandTypes) {
136
159
  options.onDispatch(ct, {}, correlationId);
137
160
  eventBuffer.delete(bufferKey(registration.templateId, correlationId, ct));
@@ -151,10 +174,18 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
151
174
  templateId,
152
175
  descriptor,
153
176
  commandTypes,
154
- maxRetries: config?.maxRetries ?? 3,
177
+ maxRetries: descriptor.maxRetries ?? config?.maxRetries ?? 3,
155
178
  sourceEventTypes: descriptor.sourceEventTypes,
156
179
  };
157
180
 
181
+ debug(
182
+ 'registerSettled: templateId=%s, commandTypes=%o, sourceEventTypes=%o, label=%s',
183
+ templateId,
184
+ commandTypes,
185
+ descriptor.sourceEventTypes,
186
+ descriptor.label,
187
+ );
188
+
158
189
  registrations.set(templateId, registration);
159
190
 
160
191
  for (const ct of commandTypes) {
@@ -167,7 +198,15 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
167
198
  onCommandStarted(command: Command, sessionCorrelationId?: string, sourceEventType?: string): void {
168
199
  const { type: commandType, correlationId, requestId } = command;
169
200
 
201
+ debug(
202
+ 'onCommandStarted: command=%s, sourceEventType=%s, sessionCorrelationId=%s',
203
+ commandType,
204
+ sourceEventType,
205
+ sessionCorrelationId,
206
+ );
207
+
170
208
  if (!isValidId(correlationId) || !isValidId(requestId)) {
209
+ debug(' skipping: invalid correlationId or requestId');
171
210
  return;
172
211
  }
173
212
 
@@ -175,20 +214,26 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
175
214
 
176
215
  const templateIds = commandToTemplateIds.get(commandType);
177
216
  if (!templateIds) {
217
+ debug(' skipping: no templateIds for command type');
178
218
  return;
179
219
  }
180
220
 
221
+ debug(' found %d templateIds: %o', templateIds.size, [...templateIds]);
222
+
181
223
  for (const templateId of templateIds) {
182
224
  const registration = registrations.get(templateId);
183
225
  if (registration) {
226
+ debug(' checking registration %s, sourceEventTypes=%o', templateId, registration.sourceEventTypes);
184
227
  if (
185
228
  sourceEventType &&
186
229
  registration.sourceEventTypes &&
187
230
  registration.sourceEventTypes.length > 0 &&
188
231
  !registration.sourceEventTypes.includes(sourceEventType)
189
232
  ) {
233
+ debug(' filtered out: sourceEventType %s not in %o', sourceEventType, registration.sourceEventTypes);
190
234
  continue;
191
235
  }
236
+ debug(' processing StartSettled for %s', templateId);
192
237
  const history = getHistory(templateId, keyCorrelationId);
193
238
  const input: SettledInput = {
194
239
  type: 'StartSettled',
@@ -208,7 +253,16 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
208
253
  ): void {
209
254
  const correlationId = event.correlationId;
210
255
 
256
+ debug(
257
+ 'onEventReceived: event=%s, sourceCommand=%s, result=%s, sourceEventType=%s',
258
+ event.type,
259
+ sourceCommandType,
260
+ result,
261
+ sourceEventType,
262
+ );
263
+
211
264
  if (!isValidId(correlationId)) {
265
+ debug(' skipping: invalid correlationId');
212
266
  return;
213
267
  }
214
268
 
@@ -216,23 +270,29 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
216
270
 
217
271
  const templateIds = commandToTemplateIds.get(sourceCommandType);
218
272
  if (!templateIds) {
273
+ debug(' skipping: no templateIds for sourceCommand');
219
274
  return;
220
275
  }
221
276
 
277
+ debug(' found %d templateIds: %o', templateIds.size, [...templateIds]);
278
+
222
279
  for (const templateId of templateIds) {
223
280
  const registration = registrations.get(templateId)!;
281
+ debug(' checking registration %s, sourceEventTypes=%o', templateId, registration.sourceEventTypes);
224
282
  if (
225
283
  sourceEventType &&
226
284
  registration.sourceEventTypes &&
227
285
  registration.sourceEventTypes.length > 0 &&
228
286
  !registration.sourceEventTypes.includes(sourceEventType)
229
287
  ) {
288
+ debug(' filtered out: sourceEventType %s not in %o', sourceEventType, registration.sourceEventTypes);
230
289
  continue;
231
290
  }
232
291
  const existing = ensureBuffer(templateId, keyCorrelationId, sourceCommandType);
233
292
  existing.push(event);
234
293
 
235
294
  const history = getHistory(templateId, keyCorrelationId);
295
+ debug(' processing CommandCompleted for %s, history length=%d', templateId, history.length);
236
296
  const input: SettledInput = {
237
297
  type: 'CommandCompleted',
238
298
  data: {
@@ -243,6 +303,10 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
243
303
  };
244
304
  const outputs = processInput(input, history, registration.maxRetries);
245
305
 
306
+ debug(
307
+ ' outputs: %o',
308
+ outputs.map((o) => o.type),
309
+ );
246
310
  handleOutputs(outputs, registration, keyCorrelationId);
247
311
  }
248
312
  },
@@ -3,7 +3,7 @@ import { createKanbanFullPipeline } from './kanban-full.pipeline';
3
3
 
4
4
  export default pipelineConfig({
5
5
  plugins: [
6
- '@auto-engineer/server-checks',
6
+ '@auto-engineer/checks',
7
7
  '@auto-engineer/server-generator-apollo-emmett',
8
8
  '@auto-engineer/narrative',
9
9
  '@auto-engineer/generate-react-client',