@auto-engineer/pipeline 1.105.0 → 1.107.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +5 -5
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +56 -0
- package/dist/src/builder/define.d.ts +3 -3
- package/dist/src/builder/define.d.ts.map +1 -1
- package/dist/src/builder/define.js +25 -11
- package/dist/src/builder/define.js.map +1 -1
- package/dist/src/core/descriptors.d.ts +3 -0
- package/dist/src/core/descriptors.d.ts.map +1 -1
- package/dist/src/server/pipeline-server.d.ts +2 -0
- package/dist/src/server/pipeline-server.d.ts.map +1 -1
- package/dist/src/server/pipeline-server.js +19 -10
- package/dist/src/server/pipeline-server.js.map +1 -1
- package/dist/src/server/v2-runtime-bridge.d.ts +2 -2
- package/dist/src/server/v2-runtime-bridge.d.ts.map +1 -1
- package/dist/src/server/v2-runtime-bridge.js +18 -3
- package/dist/src/server/v2-runtime-bridge.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +9 -0
- package/package.json +3 -3
- package/src/builder/define.specs.ts +58 -0
- package/src/builder/define.ts +34 -12
- package/src/core/descriptors.ts +3 -0
- package/src/server/pipeline-server.specs.ts +7 -7
- package/src/server/pipeline-server.ts +20 -10
- package/src/server/v2-runtime-bridge.specs.ts +116 -0
- package/src/server/v2-runtime-bridge.ts +23 -2
package/ketchup-plan.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## TODO
|
|
4
4
|
|
|
5
|
+
### Fix Settled Node Labels and Event Routing (Bursts 107-112)
|
|
6
|
+
|
|
7
|
+
- [x] Burst 107: processSettledHandler uses descriptor.label for graph node [depends: none]
|
|
8
|
+
- [x] Burst 108: bridge filters commands by sourceEventTypes [depends: none]
|
|
9
|
+
- [x] Burst 109: builder sets sourceEventTypes from emit chain event type [depends: none]
|
|
10
|
+
- [x] Burst 110: Add label and sourceEventTypes to descriptor and builder (merged into 107+109)
|
|
11
|
+
- [x] Burst 111: Add source event filtering to bridge (merged into 108)
|
|
12
|
+
- [x] Burst 112: Thread source event type through pipeline server [depends: 110, 111]
|
|
13
|
+
|
|
5
14
|
### Graph Rendering Fix (Burst 106)
|
|
6
15
|
|
|
7
16
|
- [ ] Burst 106: Show source commands whose events are listened to by the pipeline [depends: none]
|
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.
|
|
18
|
-
"@auto-engineer/message-bus": "1.
|
|
17
|
+
"@auto-engineer/file-store": "1.107.0",
|
|
18
|
+
"@auto-engineer/message-bus": "1.107.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.
|
|
27
|
+
"version": "1.107.0",
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc && tsx ../../scripts/fix-esm-imports.ts",
|
|
30
30
|
"test": "vitest run --reporter=dot",
|
|
@@ -153,6 +153,64 @@ describe('settled()', () => {
|
|
|
153
153
|
expect(settledNode?.label).toBe('Settled');
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
+
it('sets sourceEventTypes from preceding emit chain event type', () => {
|
|
157
|
+
const pipeline = define('test')
|
|
158
|
+
.on('SliceImplemented')
|
|
159
|
+
.emit('CheckTests', {})
|
|
160
|
+
.settled(['CheckTests', 'CheckTypes'])
|
|
161
|
+
.dispatch({ dispatches: ['ImplementSlice'] }, () => {})
|
|
162
|
+
.build();
|
|
163
|
+
|
|
164
|
+
const descriptor = pipeline.descriptor.handlers[1] as SettledHandlerDescriptor;
|
|
165
|
+
expect(descriptor.sourceEventTypes).toEqual(['SliceImplemented']);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('has no sourceEventTypes for top-level settled', () => {
|
|
169
|
+
const pipeline = define('test')
|
|
170
|
+
.settled(['CheckTests'])
|
|
171
|
+
.dispatch({ dispatches: [] }, () => {})
|
|
172
|
+
.build();
|
|
173
|
+
|
|
174
|
+
const descriptor = pipeline.descriptor.handlers[0] as SettledHandlerDescriptor;
|
|
175
|
+
expect(descriptor.sourceEventTypes).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('uses custom label passed to settled()', () => {
|
|
179
|
+
const pipeline = define('test')
|
|
180
|
+
.settled(['CheckTests', 'CheckTypes'], 'Slice Checks')
|
|
181
|
+
.dispatch({ dispatches: ['ImplementSlice'] }, () => {})
|
|
182
|
+
.build();
|
|
183
|
+
|
|
184
|
+
const descriptor = pipeline.descriptor.handlers[0] as SettledHandlerDescriptor;
|
|
185
|
+
expect(descriptor.label).toBe('Slice Checks');
|
|
186
|
+
|
|
187
|
+
const graph = pipeline.toGraph();
|
|
188
|
+
const settledNode = graph.nodes.find((n) => n.id.startsWith('settled:'));
|
|
189
|
+
expect(settledNode).toEqual({
|
|
190
|
+
id: 'settled:settled-0',
|
|
191
|
+
type: 'settled',
|
|
192
|
+
label: 'Slice Checks',
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('uses label from descriptor for settled graph nodes', () => {
|
|
197
|
+
const pipeline = define('test')
|
|
198
|
+
.settled(['CheckTests', 'CheckTypes'])
|
|
199
|
+
.dispatch({ dispatches: ['ImplementSlice'] }, () => {})
|
|
200
|
+
.build();
|
|
201
|
+
|
|
202
|
+
const handlers = pipeline.descriptor.handlers as SettledHandlerDescriptor[];
|
|
203
|
+
expect(handlers[0].label).toBe('ImplementSlice Settled');
|
|
204
|
+
|
|
205
|
+
const graph = pipeline.toGraph();
|
|
206
|
+
const settledNode = graph.nodes.find((n) => n.id.startsWith('settled:'));
|
|
207
|
+
expect(settledNode).toEqual({
|
|
208
|
+
id: 'settled:settled-0',
|
|
209
|
+
type: 'settled',
|
|
210
|
+
label: 'ImplementSlice Settled',
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
156
214
|
it('should accept options-first dispatch with dispatches array', () => {
|
|
157
215
|
const pipeline = define('test')
|
|
158
216
|
.settled(['CheckA'])
|
package/src/builder/define.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface PipelineBuilder {
|
|
|
34
34
|
key<E>(name: string, extractor: (event: E) => string): PipelineBuilder;
|
|
35
35
|
declare(commandType: string): DeclareBuilder;
|
|
36
36
|
on(eventType: string): TriggerBuilder;
|
|
37
|
-
settled(commandTypes: readonly string[]): SettledBuilder;
|
|
37
|
+
settled(commandTypes: readonly string[], label?: string): SettledBuilder;
|
|
38
38
|
build(): Pipeline;
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -55,7 +55,7 @@ export interface SettledBuilder {
|
|
|
55
55
|
export interface SettledChain {
|
|
56
56
|
declare(commandType: string): DeclareBuilder;
|
|
57
57
|
on(eventType: string): TriggerBuilder;
|
|
58
|
-
settled(commandTypes: readonly string[]): SettledBuilder;
|
|
58
|
+
settled(commandTypes: readonly string[], label?: string): SettledBuilder;
|
|
59
59
|
build(): Pipeline;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -133,7 +133,7 @@ export interface EmitChain {
|
|
|
133
133
|
emit(commandType: string, data: unknown): EmitChain;
|
|
134
134
|
declare(commandType: string): DeclareBuilder;
|
|
135
135
|
on(eventType: string): TriggerBuilder;
|
|
136
|
-
settled(commandTypes: readonly string[]): SettledBuilder;
|
|
136
|
+
settled(commandTypes: readonly string[], label?: string): SettledBuilder;
|
|
137
137
|
build(): Pipeline;
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -156,11 +156,16 @@ class PipelineBuilderImpl implements PipelineBuilder {
|
|
|
156
156
|
private descriptionValue?: string;
|
|
157
157
|
private readonly keys: Map<string, KeyExtractor> = new Map();
|
|
158
158
|
private readonly handlers: HandlerDescriptor[] = [];
|
|
159
|
+
private settledCounter = 0;
|
|
159
160
|
|
|
160
161
|
constructor(name: string) {
|
|
161
162
|
this.name = name;
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
nextSettledId(): string {
|
|
166
|
+
return `settled-${this.settledCounter++}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
164
169
|
version(v: string): PipelineBuilder {
|
|
165
170
|
this.versionValue = v;
|
|
166
171
|
return this;
|
|
@@ -184,8 +189,8 @@ class PipelineBuilderImpl implements PipelineBuilder {
|
|
|
184
189
|
return new TriggerBuilderImpl(this, eventType);
|
|
185
190
|
}
|
|
186
191
|
|
|
187
|
-
settled(commandTypes: readonly string[]): SettledBuilder {
|
|
188
|
-
return new SettledBuilderImpl(this, commandTypes);
|
|
192
|
+
settled(commandTypes: readonly string[], label?: string): SettledBuilder {
|
|
193
|
+
return new SettledBuilderImpl(this, commandTypes, undefined, label);
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
addHandler(handler: HandlerDescriptor): void {
|
|
@@ -288,8 +293,9 @@ function processAcceptsHandler(ctx: GraphBuilderContext, handler: AcceptsDescrip
|
|
|
288
293
|
}
|
|
289
294
|
|
|
290
295
|
function processSettledHandler(ctx: GraphBuilderContext, handler: SettledHandlerDescriptor): void {
|
|
291
|
-
const
|
|
292
|
-
|
|
296
|
+
const settledKey = handler.settledId ?? handler.commandTypes.join(',');
|
|
297
|
+
const settledNodeId = `settled:${settledKey}`;
|
|
298
|
+
addNode(ctx, settledNodeId, 'settled', handler.label ?? 'Settled');
|
|
293
299
|
|
|
294
300
|
for (const commandType of handler.commandTypes) {
|
|
295
301
|
addNode(ctx, `cmd:${commandType}`, 'command', commandType);
|
|
@@ -402,9 +408,9 @@ class EmitChainImpl implements EmitChain {
|
|
|
402
408
|
return new TriggerBuilderImpl(this.parent, eventType);
|
|
403
409
|
}
|
|
404
410
|
|
|
405
|
-
settled(commandTypes: readonly string[]): SettledBuilder {
|
|
411
|
+
settled(commandTypes: readonly string[], label?: string): SettledBuilder {
|
|
406
412
|
this.finalizeHandler();
|
|
407
|
-
return new SettledBuilderImpl(this.parent, commandTypes);
|
|
413
|
+
return new SettledBuilderImpl(this.parent, commandTypes, this.eventType, label);
|
|
408
414
|
}
|
|
409
415
|
|
|
410
416
|
build(): Pipeline {
|
|
@@ -707,6 +713,8 @@ class SettledBuilderImpl implements SettledBuilder {
|
|
|
707
713
|
constructor(
|
|
708
714
|
private readonly parent: PipelineBuilderImpl,
|
|
709
715
|
private readonly commandTypes: readonly string[],
|
|
716
|
+
private readonly sourceEventType?: string,
|
|
717
|
+
private readonly customLabel?: string,
|
|
710
718
|
) {}
|
|
711
719
|
|
|
712
720
|
dispatch<const D extends readonly string[]>(
|
|
@@ -716,7 +724,14 @@ class SettledBuilderImpl implements SettledBuilder {
|
|
|
716
724
|
send: (commandType: D[number], data: unknown) => void,
|
|
717
725
|
) => undefined | { persist: boolean },
|
|
718
726
|
): SettledChain {
|
|
719
|
-
return new SettledChainImpl(
|
|
727
|
+
return new SettledChainImpl(
|
|
728
|
+
this.parent,
|
|
729
|
+
this.commandTypes,
|
|
730
|
+
handler as SettledHandler,
|
|
731
|
+
options.dispatches,
|
|
732
|
+
this.sourceEventType,
|
|
733
|
+
this.customLabel,
|
|
734
|
+
);
|
|
720
735
|
}
|
|
721
736
|
}
|
|
722
737
|
|
|
@@ -726,6 +741,8 @@ class SettledChainImpl implements SettledChain {
|
|
|
726
741
|
private readonly commandTypes: readonly string[],
|
|
727
742
|
private readonly handler: SettledHandler,
|
|
728
743
|
private readonly dispatches?: readonly string[],
|
|
744
|
+
private readonly sourceEventType?: string,
|
|
745
|
+
private readonly customLabel?: string,
|
|
729
746
|
) {}
|
|
730
747
|
|
|
731
748
|
declare(commandType: string): DeclareBuilder {
|
|
@@ -738,9 +755,9 @@ class SettledChainImpl implements SettledChain {
|
|
|
738
755
|
return new TriggerBuilderImpl(this.parent, eventType);
|
|
739
756
|
}
|
|
740
757
|
|
|
741
|
-
settled(commandTypes: readonly string[]): SettledBuilder {
|
|
758
|
+
settled(commandTypes: readonly string[], label?: string): SettledBuilder {
|
|
742
759
|
this.finalizeHandler();
|
|
743
|
-
return new SettledBuilderImpl(this.parent, commandTypes);
|
|
760
|
+
return new SettledBuilderImpl(this.parent, commandTypes, undefined, label);
|
|
744
761
|
}
|
|
745
762
|
|
|
746
763
|
build(): Pipeline {
|
|
@@ -749,11 +766,16 @@ class SettledChainImpl implements SettledChain {
|
|
|
749
766
|
}
|
|
750
767
|
|
|
751
768
|
private finalizeHandler(): void {
|
|
769
|
+
const autoLabel = this.dispatches && this.dispatches.length > 0 ? `${this.dispatches[0]} Settled` : undefined;
|
|
770
|
+
const settledLabel = this.customLabel ?? autoLabel;
|
|
752
771
|
const descriptor: SettledHandlerDescriptor = {
|
|
753
772
|
type: 'settled',
|
|
754
773
|
commandTypes: this.commandTypes,
|
|
755
774
|
handler: this.handler,
|
|
756
775
|
dispatches: this.dispatches,
|
|
776
|
+
settledId: this.parent.nextSettledId(),
|
|
777
|
+
label: settledLabel,
|
|
778
|
+
sourceEventTypes: this.sourceEventType ? [this.sourceEventType] : undefined,
|
|
757
779
|
};
|
|
758
780
|
this.parent.addHandler(descriptor);
|
|
759
781
|
}
|
package/src/core/descriptors.ts
CHANGED
|
@@ -85,6 +85,9 @@ export interface SettledHandlerDescriptor {
|
|
|
85
85
|
commandTypes: readonly string[];
|
|
86
86
|
handler: SettledHandler;
|
|
87
87
|
dispatches?: readonly string[];
|
|
88
|
+
settledId?: string;
|
|
89
|
+
label?: string;
|
|
90
|
+
sourceEventTypes?: readonly string[];
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
export interface AcceptsDescriptor {
|
|
@@ -460,7 +460,7 @@ describe('PipelineServer', () => {
|
|
|
460
460
|
server.registerPipeline(pipeline);
|
|
461
461
|
await server.start();
|
|
462
462
|
const data = await fetchAs<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
|
|
463
|
-
const settledNode = data.nodes.find((n) => n.id === 'settled:
|
|
463
|
+
const settledNode = data.nodes.find((n) => n.id === 'settled:settled-0');
|
|
464
464
|
expect(settledNode?.status).toBe('idle');
|
|
465
465
|
expect(settledNode?.pendingCount).toBe(0);
|
|
466
466
|
expect(settledNode?.endedCount).toBe(0);
|
|
@@ -493,7 +493,7 @@ describe('PipelineServer', () => {
|
|
|
493
493
|
await new Promise((r) => setTimeout(r, 100));
|
|
494
494
|
|
|
495
495
|
const data = await fetchAs<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
|
|
496
|
-
const settledNode = data.nodes.find((n) => n.id === 'settled:
|
|
496
|
+
const settledNode = data.nodes.find((n) => n.id === 'settled:settled-0');
|
|
497
497
|
expect(settledNode?.status).toBeDefined();
|
|
498
498
|
expect(settledNode?.pendingCount).toBeDefined();
|
|
499
499
|
expect(settledNode?.endedCount).toBeDefined();
|
|
@@ -526,9 +526,9 @@ describe('PipelineServer', () => {
|
|
|
526
526
|
await new Promise((r) => setTimeout(r, 100));
|
|
527
527
|
|
|
528
528
|
const data = await fetchAs<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
|
|
529
|
-
const settledNode = data.nodes.find((n) => n.id === 'settled:
|
|
529
|
+
const settledNode = data.nodes.find((n) => n.id === 'settled:settled-0');
|
|
530
530
|
expect(settledNode).toEqual({
|
|
531
|
-
id: 'settled:
|
|
531
|
+
id: 'settled:settled-0',
|
|
532
532
|
type: 'settled',
|
|
533
533
|
label: 'Settled',
|
|
534
534
|
status: 'success',
|
|
@@ -1407,8 +1407,8 @@ describe('PipelineServer', () => {
|
|
|
1407
1407
|
await server.start();
|
|
1408
1408
|
const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
|
|
1409
1409
|
const mermaid = await res.text();
|
|
1410
|
-
expect(mermaid).toContain('CheckA -->
|
|
1411
|
-
expect(mermaid).toContain('CheckB -->
|
|
1410
|
+
expect(mermaid).toContain('CheckA --> settled_settled-0');
|
|
1411
|
+
expect(mermaid).toContain('CheckB --> settled_settled-0');
|
|
1412
1412
|
await server.stop();
|
|
1413
1413
|
});
|
|
1414
1414
|
|
|
@@ -1435,7 +1435,7 @@ describe('PipelineServer', () => {
|
|
|
1435
1435
|
await server.start();
|
|
1436
1436
|
const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
|
|
1437
1437
|
const mermaid = await res.text();
|
|
1438
|
-
expect(mermaid).toContain('
|
|
1438
|
+
expect(mermaid).toContain('settled_settled-0 -.->|retry| RetryCommand');
|
|
1439
1439
|
await server.stop();
|
|
1440
1440
|
});
|
|
1441
1441
|
|
|
@@ -75,6 +75,7 @@ export class PipelineServer {
|
|
|
75
75
|
private sqliteEventStore?: EventStore;
|
|
76
76
|
private currentSessionId = '';
|
|
77
77
|
private readonly quiescenceTracker: QuiescenceTracker;
|
|
78
|
+
private readonly requestIdToSourceEvent = new Map<string, string>();
|
|
78
79
|
|
|
79
80
|
constructor(config: PipelineServerConfig) {
|
|
80
81
|
this.storeFileName = config.storeFileName;
|
|
@@ -993,6 +994,12 @@ export class PipelineServer {
|
|
|
993
994
|
return false;
|
|
994
995
|
}
|
|
995
996
|
|
|
997
|
+
private settledCommandTypes(graph: GraphIR, settledNodeId: string): string[] {
|
|
998
|
+
return graph.edges
|
|
999
|
+
.filter((e) => e.to === settledNodeId && e.from.startsWith('cmd:'))
|
|
1000
|
+
.map((e) => e.from.replace('cmd:', ''));
|
|
1001
|
+
}
|
|
1002
|
+
|
|
996
1003
|
private extractPipelineEvents(graph: GraphIR, commandToEvents: Record<string, string[]>): Set<string> {
|
|
997
1004
|
const pipelineEvents = new Set<string>();
|
|
998
1005
|
|
|
@@ -1001,8 +1008,7 @@ export class PipelineServer {
|
|
|
1001
1008
|
pipelineEvents.add(node.id.replace('evt:', ''));
|
|
1002
1009
|
}
|
|
1003
1010
|
if (node.id.startsWith('settled:')) {
|
|
1004
|
-
const
|
|
1005
|
-
for (const commandType of commandTypes) {
|
|
1011
|
+
for (const commandType of this.settledCommandTypes(graph, node.id)) {
|
|
1006
1012
|
const events = commandToEvents[commandType];
|
|
1007
1013
|
if (events !== undefined) {
|
|
1008
1014
|
for (const eventName of events) {
|
|
@@ -1034,8 +1040,8 @@ export class PipelineServer {
|
|
|
1034
1040
|
commandNodes.add(commandName);
|
|
1035
1041
|
lines.push(` ${commandName}[${node.label}]`);
|
|
1036
1042
|
} else if (node.id.startsWith('settled:')) {
|
|
1037
|
-
const commandTypes =
|
|
1038
|
-
const safeId = `settled_${
|
|
1043
|
+
const commandTypes = this.settledCommandTypes(graph, node.id);
|
|
1044
|
+
const safeId = `settled_${node.id.replace('settled:', '')}`;
|
|
1039
1045
|
settledNodes.add(safeId);
|
|
1040
1046
|
lines.push(` ${safeId}{{${commandTypes.join(', ')}}}`);
|
|
1041
1047
|
}
|
|
@@ -1067,8 +1073,7 @@ export class PipelineServer {
|
|
|
1067
1073
|
if (nodeId.startsWith('cmd:')) {
|
|
1068
1074
|
return nodeId.replace('cmd:', '');
|
|
1069
1075
|
}
|
|
1070
|
-
|
|
1071
|
-
return `settled_${commandTypes.join('_')}`;
|
|
1076
|
+
return `settled_${nodeId.replace('settled:', '')}`;
|
|
1072
1077
|
}
|
|
1073
1078
|
|
|
1074
1079
|
private addMermaidStyles(
|
|
@@ -1135,7 +1140,9 @@ export class PipelineServer {
|
|
|
1135
1140
|
await this.getOrCreateItemStatus(this.currentSessionId, command.type, itemKey, command.requestId);
|
|
1136
1141
|
|
|
1137
1142
|
await this.updateNodeStatus(this.currentSessionId, command.type, 'running');
|
|
1138
|
-
this.
|
|
1143
|
+
const sourceEventType = this.requestIdToSourceEvent.get(command.requestId);
|
|
1144
|
+
this.requestIdToSourceEvent.delete(command.requestId);
|
|
1145
|
+
this.settledBridge.onCommandStarted(command, this.currentSessionId, sourceEventType);
|
|
1139
1146
|
|
|
1140
1147
|
const ctx = this.createContext(command.correlationId, signal);
|
|
1141
1148
|
let events: Event[];
|
|
@@ -1193,7 +1200,7 @@ export class PipelineServer {
|
|
|
1193
1200
|
const sourceCommand = this.eventCommandMapper.getSourceCommand(eventWithIds.type);
|
|
1194
1201
|
if (sourceCommand !== undefined) {
|
|
1195
1202
|
const result = eventWithIds.type.includes('Failed') ? 'failure' : 'success';
|
|
1196
|
-
this.settledBridge.onEventReceived(eventWithIds, sourceCommand, result, this.currentSessionId);
|
|
1203
|
+
this.settledBridge.onEventReceived(eventWithIds, sourceCommand, result, this.currentSessionId, sourceEventType);
|
|
1197
1204
|
}
|
|
1198
1205
|
|
|
1199
1206
|
this.routeEventToPhasedExecutor(eventWithIds);
|
|
@@ -1237,12 +1244,12 @@ export class PipelineServer {
|
|
|
1237
1244
|
}
|
|
1238
1245
|
|
|
1239
1246
|
private async routeEventToPipelines(event: EventWithCorrelation): Promise<void> {
|
|
1240
|
-
const ctx = this.createContext(event.correlationId);
|
|
1247
|
+
const ctx = this.createContext(event.correlationId, undefined, event.type);
|
|
1241
1248
|
const runtimes = Array.from(this.runtimes.values());
|
|
1242
1249
|
await Promise.all(runtimes.map((runtime) => runtime.handleEvent(event, ctx)));
|
|
1243
1250
|
}
|
|
1244
1251
|
|
|
1245
|
-
private createContext(correlationId: string, signal?: AbortSignal): PipelineContext {
|
|
1252
|
+
private createContext(correlationId: string, signal?: AbortSignal, sourceEventType?: string): PipelineContext {
|
|
1246
1253
|
return {
|
|
1247
1254
|
correlationId,
|
|
1248
1255
|
signal,
|
|
@@ -1266,6 +1273,9 @@ export class PipelineServer {
|
|
|
1266
1273
|
correlationId: effectiveCorrelationId,
|
|
1267
1274
|
requestId,
|
|
1268
1275
|
};
|
|
1276
|
+
if (sourceEventType) {
|
|
1277
|
+
this.requestIdToSourceEvent.set(requestId, sourceEventType);
|
|
1278
|
+
}
|
|
1269
1279
|
await this.emitCommandDispatched(effectiveCorrelationId, requestId, type, data as Record<string, unknown>);
|
|
1270
1280
|
void this.processCommand(command);
|
|
1271
1281
|
},
|
|
@@ -344,4 +344,120 @@ describe('V2RuntimeBridge', () => {
|
|
|
344
344
|
expect(stats).toEqual({ status: 'idle', pendingCount: 0, endedCount: 0 });
|
|
345
345
|
});
|
|
346
346
|
});
|
|
347
|
+
|
|
348
|
+
describe('sourceEventTypes filtering', () => {
|
|
349
|
+
it('only routes commands to settled blocks matching source event type', () => {
|
|
350
|
+
const bridge = createV2RuntimeBridge({ onDispatch: () => {} });
|
|
351
|
+
const handler1Calls: Record<string, Event[]>[] = [];
|
|
352
|
+
const handler2Calls: Record<string, Event[]>[] = [];
|
|
353
|
+
|
|
354
|
+
bridge.registerSettled({
|
|
355
|
+
type: 'settled',
|
|
356
|
+
commandTypes: ['A', 'B'],
|
|
357
|
+
settledId: 'settled-0',
|
|
358
|
+
sourceEventTypes: ['Foo'],
|
|
359
|
+
handler: (events) => {
|
|
360
|
+
handler1Calls.push(events);
|
|
361
|
+
return undefined;
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
bridge.registerSettled({
|
|
366
|
+
type: 'settled',
|
|
367
|
+
commandTypes: ['A', 'B'],
|
|
368
|
+
settledId: 'settled-1',
|
|
369
|
+
sourceEventTypes: ['Bar'],
|
|
370
|
+
handler: (events) => {
|
|
371
|
+
handler2Calls.push(events);
|
|
372
|
+
return undefined;
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
bridge.onCommandStarted(makeCommand('A', 'corr-1'), undefined, 'Foo');
|
|
377
|
+
bridge.onCommandStarted(makeCommand('B', 'corr-1'), undefined, 'Foo');
|
|
378
|
+
bridge.onEventReceived(makeEvent('ADone', 'corr-1', { a: 1 }), 'A', 'success', undefined, 'Foo');
|
|
379
|
+
bridge.onEventReceived(makeEvent('BDone', 'corr-1', { b: 2 }), 'B', 'success', undefined, 'Foo');
|
|
380
|
+
|
|
381
|
+
expect(handler1Calls).toHaveLength(1);
|
|
382
|
+
expect(handler2Calls).toHaveLength(0);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('routes to all blocks when sourceEventType is not provided', () => {
|
|
386
|
+
const bridge = createV2RuntimeBridge({ onDispatch: () => {} });
|
|
387
|
+
const handler1Calls: Record<string, Event[]>[] = [];
|
|
388
|
+
const handler2Calls: Record<string, Event[]>[] = [];
|
|
389
|
+
|
|
390
|
+
bridge.registerSettled({
|
|
391
|
+
type: 'settled',
|
|
392
|
+
commandTypes: ['A'],
|
|
393
|
+
settledId: 'settled-0',
|
|
394
|
+
sourceEventTypes: ['Foo'],
|
|
395
|
+
handler: (events) => {
|
|
396
|
+
handler1Calls.push(events);
|
|
397
|
+
return undefined;
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
bridge.registerSettled({
|
|
402
|
+
type: 'settled',
|
|
403
|
+
commandTypes: ['A'],
|
|
404
|
+
settledId: 'settled-1',
|
|
405
|
+
sourceEventTypes: ['Bar'],
|
|
406
|
+
handler: (events) => {
|
|
407
|
+
handler2Calls.push(events);
|
|
408
|
+
return undefined;
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
bridge.onCommandStarted(makeCommand('A', 'corr-1'));
|
|
413
|
+
bridge.onEventReceived(makeEvent('ADone', 'corr-1'), 'A');
|
|
414
|
+
|
|
415
|
+
expect(handler1Calls).toHaveLength(1);
|
|
416
|
+
expect(handler2Calls).toHaveLength(1);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
describe('multiple settled blocks with same command types', () => {
|
|
421
|
+
it('fires both handlers independently when two blocks watch the same commands', () => {
|
|
422
|
+
const bridge = createV2RuntimeBridge({ onDispatch: () => {} });
|
|
423
|
+
const handler1Calls: Record<string, Event[]>[] = [];
|
|
424
|
+
const handler2Calls: Record<string, Event[]>[] = [];
|
|
425
|
+
|
|
426
|
+
bridge.registerSettled({
|
|
427
|
+
type: 'settled',
|
|
428
|
+
commandTypes: ['A', 'B'],
|
|
429
|
+
settledId: 'settled-0',
|
|
430
|
+
handler: (events) => {
|
|
431
|
+
handler1Calls.push(events);
|
|
432
|
+
return undefined;
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
bridge.registerSettled({
|
|
437
|
+
type: 'settled',
|
|
438
|
+
commandTypes: ['A', 'B'],
|
|
439
|
+
settledId: 'settled-1',
|
|
440
|
+
handler: (events) => {
|
|
441
|
+
handler2Calls.push(events);
|
|
442
|
+
return undefined;
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
bridge.onCommandStarted(makeCommand('A', 'corr-1'));
|
|
447
|
+
bridge.onCommandStarted(makeCommand('B', 'corr-1'));
|
|
448
|
+
bridge.onEventReceived(makeEvent('ADone', 'corr-1', { a: 1 }), 'A');
|
|
449
|
+
bridge.onEventReceived(makeEvent('BDone', 'corr-1', { b: 2 }), 'B');
|
|
450
|
+
|
|
451
|
+
expect(handler1Calls).toHaveLength(1);
|
|
452
|
+
expect(handler2Calls).toHaveLength(1);
|
|
453
|
+
expect(handler1Calls[0]).toEqual({
|
|
454
|
+
A: [{ type: 'ADone', data: { a: 1 }, correlationId: 'corr-1' }],
|
|
455
|
+
B: [{ type: 'BDone', data: { b: 2 }, correlationId: 'corr-1' }],
|
|
456
|
+
});
|
|
457
|
+
expect(handler2Calls[0]).toEqual({
|
|
458
|
+
A: [{ type: 'ADone', data: { a: 1 }, correlationId: 'corr-1' }],
|
|
459
|
+
B: [{ type: 'BDone', data: { b: 2 }, correlationId: 'corr-1' }],
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
});
|
|
347
463
|
});
|
|
@@ -20,6 +20,7 @@ interface RegisteredSettled {
|
|
|
20
20
|
descriptor: SettledHandlerDescriptor;
|
|
21
21
|
commandTypes: readonly string[];
|
|
22
22
|
maxRetries: number;
|
|
23
|
+
sourceEventTypes?: readonly string[];
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
type SettledEvent = SettledInput | SettledOutput;
|
|
@@ -142,13 +143,16 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
|
|
|
142
143
|
return {
|
|
143
144
|
registerSettled(descriptor: SettledHandlerDescriptor, config?: { maxRetries?: number }): void {
|
|
144
145
|
const commandTypes = descriptor.commandTypes;
|
|
145
|
-
const templateId =
|
|
146
|
+
const templateId = descriptor.settledId
|
|
147
|
+
? `template-${descriptor.settledId}`
|
|
148
|
+
: `template-${commandTypes.join(',')}`;
|
|
146
149
|
|
|
147
150
|
const registration: RegisteredSettled = {
|
|
148
151
|
templateId,
|
|
149
152
|
descriptor,
|
|
150
153
|
commandTypes,
|
|
151
154
|
maxRetries: config?.maxRetries ?? 3,
|
|
155
|
+
sourceEventTypes: descriptor.sourceEventTypes,
|
|
152
156
|
};
|
|
153
157
|
|
|
154
158
|
registrations.set(templateId, registration);
|
|
@@ -160,7 +164,7 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
|
|
|
160
164
|
}
|
|
161
165
|
},
|
|
162
166
|
|
|
163
|
-
onCommandStarted(command: Command, sessionCorrelationId?: string): void {
|
|
167
|
+
onCommandStarted(command: Command, sessionCorrelationId?: string, sourceEventType?: string): void {
|
|
164
168
|
const { type: commandType, correlationId, requestId } = command;
|
|
165
169
|
|
|
166
170
|
if (!isValidId(correlationId) || !isValidId(requestId)) {
|
|
@@ -177,6 +181,14 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
|
|
|
177
181
|
for (const templateId of templateIds) {
|
|
178
182
|
const registration = registrations.get(templateId);
|
|
179
183
|
if (registration) {
|
|
184
|
+
if (
|
|
185
|
+
sourceEventType &&
|
|
186
|
+
registration.sourceEventTypes &&
|
|
187
|
+
registration.sourceEventTypes.length > 0 &&
|
|
188
|
+
!registration.sourceEventTypes.includes(sourceEventType)
|
|
189
|
+
) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
180
192
|
const history = getHistory(templateId, keyCorrelationId);
|
|
181
193
|
const input: SettledInput = {
|
|
182
194
|
type: 'StartSettled',
|
|
@@ -192,6 +204,7 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
|
|
|
192
204
|
sourceCommandType: string,
|
|
193
205
|
result: 'success' | 'failure' = 'success',
|
|
194
206
|
sessionCorrelationId?: string,
|
|
207
|
+
sourceEventType?: string,
|
|
195
208
|
): void {
|
|
196
209
|
const correlationId = event.correlationId;
|
|
197
210
|
|
|
@@ -208,6 +221,14 @@ export function createV2RuntimeBridge(options: V2RuntimeBridgeOptions) {
|
|
|
208
221
|
|
|
209
222
|
for (const templateId of templateIds) {
|
|
210
223
|
const registration = registrations.get(templateId)!;
|
|
224
|
+
if (
|
|
225
|
+
sourceEventType &&
|
|
226
|
+
registration.sourceEventTypes &&
|
|
227
|
+
registration.sourceEventTypes.length > 0 &&
|
|
228
|
+
!registration.sourceEventTypes.includes(sourceEventType)
|
|
229
|
+
) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
211
232
|
const existing = ensureBuffer(templateId, keyCorrelationId, sourceCommandType);
|
|
212
233
|
existing.push(event);
|
|
213
234
|
|