@auto-engineer/pipeline 0.15.0 → 0.17.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/CHANGELOG.md +24 -0
- package/dist/src/runtime/phased-executor.d.ts +0 -1
- package/dist/src/runtime/phased-executor.d.ts.map +1 -1
- package/dist/src/runtime/phased-executor.js +4 -18
- package/dist/src/runtime/phased-executor.js.map +1 -1
- package/dist/src/runtime/settled-tracker.d.ts +0 -1
- package/dist/src/runtime/settled-tracker.d.ts.map +1 -1
- package/dist/src/runtime/settled-tracker.js +0 -6
- package/dist/src/runtime/settled-tracker.js.map +1 -1
- package/dist/src/server/pipeline-server.d.ts.map +1 -1
- package/dist/src/server/pipeline-server.js +26 -10
- package/dist/src/server/pipeline-server.js.map +1 -1
- package/dist/src/server/sse-manager.d.ts +0 -1
- package/dist/src/server/sse-manager.d.ts.map +1 -1
- package/dist/src/server/sse-manager.js +0 -3
- package/dist/src/server/sse-manager.js.map +1 -1
- package/dist/src/store/pipeline-read-model.d.ts.map +1 -1
- package/dist/src/store/pipeline-read-model.js +3 -4
- package/dist/src/store/pipeline-read-model.js.map +1 -1
- package/dist/src/store/sqlite-pipeline-event-store.d.ts +14 -0
- package/dist/src/store/sqlite-pipeline-event-store.d.ts.map +1 -0
- package/dist/src/store/sqlite-pipeline-event-store.js +20 -0
- package/dist/src/store/sqlite-pipeline-event-store.js.map +1 -0
- package/dist/src/testing/fixtures/kanban.pipeline.d.ts.map +1 -1
- package/dist/src/testing/fixtures/kanban.pipeline.js +7 -1
- package/dist/src/testing/fixtures/kanban.pipeline.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +256 -0
- package/package.json +4 -3
- package/src/builder/define.specs.ts +2 -1
- package/src/config/pipeline-config.specs.ts +32 -0
- package/src/graph/filter-graph.specs.ts +27 -1
- package/src/graph/types.specs.ts +0 -14
- package/src/projections/await-tracker-projection.specs.ts +24 -0
- package/src/projections/settled-instance-projection.specs.ts +47 -0
- package/src/runtime/phased-executor.ts +5 -15
- package/src/runtime/pipeline-runtime.specs.ts +23 -0
- package/src/runtime/settled-tracker.ts +1 -9
- package/src/server/pipeline-server.specs.ts +315 -0
- package/src/server/pipeline-server.ts +36 -13
- package/src/server/sse-manager.ts +0 -4
- package/src/store/pipeline-event-store.specs.ts +48 -0
- package/src/store/pipeline-read-model.specs.ts +203 -0
- package/src/store/pipeline-read-model.ts +3 -3
- package/src/store/sqlite-pipeline-event-store.specs.ts +13 -0
- package/src/store/sqlite-pipeline-event-store.ts +36 -0
- package/src/testing/fixtures/kanban.pipeline.ts +9 -2
- package/tsconfig.json +1 -1
- package/vitest.config.ts +1 -8
- package/dist/src/builder/define.specs.d.ts +0 -2
- package/dist/src/builder/define.specs.d.ts.map +0 -1
- package/dist/src/builder/define.specs.js +0 -435
- package/dist/src/builder/define.specs.js.map +0 -1
- package/dist/src/core/descriptors.specs.d.ts +0 -2
- package/dist/src/core/descriptors.specs.d.ts.map +0 -1
- package/dist/src/core/descriptors.specs.js +0 -24
- package/dist/src/core/descriptors.specs.js.map +0 -1
- package/dist/src/core/types.specs.d.ts +0 -2
- package/dist/src/core/types.specs.d.ts.map +0 -1
- package/dist/src/core/types.specs.js +0 -40
- package/dist/src/core/types.specs.js.map +0 -1
- package/dist/src/graph/filter-graph.specs.d.ts +0 -2
- package/dist/src/graph/filter-graph.specs.d.ts.map +0 -1
- package/dist/src/graph/filter-graph.specs.js +0 -204
- package/dist/src/graph/filter-graph.specs.js.map +0 -1
- package/dist/src/graph/types.specs.d.ts +0 -2
- package/dist/src/graph/types.specs.d.ts.map +0 -1
- package/dist/src/graph/types.specs.js +0 -148
- package/dist/src/graph/types.specs.js.map +0 -1
- package/dist/src/logging/event-logger.specs.d.ts +0 -2
- package/dist/src/logging/event-logger.specs.d.ts.map +0 -1
- package/dist/src/logging/event-logger.specs.js +0 -81
- package/dist/src/logging/event-logger.specs.js.map +0 -1
- package/dist/src/plugins/handler-adapter.specs.d.ts +0 -2
- package/dist/src/plugins/handler-adapter.specs.d.ts.map +0 -1
- package/dist/src/plugins/handler-adapter.specs.js +0 -129
- package/dist/src/plugins/handler-adapter.specs.js.map +0 -1
- package/dist/src/plugins/plugin-loader.specs.d.ts +0 -2
- package/dist/src/plugins/plugin-loader.specs.d.ts.map +0 -1
- package/dist/src/plugins/plugin-loader.specs.js +0 -246
- package/dist/src/plugins/plugin-loader.specs.js.map +0 -1
- package/dist/src/projections/item-status-projection.specs.d.ts +0 -2
- package/dist/src/projections/item-status-projection.specs.d.ts.map +0 -1
- package/dist/src/projections/item-status-projection.specs.js +0 -119
- package/dist/src/projections/item-status-projection.specs.js.map +0 -1
- package/dist/src/projections/latest-run-projection.specs.d.ts +0 -2
- package/dist/src/projections/latest-run-projection.specs.d.ts.map +0 -1
- package/dist/src/projections/latest-run-projection.specs.js +0 -33
- package/dist/src/projections/latest-run-projection.specs.js.map +0 -1
- package/dist/src/projections/message-log-projection.specs.d.ts +0 -2
- package/dist/src/projections/message-log-projection.specs.d.ts.map +0 -1
- package/dist/src/projections/message-log-projection.specs.js +0 -101
- package/dist/src/projections/message-log-projection.specs.js.map +0 -1
- package/dist/src/projections/node-status-projection.specs.d.ts +0 -2
- package/dist/src/projections/node-status-projection.specs.d.ts.map +0 -1
- package/dist/src/projections/node-status-projection.specs.js +0 -116
- package/dist/src/projections/node-status-projection.specs.js.map +0 -1
- package/dist/src/projections/phased-execution-projection.specs.d.ts +0 -2
- package/dist/src/projections/phased-execution-projection.specs.d.ts.map +0 -1
- package/dist/src/projections/phased-execution-projection.specs.js +0 -171
- package/dist/src/projections/phased-execution-projection.specs.js.map +0 -1
- package/dist/src/projections/settled-instance-projection.specs.d.ts +0 -2
- package/dist/src/projections/settled-instance-projection.specs.d.ts.map +0 -1
- package/dist/src/projections/settled-instance-projection.specs.js +0 -217
- package/dist/src/projections/settled-instance-projection.specs.js.map +0 -1
- package/dist/src/projections/stats-projection.specs.d.ts +0 -2
- package/dist/src/projections/stats-projection.specs.d.ts.map +0 -1
- package/dist/src/projections/stats-projection.specs.js +0 -91
- package/dist/src/projections/stats-projection.specs.js.map +0 -1
- package/dist/src/runtime/await-tracker.specs.d.ts +0 -2
- package/dist/src/runtime/await-tracker.specs.d.ts.map +0 -1
- package/dist/src/runtime/await-tracker.specs.js +0 -64
- package/dist/src/runtime/await-tracker.specs.js.map +0 -1
- package/dist/src/runtime/context.specs.d.ts +0 -2
- package/dist/src/runtime/context.specs.d.ts.map +0 -1
- package/dist/src/runtime/context.specs.js +0 -26
- package/dist/src/runtime/context.specs.js.map +0 -1
- package/dist/src/runtime/event-command-map.specs.d.ts +0 -2
- package/dist/src/runtime/event-command-map.specs.d.ts.map +0 -1
- package/dist/src/runtime/event-command-map.specs.js +0 -108
- package/dist/src/runtime/event-command-map.specs.js.map +0 -1
- package/dist/src/runtime/phased-executor.specs.d.ts +0 -2
- package/dist/src/runtime/phased-executor.specs.d.ts.map +0 -1
- package/dist/src/runtime/phased-executor.specs.js +0 -418
- package/dist/src/runtime/phased-executor.specs.js.map +0 -1
- package/dist/src/runtime/pipeline-runtime.specs.d.ts +0 -2
- package/dist/src/runtime/pipeline-runtime.specs.d.ts.map +0 -1
- package/dist/src/runtime/pipeline-runtime.specs.js +0 -227
- package/dist/src/runtime/pipeline-runtime.specs.js.map +0 -1
- package/dist/src/runtime/settled-tracker.specs.d.ts +0 -2
- package/dist/src/runtime/settled-tracker.specs.d.ts.map +0 -1
- package/dist/src/runtime/settled-tracker.specs.js +0 -811
- package/dist/src/runtime/settled-tracker.specs.js.map +0 -1
- package/dist/src/server/full-orchestration.e2e.specs.d.ts +0 -2
- package/dist/src/server/full-orchestration.e2e.specs.d.ts.map +0 -1
- package/dist/src/server/full-orchestration.e2e.specs.js +0 -561
- package/dist/src/server/full-orchestration.e2e.specs.js.map +0 -1
- package/dist/src/server/pipeline-server.e2e.specs.d.ts +0 -2
- package/dist/src/server/pipeline-server.e2e.specs.d.ts.map +0 -1
- package/dist/src/server/pipeline-server.e2e.specs.js +0 -373
- package/dist/src/server/pipeline-server.e2e.specs.js.map +0 -1
- package/dist/src/server/pipeline-server.specs.d.ts +0 -2
- package/dist/src/server/pipeline-server.specs.d.ts.map +0 -1
- package/dist/src/server/pipeline-server.specs.js +0 -1407
- package/dist/src/server/pipeline-server.specs.js.map +0 -1
- package/dist/src/server/sse-manager.specs.d.ts +0 -2
- package/dist/src/server/sse-manager.specs.d.ts.map +0 -1
- package/dist/src/server/sse-manager.specs.js +0 -178
- package/dist/src/server/sse-manager.specs.js.map +0 -1
- package/dist/src/store/pipeline-event-store.specs.d.ts +0 -2
- package/dist/src/store/pipeline-event-store.specs.d.ts.map +0 -1
- package/dist/src/store/pipeline-event-store.specs.js +0 -287
- package/dist/src/store/pipeline-event-store.specs.js.map +0 -1
- package/dist/src/store/pipeline-read-model.specs.d.ts +0 -2
- package/dist/src/store/pipeline-read-model.specs.d.ts.map +0 -1
- package/dist/src/store/pipeline-read-model.specs.js +0 -830
- package/dist/src/store/pipeline-read-model.specs.js.map +0 -1
- package/dist/src/testing/event-capture.specs.d.ts +0 -2
- package/dist/src/testing/event-capture.specs.d.ts.map +0 -1
- package/dist/src/testing/event-capture.specs.js +0 -114
- package/dist/src/testing/event-capture.specs.js.map +0 -1
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts +0 -2
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts.map +0 -1
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js +0 -263
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js.map +0 -1
- package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts +0 -2
- package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts.map +0 -1
- package/dist/src/testing/fixtures/kanban.pipeline.specs.js +0 -29
- package/dist/src/testing/fixtures/kanban.pipeline.specs.js.map +0 -1
- package/dist/src/testing/kanban-todo.e2e.specs.d.ts +0 -2
- package/dist/src/testing/kanban-todo.e2e.specs.d.ts.map +0 -1
- package/dist/src/testing/kanban-todo.e2e.specs.js +0 -160
- package/dist/src/testing/kanban-todo.e2e.specs.js.map +0 -1
- package/dist/src/testing/mock-handlers.specs.d.ts +0 -2
- package/dist/src/testing/mock-handlers.specs.d.ts.map +0 -1
- package/dist/src/testing/mock-handlers.specs.js +0 -193
- package/dist/src/testing/mock-handlers.specs.js.map +0 -1
- package/dist/src/testing/real-execution.e2e.specs.d.ts +0 -2
- package/dist/src/testing/real-execution.e2e.specs.d.ts.map +0 -1
- package/dist/src/testing/real-execution.e2e.specs.js +0 -140
- package/dist/src/testing/real-execution.e2e.specs.js.map +0 -1
- package/dist/src/testing/real-plugin.e2e.specs.d.ts +0 -2
- package/dist/src/testing/real-plugin.e2e.specs.d.ts.map +0 -1
- package/dist/src/testing/real-plugin.e2e.specs.js +0 -65
- package/dist/src/testing/real-plugin.e2e.specs.js.map +0 -1
- package/dist/src/testing/server-startup.e2e.specs.d.ts +0 -2
- package/dist/src/testing/server-startup.e2e.specs.d.ts.map +0 -1
- package/dist/src/testing/server-startup.e2e.specs.js +0 -104
- package/dist/src/testing/server-startup.e2e.specs.js.map +0 -1
- package/dist/src/testing/snapshot-compare.specs.d.ts +0 -2
- package/dist/src/testing/snapshot-compare.specs.d.ts.map +0 -1
- package/dist/src/testing/snapshot-compare.specs.js +0 -112
- package/dist/src/testing/snapshot-compare.specs.js.map +0 -1
- package/dist/src/testing/snapshot-sanitize.specs.d.ts +0 -2
- package/dist/src/testing/snapshot-sanitize.specs.d.ts.map +0 -1
- package/dist/src/testing/snapshot-sanitize.specs.js +0 -104
- package/dist/src/testing/snapshot-sanitize.specs.js.map +0 -1
- package/src/core/descriptors.specs.ts +0 -28
- package/src/core/types.specs.ts +0 -44
- package/src/projections/latest-run-projection.specs.ts +0 -38
- package/src/projections/message-log-projection.specs.ts +0 -118
- package/src/projections/node-status-projection.specs.ts +0 -127
- package/src/projections/stats-projection.specs.ts +0 -105
- package/src/runtime/context.specs.ts +0 -28
|
@@ -157,6 +157,28 @@ describe('PipelineServer', () => {
|
|
|
157
157
|
expect(data.folds).toEqual([]);
|
|
158
158
|
await server.stop();
|
|
159
159
|
});
|
|
160
|
+
|
|
161
|
+
it('should exclude settled handlers from eventHandlers list', async () => {
|
|
162
|
+
const handler = {
|
|
163
|
+
name: 'CheckTests',
|
|
164
|
+
events: ['TestsPassed'],
|
|
165
|
+
handle: async () => ({ type: 'TestsPassed', data: {} }),
|
|
166
|
+
};
|
|
167
|
+
const pipeline = define('test')
|
|
168
|
+
.on('Start')
|
|
169
|
+
.emit('CheckTests', {})
|
|
170
|
+
.settled(['CheckTests'])
|
|
171
|
+
.dispatch({ dispatches: [] }, () => {})
|
|
172
|
+
.build();
|
|
173
|
+
const server = new PipelineServer({ port: 0 });
|
|
174
|
+
server.registerCommandHandlers([handler]);
|
|
175
|
+
server.registerPipeline(pipeline);
|
|
176
|
+
await server.start();
|
|
177
|
+
const data = await fetchAs<RegistryResponse>(`http://localhost:${server.port}/registry`);
|
|
178
|
+
expect(data.eventHandlers).toContain('Start');
|
|
179
|
+
expect(data.eventHandlers).not.toContain('settled:CheckTests');
|
|
180
|
+
await server.stop();
|
|
181
|
+
});
|
|
160
182
|
});
|
|
161
183
|
|
|
162
184
|
describe('GET /pipeline', () => {
|
|
@@ -319,6 +341,41 @@ describe('PipelineServer', () => {
|
|
|
319
341
|
await server.stop();
|
|
320
342
|
});
|
|
321
343
|
|
|
344
|
+
it('should have status from computeSettledStats on settled nodes when correlationId provided', async () => {
|
|
345
|
+
const handler = {
|
|
346
|
+
name: 'CheckTests',
|
|
347
|
+
events: ['TestsPassed'],
|
|
348
|
+
handle: async () => ({ type: 'TestsPassed', data: {} }),
|
|
349
|
+
};
|
|
350
|
+
const pipeline = define('test')
|
|
351
|
+
.on('Start')
|
|
352
|
+
.emit('CheckTests', {})
|
|
353
|
+
.settled(['CheckTests'])
|
|
354
|
+
.dispatch({ dispatches: [] }, () => {})
|
|
355
|
+
.build();
|
|
356
|
+
const server = new PipelineServer({ port: 0 });
|
|
357
|
+
server.registerCommandHandlers([handler]);
|
|
358
|
+
server.registerPipeline(pipeline);
|
|
359
|
+
await server.start();
|
|
360
|
+
|
|
361
|
+
const commandResponse = await fetchAs<{ correlationId: string }>(`http://localhost:${server.port}/command`, {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
headers: { 'Content-Type': 'application/json' },
|
|
364
|
+
body: JSON.stringify({ type: 'CheckTests', data: {} }),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
368
|
+
|
|
369
|
+
const data = await fetchAs<PipelineResponse>(
|
|
370
|
+
`http://localhost:${server.port}/pipeline?correlationId=${commandResponse.correlationId}`,
|
|
371
|
+
);
|
|
372
|
+
const settledNode = data.nodes.find((n) => n.id === 'settled:CheckTests');
|
|
373
|
+
expect(settledNode?.status).toBeDefined();
|
|
374
|
+
expect(settledNode?.pendingCount).toBeDefined();
|
|
375
|
+
expect(settledNode?.endedCount).toBeDefined();
|
|
376
|
+
await server.stop();
|
|
377
|
+
});
|
|
378
|
+
|
|
322
379
|
it('should show running status for command being executed', async () => {
|
|
323
380
|
let resolveHandler: () => void = () => {};
|
|
324
381
|
const handlerPromise = new Promise<void>((resolve) => {
|
|
@@ -1196,6 +1253,52 @@ describe('PipelineServer', () => {
|
|
|
1196
1253
|
await server.stop();
|
|
1197
1254
|
});
|
|
1198
1255
|
|
|
1256
|
+
it('should handle diamond graph patterns when detecting backlinks', async () => {
|
|
1257
|
+
const cmdAHandler = {
|
|
1258
|
+
name: 'CmdA',
|
|
1259
|
+
events: ['EventA'],
|
|
1260
|
+
handle: async () => ({ type: 'EventA', data: {} }),
|
|
1261
|
+
};
|
|
1262
|
+
const cmdBHandler = {
|
|
1263
|
+
name: 'CmdB',
|
|
1264
|
+
events: ['EventB'],
|
|
1265
|
+
handle: async () => ({ type: 'EventB', data: {} }),
|
|
1266
|
+
};
|
|
1267
|
+
const cmdCHandler = {
|
|
1268
|
+
name: 'CmdC',
|
|
1269
|
+
events: ['EventC'],
|
|
1270
|
+
handle: async () => ({ type: 'EventC', data: {} }),
|
|
1271
|
+
};
|
|
1272
|
+
const cmdDHandler = {
|
|
1273
|
+
name: 'CmdD',
|
|
1274
|
+
events: ['EventD'],
|
|
1275
|
+
handle: async () => ({ type: 'EventD', data: {} }),
|
|
1276
|
+
};
|
|
1277
|
+
const pipeline = define('test')
|
|
1278
|
+
.on('Start')
|
|
1279
|
+
.emit('CmdA', {})
|
|
1280
|
+
.on('EventA')
|
|
1281
|
+
.emit('CmdB', {})
|
|
1282
|
+
.on('EventA')
|
|
1283
|
+
.emit('CmdC', {})
|
|
1284
|
+
.on('EventB')
|
|
1285
|
+
.emit('CmdD', {})
|
|
1286
|
+
.on('EventC')
|
|
1287
|
+
.emit('CmdD', {})
|
|
1288
|
+
.on('EventD')
|
|
1289
|
+
.emit('CmdA', {})
|
|
1290
|
+
.build();
|
|
1291
|
+
const server = new PipelineServer({ port: 0 });
|
|
1292
|
+
server.registerCommandHandlers([cmdAHandler, cmdBHandler, cmdCHandler, cmdDHandler]);
|
|
1293
|
+
server.registerPipeline(pipeline);
|
|
1294
|
+
await server.start();
|
|
1295
|
+
const data = await fetchAs<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
|
|
1296
|
+
const backLinkEdge = data.edges.find((e) => e.from === 'evt:EventD' && e.to === 'cmd:CmdA');
|
|
1297
|
+
expect(backLinkEdge).toBeDefined();
|
|
1298
|
+
expect(backLinkEdge?.backLink).toBe(true);
|
|
1299
|
+
await server.stop();
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1199
1302
|
it('should add event nodes from settled handler commandToEvents when not already added', async () => {
|
|
1200
1303
|
const checkAHandler = {
|
|
1201
1304
|
name: 'CheckA',
|
|
@@ -1641,6 +1744,56 @@ describe('PipelineServer', () => {
|
|
|
1641
1744
|
|
|
1642
1745
|
await server.stop();
|
|
1643
1746
|
});
|
|
1747
|
+
|
|
1748
|
+
it('documents behavior: status remains error after retry without itemKey extractor (fix: register extractor)', async () => {
|
|
1749
|
+
let callCount = 0;
|
|
1750
|
+
const handler = {
|
|
1751
|
+
name: 'RetryNoExtractor',
|
|
1752
|
+
events: ['RetryNoExtractorDone', 'RetryNoExtractorFailed'],
|
|
1753
|
+
handle: async () => {
|
|
1754
|
+
callCount++;
|
|
1755
|
+
if (callCount === 1) {
|
|
1756
|
+
return { type: 'RetryNoExtractorFailed', data: {} };
|
|
1757
|
+
}
|
|
1758
|
+
return { type: 'RetryNoExtractorDone', data: {} };
|
|
1759
|
+
},
|
|
1760
|
+
};
|
|
1761
|
+
const pipeline = define('test').on('Trigger').emit('RetryNoExtractor', {}).build();
|
|
1762
|
+
const server = new PipelineServer({ port: 0 });
|
|
1763
|
+
server.registerCommandHandlers([handler]);
|
|
1764
|
+
server.registerPipeline(pipeline);
|
|
1765
|
+
await server.start();
|
|
1766
|
+
|
|
1767
|
+
const correlationId = `corr-retry-no-extractor-bug`;
|
|
1768
|
+
|
|
1769
|
+
await fetch(`http://localhost:${server.port}/command`, {
|
|
1770
|
+
method: 'POST',
|
|
1771
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1772
|
+
body: JSON.stringify({ type: 'RetryNoExtractor', data: { targetDir: '/slice1' }, correlationId }),
|
|
1773
|
+
});
|
|
1774
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1775
|
+
|
|
1776
|
+
const afterFailure = await fetchAs<PipelineResponse>(
|
|
1777
|
+
`http://localhost:${server.port}/pipeline?correlationId=${correlationId}`,
|
|
1778
|
+
);
|
|
1779
|
+
expect(afterFailure.nodes.find((n) => n.id === 'cmd:RetryNoExtractor')?.status).toBe('error');
|
|
1780
|
+
|
|
1781
|
+
await fetch(`http://localhost:${server.port}/command`, {
|
|
1782
|
+
method: 'POST',
|
|
1783
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1784
|
+
body: JSON.stringify({ type: 'RetryNoExtractor', data: { targetDir: '/slice1' }, correlationId }),
|
|
1785
|
+
});
|
|
1786
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1787
|
+
|
|
1788
|
+
const afterRetry = await fetchAs<PipelineResponse>(
|
|
1789
|
+
`http://localhost:${server.port}/pipeline?correlationId=${correlationId}`,
|
|
1790
|
+
);
|
|
1791
|
+
const node = afterRetry.nodes.find((n) => n.id === 'cmd:RetryNoExtractor');
|
|
1792
|
+
expect(node?.status).toBe('error');
|
|
1793
|
+
expect(node?.endedCount).toBe(2);
|
|
1794
|
+
|
|
1795
|
+
await server.stop();
|
|
1796
|
+
});
|
|
1644
1797
|
});
|
|
1645
1798
|
|
|
1646
1799
|
describe('integration', () => {
|
|
@@ -1673,4 +1826,166 @@ describe('PipelineServer', () => {
|
|
|
1673
1826
|
await server.stop();
|
|
1674
1827
|
});
|
|
1675
1828
|
});
|
|
1829
|
+
|
|
1830
|
+
describe('GET /events', () => {
|
|
1831
|
+
it('should accept SSE connections', async () => {
|
|
1832
|
+
const server = new PipelineServer({ port: 0 });
|
|
1833
|
+
await server.start();
|
|
1834
|
+
|
|
1835
|
+
const controller = new AbortController();
|
|
1836
|
+
const responsePromise = fetch(`http://localhost:${server.port}/events`, {
|
|
1837
|
+
signal: controller.signal,
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1841
|
+
controller.abort();
|
|
1842
|
+
|
|
1843
|
+
try {
|
|
1844
|
+
await responsePromise;
|
|
1845
|
+
} catch {
|
|
1846
|
+
// AbortError expected
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
await server.stop();
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
it('should accept SSE connections with correlationId filter', async () => {
|
|
1853
|
+
const server = new PipelineServer({ port: 0 });
|
|
1854
|
+
await server.start();
|
|
1855
|
+
|
|
1856
|
+
const controller = new AbortController();
|
|
1857
|
+
const responsePromise = fetch(`http://localhost:${server.port}/events?correlationId=test-123`, {
|
|
1858
|
+
signal: controller.signal,
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1861
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1862
|
+
controller.abort();
|
|
1863
|
+
|
|
1864
|
+
try {
|
|
1865
|
+
await responsePromise;
|
|
1866
|
+
} catch {
|
|
1867
|
+
// AbortError expected
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
await server.stop();
|
|
1871
|
+
});
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
describe('phased execution', () => {
|
|
1875
|
+
it('should emit phased execution events when foreach-phased handler runs', async () => {
|
|
1876
|
+
type Component = { path: string; priority: 'high' | 'medium' | 'low' };
|
|
1877
|
+
type ComponentEvent = { data: { components: Component[] } };
|
|
1878
|
+
type ResultEvent = { data: { componentPath: string } };
|
|
1879
|
+
|
|
1880
|
+
const generateHandler = {
|
|
1881
|
+
name: 'GenerateComponents',
|
|
1882
|
+
events: ['ComponentsGenerated'],
|
|
1883
|
+
handle: async () => ({
|
|
1884
|
+
type: 'ComponentsGenerated',
|
|
1885
|
+
data: { components: [{ path: '/comp/a.tsx', priority: 'high' }] },
|
|
1886
|
+
}),
|
|
1887
|
+
};
|
|
1888
|
+
|
|
1889
|
+
const implementHandler = {
|
|
1890
|
+
name: 'ImplementComponent',
|
|
1891
|
+
events: ['ComponentImplemented'],
|
|
1892
|
+
handle: async (cmd: { data: { componentPath: string } }) => ({
|
|
1893
|
+
type: 'ComponentImplemented',
|
|
1894
|
+
data: { componentPath: cmd.data.componentPath },
|
|
1895
|
+
}),
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1898
|
+
const pipeline = define('test')
|
|
1899
|
+
.on('ComponentsGenerated')
|
|
1900
|
+
.forEach((e: ComponentEvent) => e.data.components)
|
|
1901
|
+
.groupInto(['high', 'medium', 'low'], (c: Component) => c.priority)
|
|
1902
|
+
.process('ImplementComponent', (c: Component) => ({ componentPath: c.path }))
|
|
1903
|
+
.onComplete({
|
|
1904
|
+
success: 'AllComponentsImplemented',
|
|
1905
|
+
failure: 'ComponentImplementationFailed',
|
|
1906
|
+
itemKey: (e: ResultEvent) => e.data.componentPath,
|
|
1907
|
+
})
|
|
1908
|
+
.build();
|
|
1909
|
+
|
|
1910
|
+
const server = new PipelineServer({ port: 0 });
|
|
1911
|
+
server.registerCommandHandlers([generateHandler, implementHandler]);
|
|
1912
|
+
server.registerPipeline(pipeline);
|
|
1913
|
+
await server.start();
|
|
1914
|
+
|
|
1915
|
+
await fetch(`http://localhost:${server.port}/command`, {
|
|
1916
|
+
method: 'POST',
|
|
1917
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1918
|
+
body: JSON.stringify({ type: 'GenerateComponents', data: {} }),
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
1922
|
+
await server.stop();
|
|
1923
|
+
});
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
describe('POST /execute', () => {
|
|
1927
|
+
it('should call handler and return event directly', async () => {
|
|
1928
|
+
const handler = {
|
|
1929
|
+
name: 'TestCmd',
|
|
1930
|
+
handle: async () => ({ type: 'TestDone', data: { result: 'success' } }),
|
|
1931
|
+
};
|
|
1932
|
+
const server = new PipelineServer({ port: 0 });
|
|
1933
|
+
server.registerCommandHandlers([handler]);
|
|
1934
|
+
await server.start();
|
|
1935
|
+
|
|
1936
|
+
const response = await fetch(`http://localhost:${server.port}/execute`, {
|
|
1937
|
+
method: 'POST',
|
|
1938
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1939
|
+
body: JSON.stringify({ command: 'TestCmd', payload: { input: 'test' } }),
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
const data = (await response.json()) as { event: string; data: Record<string, unknown> };
|
|
1943
|
+
expect(response.status).toBe(200);
|
|
1944
|
+
expect(data).toEqual({ event: 'TestDone', data: { result: 'success' } });
|
|
1945
|
+
|
|
1946
|
+
await server.stop();
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1949
|
+
it('should return 400 for unknown command', async () => {
|
|
1950
|
+
const server = new PipelineServer({ port: 0 });
|
|
1951
|
+
await server.start();
|
|
1952
|
+
|
|
1953
|
+
const response = await fetch(`http://localhost:${server.port}/execute`, {
|
|
1954
|
+
method: 'POST',
|
|
1955
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1956
|
+
body: JSON.stringify({ command: 'NonExistentCmd', payload: {} }),
|
|
1957
|
+
});
|
|
1958
|
+
|
|
1959
|
+
const data = (await response.json()) as { error: string };
|
|
1960
|
+
expect(response.status).toBe(400);
|
|
1961
|
+
expect(data).toEqual({ error: 'Unknown command: NonExistentCmd' });
|
|
1962
|
+
|
|
1963
|
+
await server.stop();
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
it('should return first event when handler returns array', async () => {
|
|
1967
|
+
const handler = {
|
|
1968
|
+
name: 'MultiEventCmd',
|
|
1969
|
+
handle: async () => [
|
|
1970
|
+
{ type: 'FirstEvent', data: { order: 1 } },
|
|
1971
|
+
{ type: 'SecondEvent', data: { order: 2 } },
|
|
1972
|
+
],
|
|
1973
|
+
};
|
|
1974
|
+
const server = new PipelineServer({ port: 0 });
|
|
1975
|
+
server.registerCommandHandlers([handler]);
|
|
1976
|
+
await server.start();
|
|
1977
|
+
|
|
1978
|
+
const response = await fetch(`http://localhost:${server.port}/execute`, {
|
|
1979
|
+
method: 'POST',
|
|
1980
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1981
|
+
body: JSON.stringify({ command: 'MultiEventCmd', payload: {} }),
|
|
1982
|
+
});
|
|
1983
|
+
|
|
1984
|
+
const data = (await response.json()) as { event: string; data: Record<string, unknown> };
|
|
1985
|
+
expect(response.status).toBe(200);
|
|
1986
|
+
expect(data).toEqual({ event: 'FirstEvent', data: { order: 1 } });
|
|
1987
|
+
|
|
1988
|
+
await server.stop();
|
|
1989
|
+
});
|
|
1990
|
+
});
|
|
1676
1991
|
});
|
|
@@ -69,9 +69,11 @@ export class PipelineServer {
|
|
|
69
69
|
this.eventCommandMapper = new EventCommandMapper([]);
|
|
70
70
|
this.settledTracker = new SettledTracker({
|
|
71
71
|
readModel: this.eventStoreContext.readModel,
|
|
72
|
+
/* v8 ignore next 3 - integration callback tested via settled-tracker.specs.ts */
|
|
72
73
|
onDispatch: (commandType, data, correlationId) => {
|
|
73
74
|
void this.dispatchFromSettled(commandType, data, correlationId);
|
|
74
75
|
},
|
|
76
|
+
/* v8 ignore next 4 - integration callback tested via settled-tracker.specs.ts */
|
|
75
77
|
onEventEmit: async (event) => {
|
|
76
78
|
const correlationId = event.data.correlationId;
|
|
77
79
|
await this.eventStoreContext.eventStore.appendToStream(`pipeline-${correlationId}`, [event]);
|
|
@@ -79,12 +81,15 @@ export class PipelineServer {
|
|
|
79
81
|
});
|
|
80
82
|
this.phasedExecutor = new PhasedExecutor({
|
|
81
83
|
readModel: this.eventStoreContext.readModel,
|
|
84
|
+
/* v8 ignore next 3 - integration callback tested via phased-executor.specs.ts */
|
|
82
85
|
onDispatch: (commandType, data, correlationId) => {
|
|
83
86
|
void this.dispatchFromSettled(commandType, data, correlationId);
|
|
84
87
|
},
|
|
88
|
+
/* v8 ignore next 3 - integration callback tested via phased-executor.specs.ts */
|
|
85
89
|
onComplete: (event, correlationId) => {
|
|
86
90
|
void this.handlePhasedComplete(event, correlationId);
|
|
87
91
|
},
|
|
92
|
+
/* v8 ignore next 5 - integration callback tested via phased-executor.specs.ts */
|
|
88
93
|
onEventEmit: async (event) => {
|
|
89
94
|
const data = event.data as Record<string, unknown>;
|
|
90
95
|
const correlationId =
|
|
@@ -289,6 +294,27 @@ export class PipelineServer {
|
|
|
289
294
|
const correlationIdFilter = req.query.correlationId as string | undefined;
|
|
290
295
|
this.sseManager.addClient(clientId, res, correlationIdFilter);
|
|
291
296
|
});
|
|
297
|
+
|
|
298
|
+
this.app.post('/execute', (req, res) => {
|
|
299
|
+
void (async () => {
|
|
300
|
+
const { command, payload } = req.body as {
|
|
301
|
+
command: string;
|
|
302
|
+
payload: Record<string, unknown>;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const handler = this.commandHandlers.get(command);
|
|
306
|
+
if (!handler) {
|
|
307
|
+
res.status(400).json({ error: `Unknown command: ${command}` });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const resultEvent = await handler.handle({ type: command, data: payload });
|
|
312
|
+
const events = Array.isArray(resultEvent) ? resultEvent : [resultEvent];
|
|
313
|
+
const firstEvent = events[0];
|
|
314
|
+
|
|
315
|
+
res.json({ event: firstEvent.type, data: firstEvent.data });
|
|
316
|
+
})();
|
|
317
|
+
});
|
|
292
318
|
}
|
|
293
319
|
|
|
294
320
|
private buildCombinedGraph(): GraphIR {
|
|
@@ -622,14 +648,14 @@ export class PipelineServer {
|
|
|
622
648
|
return { excludeTypes, maintainEdges };
|
|
623
649
|
}
|
|
624
650
|
|
|
625
|
-
private buildMermaidDiagram(filterOptions
|
|
651
|
+
private buildMermaidDiagram(filterOptions: FilterOptions): string {
|
|
626
652
|
const commandToEvents = this.buildCommandToEvents();
|
|
627
653
|
const rawGraph = this.buildCombinedGraph();
|
|
628
654
|
const pipelineEvents = this.extractPipelineEvents(rawGraph, commandToEvents);
|
|
629
655
|
const graphWithEvents = this.addCommandEventEdgesToGraph(rawGraph, commandToEvents, pipelineEvents);
|
|
630
656
|
const graphWithEnrichedEvents = this.enrichEventLabels(graphWithEvents);
|
|
631
657
|
const completeGraph = this.markBackLinks(graphWithEnrichedEvents);
|
|
632
|
-
const graph =
|
|
658
|
+
const graph = filterGraph(completeGraph, filterOptions);
|
|
633
659
|
const lines: string[] = ['flowchart LR'];
|
|
634
660
|
|
|
635
661
|
const eventNodes = new Set<string>();
|
|
@@ -707,10 +733,7 @@ export class PipelineServer {
|
|
|
707
733
|
const queue = [from];
|
|
708
734
|
|
|
709
735
|
while (queue.length > 0) {
|
|
710
|
-
const current = queue.shift()
|
|
711
|
-
if (current === undefined) {
|
|
712
|
-
break;
|
|
713
|
-
}
|
|
736
|
+
const current = queue.shift()!;
|
|
714
737
|
if (current === target) {
|
|
715
738
|
return true;
|
|
716
739
|
}
|
|
@@ -721,10 +744,7 @@ export class PipelineServer {
|
|
|
721
744
|
|
|
722
745
|
const neighbors = outgoingEdges.get(current) ?? [];
|
|
723
746
|
for (const neighbor of neighbors) {
|
|
724
|
-
if (neighbor.isBackLink) {
|
|
725
|
-
continue;
|
|
726
|
-
}
|
|
727
|
-
if (!visited.has(neighbor.to)) {
|
|
747
|
+
if (!neighbor.isBackLink && !visited.has(neighbor.to)) {
|
|
728
748
|
queue.push(neighbor.to);
|
|
729
749
|
}
|
|
730
750
|
}
|
|
@@ -863,8 +883,7 @@ export class PipelineServer {
|
|
|
863
883
|
const events = Array.isArray(resultEvent) ? resultEvent : [resultEvent];
|
|
864
884
|
|
|
865
885
|
const finalStatus = this.getStatusFromEvents(events);
|
|
866
|
-
|
|
867
|
-
await this.updateItemStatus(command.correlationId, command.type, itemKey, itemFinalStatus);
|
|
886
|
+
await this.updateItemStatus(command.correlationId, command.type, itemKey, finalStatus);
|
|
868
887
|
await this.updateNodeStatus(command.correlationId, command.type, finalStatus);
|
|
869
888
|
|
|
870
889
|
const eventsWithIds: EventWithCorrelation[] = events.map((event) => ({
|
|
@@ -893,7 +912,7 @@ export class PipelineServer {
|
|
|
893
912
|
await Promise.all(eventsWithIds.map((e) => this.routeEventToPipelines(e)));
|
|
894
913
|
}
|
|
895
914
|
|
|
896
|
-
private getStatusFromEvents(events: Event[]):
|
|
915
|
+
private getStatusFromEvents(events: Event[]): 'success' | 'error' {
|
|
897
916
|
for (const event of events) {
|
|
898
917
|
if (event.type.includes('Failed')) {
|
|
899
918
|
return 'error';
|
|
@@ -902,6 +921,7 @@ export class PipelineServer {
|
|
|
902
921
|
return 'success';
|
|
903
922
|
}
|
|
904
923
|
|
|
924
|
+
/* v8 ignore next 11 - integration path tested via settled-tracker.specs.ts */
|
|
905
925
|
private async dispatchFromSettled(commandType: string, data: unknown, correlationId: string): Promise<void> {
|
|
906
926
|
const requestId = `req-${nanoid()}`;
|
|
907
927
|
const command: Command & { correlationId: string; requestId: string } = {
|
|
@@ -914,6 +934,7 @@ export class PipelineServer {
|
|
|
914
934
|
await this.processCommand(command);
|
|
915
935
|
}
|
|
916
936
|
|
|
937
|
+
/* v8 ignore next 10 - integration path tested via phased-executor.specs.ts */
|
|
917
938
|
private async handlePhasedComplete(event: Event, correlationId: string): Promise<void> {
|
|
918
939
|
const requestId = `req-${nanoid()}`;
|
|
919
940
|
const eventWithIds: EventWithCorrelation = {
|
|
@@ -956,12 +977,14 @@ export class PipelineServer {
|
|
|
956
977
|
await this.emitCommandDispatched(correlationId, requestId, type, data as Record<string, unknown>);
|
|
957
978
|
await this.processCommand(command);
|
|
958
979
|
},
|
|
980
|
+
/* v8 ignore next 3 - integration path tested via pipeline-runtime.specs.ts */
|
|
959
981
|
startPhased: async (handler, event) => {
|
|
960
982
|
await this.phasedExecutor.startPhased(handler, event, correlationId);
|
|
961
983
|
},
|
|
962
984
|
};
|
|
963
985
|
}
|
|
964
986
|
|
|
987
|
+
/* v8 ignore next 10 - integration path tested via phased-executor.specs.ts */
|
|
965
988
|
private async routeEventToPhasedExecutor(event: EventWithCorrelation): Promise<void> {
|
|
966
989
|
for (const pipeline of this.pipelines.values()) {
|
|
967
990
|
for (const handler of pipeline.descriptor.handlers) {
|
|
@@ -10,10 +10,6 @@ interface SSEClient {
|
|
|
10
10
|
export class SSEManager {
|
|
11
11
|
private clients = new Map<string, SSEClient>();
|
|
12
12
|
|
|
13
|
-
get clientCount(): number {
|
|
14
|
-
return this.clients.size;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
addClient(id: string, response: Response, correlationIdFilter?: string): void {
|
|
18
14
|
response.writeHead(200, {
|
|
19
15
|
'Content-Type': 'text/event-stream',
|
|
@@ -170,6 +170,54 @@ describe('PipelineEventStore', () => {
|
|
|
170
170
|
await close();
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
|
+
|
|
174
|
+
it('should track message stats through CommandDispatched and DomainEventEmitted', async () => {
|
|
175
|
+
const { eventStore, readModel, close } = createPipelineEventStore();
|
|
176
|
+
try {
|
|
177
|
+
await eventStore.appendToStream('pipeline-c1', [
|
|
178
|
+
{
|
|
179
|
+
type: 'CommandDispatched',
|
|
180
|
+
data: {
|
|
181
|
+
correlationId: 'c1',
|
|
182
|
+
requestId: 'r1',
|
|
183
|
+
commandType: 'CreateUser',
|
|
184
|
+
commandData: { name: 'Alice' },
|
|
185
|
+
timestamp: new Date(),
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
type: 'CommandDispatched',
|
|
190
|
+
data: {
|
|
191
|
+
correlationId: 'c1',
|
|
192
|
+
requestId: 'r2',
|
|
193
|
+
commandType: 'UpdateUser',
|
|
194
|
+
commandData: { name: 'Bob' },
|
|
195
|
+
timestamp: new Date(),
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'DomainEventEmitted',
|
|
200
|
+
data: {
|
|
201
|
+
correlationId: 'c1',
|
|
202
|
+
requestId: 'r1',
|
|
203
|
+
eventType: 'UserCreated',
|
|
204
|
+
eventData: { userId: '123' },
|
|
205
|
+
timestamp: new Date(),
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
const stats = await readModel.getStats();
|
|
211
|
+
|
|
212
|
+
expect(stats).toEqual({
|
|
213
|
+
totalMessages: 3,
|
|
214
|
+
totalCommands: 2,
|
|
215
|
+
totalEvents: 1,
|
|
216
|
+
});
|
|
217
|
+
} finally {
|
|
218
|
+
await close();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
173
221
|
});
|
|
174
222
|
|
|
175
223
|
describe('settled instance projection', () => {
|