@auto-engineer/pipeline 0.0.1
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 +5 -0
- package/LICENSE +10 -0
- package/claude.md +160 -0
- package/dist/src/builder/define.d.ts +90 -0
- package/dist/src/builder/define.d.ts.map +1 -0
- package/dist/src/builder/define.js +425 -0
- package/dist/src/builder/define.js.map +1 -0
- package/dist/src/builder/define.specs.d.ts +2 -0
- package/dist/src/builder/define.specs.d.ts.map +1 -0
- package/dist/src/builder/define.specs.js +435 -0
- package/dist/src/builder/define.specs.js.map +1 -0
- package/dist/src/config/pipeline-config.d.ts +13 -0
- package/dist/src/config/pipeline-config.d.ts.map +1 -0
- package/dist/src/config/pipeline-config.js +15 -0
- package/dist/src/config/pipeline-config.js.map +1 -0
- package/dist/src/core/descriptors.d.ts +84 -0
- package/dist/src/core/descriptors.d.ts.map +1 -0
- package/dist/src/core/descriptors.js +2 -0
- package/dist/src/core/descriptors.js.map +1 -0
- package/dist/src/core/descriptors.specs.d.ts +2 -0
- package/dist/src/core/descriptors.specs.d.ts.map +1 -0
- package/dist/src/core/descriptors.specs.js +24 -0
- package/dist/src/core/descriptors.specs.js.map +1 -0
- package/dist/src/core/types.d.ts +10 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +4 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/types.specs.d.ts +2 -0
- package/dist/src/core/types.specs.d.ts.map +1 -0
- package/dist/src/core/types.specs.js +40 -0
- package/dist/src/core/types.specs.js.map +1 -0
- package/dist/src/graph/types.d.ts +17 -0
- package/dist/src/graph/types.d.ts.map +1 -0
- package/dist/src/graph/types.js +2 -0
- package/dist/src/graph/types.js.map +1 -0
- package/dist/src/graph/types.specs.d.ts +2 -0
- package/dist/src/graph/types.specs.d.ts.map +1 -0
- package/dist/src/graph/types.specs.js +148 -0
- package/dist/src/graph/types.specs.js.map +1 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logging/event-logger.d.ts +21 -0
- package/dist/src/logging/event-logger.d.ts.map +1 -0
- package/dist/src/logging/event-logger.js +31 -0
- package/dist/src/logging/event-logger.js.map +1 -0
- package/dist/src/logging/event-logger.specs.d.ts +2 -0
- package/dist/src/logging/event-logger.specs.d.ts.map +1 -0
- package/dist/src/logging/event-logger.specs.js +81 -0
- package/dist/src/logging/event-logger.specs.js.map +1 -0
- package/dist/src/plugins/handler-adapter.d.ts +5 -0
- package/dist/src/plugins/handler-adapter.d.ts.map +1 -0
- package/dist/src/plugins/handler-adapter.js +17 -0
- package/dist/src/plugins/handler-adapter.js.map +1 -0
- package/dist/src/plugins/handler-adapter.specs.d.ts +2 -0
- package/dist/src/plugins/handler-adapter.specs.d.ts.map +1 -0
- package/dist/src/plugins/handler-adapter.specs.js +129 -0
- package/dist/src/plugins/handler-adapter.specs.js.map +1 -0
- package/dist/src/plugins/plugin-loader.d.ts +25 -0
- package/dist/src/plugins/plugin-loader.d.ts.map +1 -0
- package/dist/src/plugins/plugin-loader.js +150 -0
- package/dist/src/plugins/plugin-loader.js.map +1 -0
- package/dist/src/plugins/plugin-loader.specs.d.ts +2 -0
- package/dist/src/plugins/plugin-loader.specs.d.ts.map +1 -0
- package/dist/src/plugins/plugin-loader.specs.js +246 -0
- package/dist/src/plugins/plugin-loader.specs.js.map +1 -0
- package/dist/src/runtime/await-tracker.d.ts +10 -0
- package/dist/src/runtime/await-tracker.d.ts.map +1 -0
- package/dist/src/runtime/await-tracker.js +42 -0
- package/dist/src/runtime/await-tracker.js.map +1 -0
- package/dist/src/runtime/await-tracker.specs.d.ts +2 -0
- package/dist/src/runtime/await-tracker.specs.d.ts.map +1 -0
- package/dist/src/runtime/await-tracker.specs.js +46 -0
- package/dist/src/runtime/await-tracker.specs.js.map +1 -0
- package/dist/src/runtime/context.d.ts +12 -0
- package/dist/src/runtime/context.d.ts.map +1 -0
- package/dist/src/runtime/context.js +2 -0
- package/dist/src/runtime/context.js.map +1 -0
- package/dist/src/runtime/context.specs.d.ts +2 -0
- package/dist/src/runtime/context.specs.d.ts.map +1 -0
- package/dist/src/runtime/context.specs.js +26 -0
- package/dist/src/runtime/context.specs.js.map +1 -0
- package/dist/src/runtime/event-command-map.d.ts +15 -0
- package/dist/src/runtime/event-command-map.d.ts.map +1 -0
- package/dist/src/runtime/event-command-map.js +26 -0
- package/dist/src/runtime/event-command-map.js.map +1 -0
- package/dist/src/runtime/event-command-map.specs.d.ts +2 -0
- package/dist/src/runtime/event-command-map.specs.d.ts.map +1 -0
- package/dist/src/runtime/event-command-map.specs.js +108 -0
- package/dist/src/runtime/event-command-map.specs.js.map +1 -0
- package/dist/src/runtime/phased-executor.d.ts +29 -0
- package/dist/src/runtime/phased-executor.d.ts.map +1 -0
- package/dist/src/runtime/phased-executor.js +164 -0
- package/dist/src/runtime/phased-executor.js.map +1 -0
- package/dist/src/runtime/phased-executor.specs.d.ts +2 -0
- package/dist/src/runtime/phased-executor.specs.d.ts.map +1 -0
- package/dist/src/runtime/phased-executor.specs.js +256 -0
- package/dist/src/runtime/phased-executor.specs.js.map +1 -0
- package/dist/src/runtime/pipeline-runtime.d.ts +17 -0
- package/dist/src/runtime/pipeline-runtime.d.ts.map +1 -0
- package/dist/src/runtime/pipeline-runtime.js +87 -0
- package/dist/src/runtime/pipeline-runtime.js.map +1 -0
- package/dist/src/runtime/pipeline-runtime.specs.d.ts +2 -0
- package/dist/src/runtime/pipeline-runtime.specs.d.ts.map +1 -0
- package/dist/src/runtime/pipeline-runtime.specs.js +192 -0
- package/dist/src/runtime/pipeline-runtime.specs.js.map +1 -0
- package/dist/src/runtime/settled-tracker.d.ts +42 -0
- package/dist/src/runtime/settled-tracker.d.ts.map +1 -0
- package/dist/src/runtime/settled-tracker.js +161 -0
- package/dist/src/runtime/settled-tracker.js.map +1 -0
- package/dist/src/runtime/settled-tracker.specs.d.ts +2 -0
- package/dist/src/runtime/settled-tracker.specs.d.ts.map +1 -0
- package/dist/src/runtime/settled-tracker.specs.js +361 -0
- package/dist/src/runtime/settled-tracker.specs.js.map +1 -0
- package/dist/src/server/full-orchestration.e2e.specs.d.ts +2 -0
- package/dist/src/server/full-orchestration.e2e.specs.d.ts.map +1 -0
- package/dist/src/server/full-orchestration.e2e.specs.js +561 -0
- package/dist/src/server/full-orchestration.e2e.specs.js.map +1 -0
- package/dist/src/server/pipeline-server.d.ts +59 -0
- package/dist/src/server/pipeline-server.d.ts.map +1 -0
- package/dist/src/server/pipeline-server.e2e.specs.d.ts +2 -0
- package/dist/src/server/pipeline-server.e2e.specs.d.ts.map +1 -0
- package/dist/src/server/pipeline-server.e2e.specs.js +381 -0
- package/dist/src/server/pipeline-server.e2e.specs.js.map +1 -0
- package/dist/src/server/pipeline-server.js +527 -0
- package/dist/src/server/pipeline-server.js.map +1 -0
- package/dist/src/server/pipeline-server.specs.d.ts +2 -0
- package/dist/src/server/pipeline-server.specs.d.ts.map +1 -0
- package/dist/src/server/pipeline-server.specs.js +662 -0
- package/dist/src/server/pipeline-server.specs.js.map +1 -0
- package/dist/src/server/sse-manager.d.ts +12 -0
- package/dist/src/server/sse-manager.d.ts.map +1 -0
- package/dist/src/server/sse-manager.js +63 -0
- package/dist/src/server/sse-manager.js.map +1 -0
- package/dist/src/server/sse-manager.specs.d.ts +2 -0
- package/dist/src/server/sse-manager.specs.d.ts.map +1 -0
- package/dist/src/server/sse-manager.specs.js +158 -0
- package/dist/src/server/sse-manager.specs.js.map +1 -0
- package/dist/src/testing/event-capture.d.ts +14 -0
- package/dist/src/testing/event-capture.d.ts.map +1 -0
- package/dist/src/testing/event-capture.js +55 -0
- package/dist/src/testing/event-capture.js.map +1 -0
- package/dist/src/testing/event-capture.specs.d.ts +2 -0
- package/dist/src/testing/event-capture.specs.d.ts.map +1 -0
- package/dist/src/testing/event-capture.specs.js +114 -0
- package/dist/src/testing/event-capture.specs.js.map +1 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.d.ts +7 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.d.ts.map +1 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.js +168 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.js.map +1 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts +2 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts.map +1 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js +263 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js.map +1 -0
- package/dist/src/testing/fixtures/kanban-todo.config.d.ts +3 -0
- package/dist/src/testing/fixtures/kanban-todo.config.d.ts.map +1 -0
- package/dist/src/testing/fixtures/kanban-todo.config.js +19 -0
- package/dist/src/testing/fixtures/kanban-todo.config.js.map +1 -0
- package/dist/src/testing/fixtures/kanban.pipeline.d.ts +5 -0
- package/dist/src/testing/fixtures/kanban.pipeline.d.ts.map +1 -0
- package/dist/src/testing/fixtures/kanban.pipeline.js +76 -0
- package/dist/src/testing/fixtures/kanban.pipeline.js.map +1 -0
- package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts +2 -0
- package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts.map +1 -0
- package/dist/src/testing/fixtures/kanban.pipeline.specs.js +29 -0
- package/dist/src/testing/fixtures/kanban.pipeline.specs.js.map +1 -0
- package/dist/src/testing/kanban-todo.e2e.specs.d.ts +2 -0
- package/dist/src/testing/kanban-todo.e2e.specs.d.ts.map +1 -0
- package/dist/src/testing/kanban-todo.e2e.specs.js +160 -0
- package/dist/src/testing/kanban-todo.e2e.specs.js.map +1 -0
- package/dist/src/testing/mock-handlers.d.ts +21 -0
- package/dist/src/testing/mock-handlers.d.ts.map +1 -0
- package/dist/src/testing/mock-handlers.js +34 -0
- package/dist/src/testing/mock-handlers.js.map +1 -0
- package/dist/src/testing/mock-handlers.specs.d.ts +2 -0
- package/dist/src/testing/mock-handlers.specs.d.ts.map +1 -0
- package/dist/src/testing/mock-handlers.specs.js +193 -0
- package/dist/src/testing/mock-handlers.specs.js.map +1 -0
- package/dist/src/testing/real-execution.e2e.specs.d.ts +2 -0
- package/dist/src/testing/real-execution.e2e.specs.d.ts.map +1 -0
- package/dist/src/testing/real-execution.e2e.specs.js +140 -0
- package/dist/src/testing/real-execution.e2e.specs.js.map +1 -0
- package/dist/src/testing/real-plugin.e2e.specs.d.ts +2 -0
- package/dist/src/testing/real-plugin.e2e.specs.d.ts.map +1 -0
- package/dist/src/testing/real-plugin.e2e.specs.js +65 -0
- package/dist/src/testing/real-plugin.e2e.specs.js.map +1 -0
- package/dist/src/testing/server-startup.e2e.specs.d.ts +2 -0
- package/dist/src/testing/server-startup.e2e.specs.d.ts.map +1 -0
- package/dist/src/testing/server-startup.e2e.specs.js +104 -0
- package/dist/src/testing/server-startup.e2e.specs.js.map +1 -0
- package/dist/src/testing/snapshot-compare.d.ts +18 -0
- package/dist/src/testing/snapshot-compare.d.ts.map +1 -0
- package/dist/src/testing/snapshot-compare.js +86 -0
- package/dist/src/testing/snapshot-compare.js.map +1 -0
- package/dist/src/testing/snapshot-compare.specs.d.ts +2 -0
- package/dist/src/testing/snapshot-compare.specs.d.ts.map +1 -0
- package/dist/src/testing/snapshot-compare.specs.js +112 -0
- package/dist/src/testing/snapshot-compare.specs.js.map +1 -0
- package/dist/src/testing/snapshot-sanitize.d.ts +8 -0
- package/dist/src/testing/snapshot-sanitize.d.ts.map +1 -0
- package/dist/src/testing/snapshot-sanitize.js +10 -0
- package/dist/src/testing/snapshot-sanitize.js.map +1 -0
- package/dist/src/testing/snapshot-sanitize.specs.d.ts +2 -0
- package/dist/src/testing/snapshot-sanitize.specs.d.ts.map +1 -0
- package/dist/src/testing/snapshot-sanitize.specs.js +104 -0
- package/dist/src/testing/snapshot-sanitize.specs.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/testing-analysis.md +395 -0
- package/package.json +31 -0
- package/pipeline-api-new.md +1078 -0
- package/pomodoro-plan.md +651 -0
- package/scripts/run-kanban-e2e.ts +219 -0
- package/scripts/start-server.ts +64 -0
- package/snapshots/e2e-run-2025-12-22T15-52-03.json +613 -0
- package/snapshots/e2e-run-2025-12-22T16-51-30.json +699 -0
- package/src/builder/define.specs.ts +531 -0
- package/src/builder/define.ts +700 -0
- package/src/config/pipeline-config.ts +32 -0
- package/src/core/descriptors.specs.ts +28 -0
- package/src/core/descriptors.ts +99 -0
- package/src/core/types.specs.ts +44 -0
- package/src/core/types.ts +16 -0
- package/src/graph/types.specs.ts +176 -0
- package/src/graph/types.ts +19 -0
- package/src/index.ts +54 -0
- package/src/logging/event-logger.specs.ts +100 -0
- package/src/logging/event-logger.ts +50 -0
- package/src/plugins/handler-adapter.specs.ts +164 -0
- package/src/plugins/handler-adapter.ts +21 -0
- package/src/plugins/plugin-loader.specs.ts +295 -0
- package/src/plugins/plugin-loader.ts +202 -0
- package/src/runtime/await-tracker.specs.ts +52 -0
- package/src/runtime/await-tracker.ts +50 -0
- package/src/runtime/context.specs.ts +28 -0
- package/src/runtime/context.ts +13 -0
- package/src/runtime/event-command-map.specs.ts +136 -0
- package/src/runtime/event-command-map.ts +38 -0
- package/src/runtime/phased-executor.specs.ts +358 -0
- package/src/runtime/phased-executor.ts +224 -0
- package/src/runtime/pipeline-runtime.specs.ts +214 -0
- package/src/runtime/pipeline-runtime.ts +119 -0
- package/src/runtime/settled-tracker.specs.ts +448 -0
- package/src/runtime/settled-tracker.ts +237 -0
- package/src/server/full-orchestration.e2e.specs.ts +672 -0
- package/src/server/pipeline-server.e2e.specs.ts +505 -0
- package/src/server/pipeline-server.specs.ts +761 -0
- package/src/server/pipeline-server.ts +656 -0
- package/src/server/sse-manager.specs.ts +208 -0
- package/src/server/sse-manager.ts +79 -0
- package/src/testing/event-capture.specs.ts +143 -0
- package/src/testing/event-capture.ts +65 -0
- package/src/testing/fixtures/kanban-full.pipeline.specs.ts +337 -0
- package/src/testing/fixtures/kanban-full.pipeline.ts +225 -0
- package/src/testing/fixtures/kanban-todo.config.ts +19 -0
- package/src/testing/fixtures/kanban.pipeline.specs.ts +33 -0
- package/src/testing/fixtures/kanban.pipeline.ts +124 -0
- package/src/testing/kanban-todo.e2e.specs.ts +209 -0
- package/src/testing/mock-handlers.specs.ts +229 -0
- package/src/testing/mock-handlers.ts +58 -0
- package/src/testing/real-execution.e2e.specs.ts +193 -0
- package/src/testing/real-plugin.e2e.specs.ts +94 -0
- package/src/testing/server-startup.e2e.specs.ts +162 -0
- package/src/testing/snapshot-compare.specs.ts +136 -0
- package/src/testing/snapshot-compare.ts +106 -0
- package/src/testing/snapshot-sanitize.specs.ts +131 -0
- package/src/testing/snapshot-sanitize.ts +17 -0
- package/tsconfig.json +11 -0
- package/tsconfig.test.json +9 -0
- package/vitest.config.ts +29 -0
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { createServer, type Server as HttpServer } from 'node:http';
|
|
2
|
+
import {
|
|
3
|
+
type Command,
|
|
4
|
+
type CommandHandler,
|
|
5
|
+
createMessageBus,
|
|
6
|
+
type Event,
|
|
7
|
+
type MessageBus,
|
|
8
|
+
} from '@auto-engineer/message-bus';
|
|
9
|
+
import { type ILocalMessageStore, MemoryMessageStore } from '@auto-engineer/message-store';
|
|
10
|
+
import cors from 'cors';
|
|
11
|
+
import express from 'express';
|
|
12
|
+
import getPort from 'get-port';
|
|
13
|
+
import { nanoid } from 'nanoid';
|
|
14
|
+
import type { Pipeline } from '../builder/define';
|
|
15
|
+
import type { GraphIR } from '../graph/types';
|
|
16
|
+
import type { PipelineContext } from '../runtime/context';
|
|
17
|
+
import { EventCommandMapper } from '../runtime/event-command-map';
|
|
18
|
+
import { PhasedExecutor } from '../runtime/phased-executor';
|
|
19
|
+
import { PipelineRuntime } from '../runtime/pipeline-runtime';
|
|
20
|
+
import { SettledTracker } from '../runtime/settled-tracker';
|
|
21
|
+
import { SSEManager } from './sse-manager';
|
|
22
|
+
|
|
23
|
+
export interface CommandHandlerWithMetadata extends CommandHandler {
|
|
24
|
+
alias?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
fields?: Record<string, unknown>;
|
|
27
|
+
examples?: unknown[];
|
|
28
|
+
events?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PipelineServerConfig {
|
|
32
|
+
port: number;
|
|
33
|
+
messageStore?: ILocalMessageStore;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface EventWithCorrelation extends Event {
|
|
37
|
+
correlationId: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class PipelineServer {
|
|
41
|
+
private app: express.Application;
|
|
42
|
+
private httpServer: HttpServer;
|
|
43
|
+
private messageBus: MessageBus;
|
|
44
|
+
private messageStore: ILocalMessageStore;
|
|
45
|
+
private readonly commandHandlers: Map<string, CommandHandlerWithMetadata> = new Map();
|
|
46
|
+
private readonly pipelines: Map<string, Pipeline> = new Map();
|
|
47
|
+
private readonly runtimes: Map<string, PipelineRuntime> = new Map();
|
|
48
|
+
private actualPort: number;
|
|
49
|
+
private readonly requestedPort: number;
|
|
50
|
+
private currentSessionId?: string;
|
|
51
|
+
private readonly settledTracker: SettledTracker;
|
|
52
|
+
private readonly eventCommandMapper: EventCommandMapper;
|
|
53
|
+
private readonly phasedExecutor: PhasedExecutor;
|
|
54
|
+
private readonly sseManager: SSEManager;
|
|
55
|
+
|
|
56
|
+
constructor(config: PipelineServerConfig) {
|
|
57
|
+
this.requestedPort = config.port;
|
|
58
|
+
this.actualPort = config.port;
|
|
59
|
+
this.app = express();
|
|
60
|
+
this.app.use(cors());
|
|
61
|
+
this.app.use(express.json());
|
|
62
|
+
this.httpServer = createServer(this.app);
|
|
63
|
+
this.messageBus = createMessageBus();
|
|
64
|
+
this.messageStore = config.messageStore ?? new MemoryMessageStore();
|
|
65
|
+
this.eventCommandMapper = new EventCommandMapper([]);
|
|
66
|
+
this.settledTracker = new SettledTracker({
|
|
67
|
+
onDispatch: (commandType, data, correlationId) => {
|
|
68
|
+
void this.dispatchFromSettled(commandType, data, correlationId);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
this.phasedExecutor = new PhasedExecutor({
|
|
72
|
+
onDispatch: (commandType, data, correlationId) => {
|
|
73
|
+
void this.dispatchFromSettled(commandType, data, correlationId);
|
|
74
|
+
},
|
|
75
|
+
onComplete: (event, correlationId) => {
|
|
76
|
+
void this.handlePhasedComplete(event, correlationId);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
this.sseManager = new SSEManager();
|
|
80
|
+
this.setupRoutes();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get port(): number {
|
|
84
|
+
return this.actualPort;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
registerCommandHandlers(handlers: CommandHandlerWithMetadata[]): void {
|
|
88
|
+
for (const handler of handlers) {
|
|
89
|
+
this.commandHandlers.set(handler.name, handler);
|
|
90
|
+
this.messageBus.registerCommandHandler(handler);
|
|
91
|
+
this.eventCommandMapper.addHandler(handler);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getRegisteredCommands(): string[] {
|
|
96
|
+
return Array.from(this.commandHandlers.keys());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
registerPipeline(pipeline: Pipeline): void {
|
|
100
|
+
this.pipelines.set(pipeline.descriptor.name, pipeline);
|
|
101
|
+
this.runtimes.set(pipeline.descriptor.name, new PipelineRuntime(pipeline.descriptor));
|
|
102
|
+
|
|
103
|
+
for (const handler of pipeline.descriptor.handlers) {
|
|
104
|
+
if (handler.type === 'settled') {
|
|
105
|
+
this.settledTracker.registerHandler({
|
|
106
|
+
commandTypes: handler.commandTypes,
|
|
107
|
+
handler: handler.handler,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getPipelineNames(): string[] {
|
|
114
|
+
return Array.from(this.pipelines.keys());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async start(): Promise<void> {
|
|
118
|
+
if (this.requestedPort === 0) {
|
|
119
|
+
this.actualPort = await getPort();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.currentSessionId = await this.messageStore.createSession();
|
|
123
|
+
|
|
124
|
+
await new Promise<void>((resolve) => {
|
|
125
|
+
this.httpServer.listen(this.actualPort, () => {
|
|
126
|
+
resolve();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async stop(): Promise<void> {
|
|
132
|
+
this.sseManager.closeAll();
|
|
133
|
+
if (this.currentSessionId !== undefined) {
|
|
134
|
+
await this.messageStore.endSession(this.currentSessionId);
|
|
135
|
+
}
|
|
136
|
+
await new Promise<void>((resolve) => {
|
|
137
|
+
this.httpServer.close(() => resolve());
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private setupRoutes(): void {
|
|
142
|
+
this.app.get('/health', (_req, res) => {
|
|
143
|
+
res.json({
|
|
144
|
+
status: 'healthy',
|
|
145
|
+
uptime: process.uptime(),
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
this.app.get('/registry', (_req, res) => {
|
|
151
|
+
const eventHandlers: string[] = [];
|
|
152
|
+
for (const pipeline of this.pipelines.values()) {
|
|
153
|
+
for (const handler of pipeline.descriptor.handlers) {
|
|
154
|
+
if (handler.type === 'settled') {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!eventHandlers.includes(handler.eventType)) {
|
|
158
|
+
eventHandlers.push(handler.eventType);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const commandsWithMetadata = Array.from(this.commandHandlers.entries()).map(([name, handler]) => {
|
|
164
|
+
const alias = handler.alias ?? name;
|
|
165
|
+
const description = handler.description ?? '';
|
|
166
|
+
const fields = handler.fields ?? {};
|
|
167
|
+
const examples = handler.examples ?? [];
|
|
168
|
+
return { id: alias, name, alias, description, fields, examples };
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
res.json({
|
|
172
|
+
eventHandlers,
|
|
173
|
+
commandHandlers: Array.from(this.commandHandlers.keys()),
|
|
174
|
+
commandsWithMetadata,
|
|
175
|
+
folds: [],
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
this.app.get('/pipeline', (_req, res) => {
|
|
180
|
+
const combinedGraph = this.buildCombinedGraph();
|
|
181
|
+
const commandToEvents = this.buildCommandToEvents();
|
|
182
|
+
const eventToCommand = this.buildEventToCommand();
|
|
183
|
+
const pipelineNodes = this.buildPipelineNodes();
|
|
184
|
+
|
|
185
|
+
res.json({
|
|
186
|
+
nodes: [...combinedGraph.nodes, ...pipelineNodes],
|
|
187
|
+
edges: combinedGraph.edges,
|
|
188
|
+
commandToEvents,
|
|
189
|
+
eventToCommand,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
this.app.get('/pipeline/mermaid', (_req, res) => {
|
|
194
|
+
const mermaid = this.buildMermaidDiagram();
|
|
195
|
+
res.type('text/plain').send(mermaid);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
this.app.get('/pipeline/diagram', (_req, res) => {
|
|
199
|
+
const mermaidDefinition = this.buildMermaidDiagram();
|
|
200
|
+
const html = this.buildDiagramHtml(mermaidDefinition);
|
|
201
|
+
res.type('text/html').send(html);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
this.app.post('/command', (req, res) => {
|
|
205
|
+
void (async () => {
|
|
206
|
+
const command = req.body as Command;
|
|
207
|
+
|
|
208
|
+
if (!this.commandHandlers.has(command.type)) {
|
|
209
|
+
res.status(404).json({
|
|
210
|
+
status: 'nack',
|
|
211
|
+
error: `Command handler not found: ${command.type}`,
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const requestId = command.requestId ?? `req-${nanoid()}`;
|
|
217
|
+
const correlationId = command.correlationId ?? `corr-${nanoid()}`;
|
|
218
|
+
const commandWithIds: Command & { correlationId: string; requestId: string } = {
|
|
219
|
+
...command,
|
|
220
|
+
requestId,
|
|
221
|
+
correlationId,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
await this.messageStore.saveMessage('$all', commandWithIds, undefined, 'command');
|
|
225
|
+
|
|
226
|
+
void this.processCommand(commandWithIds);
|
|
227
|
+
|
|
228
|
+
res.json({
|
|
229
|
+
status: 'ack',
|
|
230
|
+
commandId: commandWithIds.requestId,
|
|
231
|
+
timestamp: new Date().toISOString(),
|
|
232
|
+
});
|
|
233
|
+
})();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
this.app.get('/messages', (_req, res) => {
|
|
237
|
+
void (async () => {
|
|
238
|
+
const messages = await this.messageStore.getMessages('$all');
|
|
239
|
+
const serialized = messages.map((m) => ({
|
|
240
|
+
...m,
|
|
241
|
+
revision: m.revision.toString(),
|
|
242
|
+
position: m.position.toString(),
|
|
243
|
+
}));
|
|
244
|
+
res.json(serialized);
|
|
245
|
+
})();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
this.app.get('/stats', (_req, res) => {
|
|
249
|
+
void (async () => {
|
|
250
|
+
const stats = await this.messageStore.getStats();
|
|
251
|
+
res.json(stats);
|
|
252
|
+
})();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
this.app.get('/sessions', (_req, res) => {
|
|
256
|
+
void (async () => {
|
|
257
|
+
const sessions = await this.messageStore.getSessions();
|
|
258
|
+
res.json(sessions);
|
|
259
|
+
})();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
this.app.get('/events', (req, res) => {
|
|
263
|
+
const clientId = `sse-${nanoid()}`;
|
|
264
|
+
const correlationIdFilter = req.query.correlationId as string | undefined;
|
|
265
|
+
this.sseManager.addClient(clientId, res, correlationIdFilter);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private buildCombinedGraph(): GraphIR {
|
|
270
|
+
const combinedGraph: GraphIR = { nodes: [], edges: [] };
|
|
271
|
+
const nodeSet = new Set<string>();
|
|
272
|
+
|
|
273
|
+
for (const pipeline of this.pipelines.values()) {
|
|
274
|
+
const graph = pipeline.toGraph();
|
|
275
|
+
for (const node of graph.nodes) {
|
|
276
|
+
if (!nodeSet.has(node.id)) {
|
|
277
|
+
nodeSet.add(node.id);
|
|
278
|
+
combinedGraph.nodes.push(node);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
combinedGraph.edges.push(...graph.edges);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return combinedGraph;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private buildCommandToEvents(): Record<string, string[]> {
|
|
288
|
+
const commandToEvents: Record<string, string[]> = {};
|
|
289
|
+
for (const [name, handler] of this.commandHandlers.entries()) {
|
|
290
|
+
if (handler.events !== undefined && Array.isArray(handler.events)) {
|
|
291
|
+
commandToEvents[name] = handler.events;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return commandToEvents;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private buildEventToCommand(): Record<string, string> {
|
|
298
|
+
const eventToCommand: Record<string, string> = {};
|
|
299
|
+
for (const pipeline of this.pipelines.values()) {
|
|
300
|
+
for (const handler of pipeline.descriptor.handlers) {
|
|
301
|
+
if (handler.type === 'emit') {
|
|
302
|
+
for (const cmd of handler.commands) {
|
|
303
|
+
eventToCommand[handler.eventType] = cmd.commandType;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return eventToCommand;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private buildPipelineNodes(): Array<{ id: string; name: string; title: string; alias?: string; status: 'None' }> {
|
|
312
|
+
return Array.from(this.commandHandlers.entries()).map(([name, handler]) => ({
|
|
313
|
+
id: name,
|
|
314
|
+
name,
|
|
315
|
+
title: handler.description ?? '',
|
|
316
|
+
alias: handler.alias,
|
|
317
|
+
status: 'None' as const,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private buildMermaidDiagram(): string {
|
|
322
|
+
const graph = this.buildCombinedGraph();
|
|
323
|
+
const commandToEvents = this.buildCommandToEvents();
|
|
324
|
+
const pipelineEvents = this.extractPipelineEvents(graph, commandToEvents);
|
|
325
|
+
const lines: string[] = ['flowchart LR'];
|
|
326
|
+
|
|
327
|
+
const eventNodes = new Set<string>();
|
|
328
|
+
const commandNodes = new Set<string>();
|
|
329
|
+
const settledNodes = new Set<string>();
|
|
330
|
+
const edgeContext = { index: 0, backLinkIndices: [] as number[] };
|
|
331
|
+
|
|
332
|
+
this.addGraphNodesToMermaid(graph, lines, eventNodes, commandNodes, settledNodes);
|
|
333
|
+
const pipelineCommands = new Set(commandNodes);
|
|
334
|
+
this.addCommandEventNodesToMermaid(commandToEvents, pipelineCommands, pipelineEvents, lines, eventNodes);
|
|
335
|
+
this.addGraphEdgesToMermaid(graph, commandToEvents, lines, edgeContext);
|
|
336
|
+
this.addCommandEventEdgesToMermaid(commandToEvents, pipelineCommands, pipelineEvents, lines, edgeContext);
|
|
337
|
+
this.addMermaidStyles(lines, eventNodes, commandNodes, settledNodes, edgeContext.backLinkIndices);
|
|
338
|
+
|
|
339
|
+
return lines.join('\n');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private extractPipelineEvents(graph: GraphIR, commandToEvents: Record<string, string[]>): Set<string> {
|
|
343
|
+
const pipelineEvents = new Set<string>();
|
|
344
|
+
|
|
345
|
+
for (const node of graph.nodes) {
|
|
346
|
+
if (node.id.startsWith('evt:')) {
|
|
347
|
+
pipelineEvents.add(node.id.replace('evt:', ''));
|
|
348
|
+
}
|
|
349
|
+
if (node.id.startsWith('settled:')) {
|
|
350
|
+
const commandTypes = node.id.replace('settled:', '').split(',');
|
|
351
|
+
for (const commandType of commandTypes) {
|
|
352
|
+
const events = commandToEvents[commandType];
|
|
353
|
+
if (events !== undefined) {
|
|
354
|
+
for (const eventName of events) {
|
|
355
|
+
pipelineEvents.add(eventName);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return pipelineEvents;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private addGraphNodesToMermaid(
|
|
366
|
+
graph: GraphIR,
|
|
367
|
+
lines: string[],
|
|
368
|
+
eventNodes: Set<string>,
|
|
369
|
+
commandNodes: Set<string>,
|
|
370
|
+
settledNodes: Set<string>,
|
|
371
|
+
): void {
|
|
372
|
+
for (const node of graph.nodes) {
|
|
373
|
+
if (node.id.startsWith('evt:')) {
|
|
374
|
+
const eventName = node.id.replace('evt:', '');
|
|
375
|
+
const safeId = `evt_${eventName}`;
|
|
376
|
+
eventNodes.add(safeId);
|
|
377
|
+
lines.push(` ${safeId}([${eventName}])`);
|
|
378
|
+
} else if (node.id.startsWith('cmd:')) {
|
|
379
|
+
const commandName = node.id.replace('cmd:', '');
|
|
380
|
+
commandNodes.add(commandName);
|
|
381
|
+
lines.push(` ${commandName}[${commandName}]`);
|
|
382
|
+
} else if (node.id.startsWith('settled:')) {
|
|
383
|
+
const commandTypes = node.id.replace('settled:', '').split(',');
|
|
384
|
+
const safeId = `settled_${commandTypes.join('_')}`;
|
|
385
|
+
settledNodes.add(safeId);
|
|
386
|
+
lines.push(` ${safeId}{{${commandTypes.join(', ')}}}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private addCommandEventNodesToMermaid(
|
|
392
|
+
commandToEvents: Record<string, string[]>,
|
|
393
|
+
pipelineCommands: Set<string>,
|
|
394
|
+
pipelineEvents: Set<string>,
|
|
395
|
+
lines: string[],
|
|
396
|
+
eventNodes: Set<string>,
|
|
397
|
+
): void {
|
|
398
|
+
for (const [commandName, events] of Object.entries(commandToEvents)) {
|
|
399
|
+
if (!pipelineCommands.has(commandName)) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
for (const eventName of events) {
|
|
403
|
+
if (!pipelineEvents.has(eventName)) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const safeEventId = `evt_${eventName}`;
|
|
407
|
+
if (!eventNodes.has(safeEventId)) {
|
|
408
|
+
eventNodes.add(safeEventId);
|
|
409
|
+
lines.push(` ${safeEventId}([${eventName}])`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private addGraphEdgesToMermaid(
|
|
416
|
+
graph: GraphIR,
|
|
417
|
+
commandToEvents: Record<string, string[]>,
|
|
418
|
+
lines: string[],
|
|
419
|
+
edgeContext: { index: number; backLinkIndices: number[] },
|
|
420
|
+
): void {
|
|
421
|
+
for (const edge of graph.edges) {
|
|
422
|
+
if (edge.from.startsWith('cmd:') && edge.to.startsWith('settled:')) {
|
|
423
|
+
const commandType = edge.from.replace('cmd:', '');
|
|
424
|
+
const commandTypes = edge.to.replace('settled:', '').split(',');
|
|
425
|
+
const settledId = `settled_${commandTypes.join('_')}`;
|
|
426
|
+
const events = commandToEvents[commandType];
|
|
427
|
+
if (events !== undefined) {
|
|
428
|
+
for (const eventName of events) {
|
|
429
|
+
lines.push(` evt_${eventName} --> ${settledId}`);
|
|
430
|
+
edgeContext.index++;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const from = this.normalizeNodeId(edge.from);
|
|
436
|
+
const to = this.normalizeNodeId(edge.to);
|
|
437
|
+
lines.push(` ${from} --> ${to}`);
|
|
438
|
+
if (edge.backLink === true) {
|
|
439
|
+
edgeContext.backLinkIndices.push(edgeContext.index);
|
|
440
|
+
}
|
|
441
|
+
edgeContext.index++;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private normalizeNodeId(nodeId: string): string {
|
|
446
|
+
if (nodeId.startsWith('evt:')) {
|
|
447
|
+
return `evt_${nodeId.replace('evt:', '')}`;
|
|
448
|
+
}
|
|
449
|
+
if (nodeId.startsWith('cmd:')) {
|
|
450
|
+
return nodeId.replace('cmd:', '');
|
|
451
|
+
}
|
|
452
|
+
const commandTypes = nodeId.replace('settled:', '').split(',');
|
|
453
|
+
return `settled_${commandTypes.join('_')}`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private addCommandEventEdgesToMermaid(
|
|
457
|
+
commandToEvents: Record<string, string[]>,
|
|
458
|
+
pipelineCommands: Set<string>,
|
|
459
|
+
pipelineEvents: Set<string>,
|
|
460
|
+
lines: string[],
|
|
461
|
+
edgeContext: { index: number; backLinkIndices: number[] },
|
|
462
|
+
): void {
|
|
463
|
+
for (const [commandName, events] of Object.entries(commandToEvents)) {
|
|
464
|
+
if (!pipelineCommands.has(commandName)) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
for (const eventName of events) {
|
|
468
|
+
if (!pipelineEvents.has(eventName)) {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
lines.push(` ${commandName} --> evt_${eventName}`);
|
|
472
|
+
edgeContext.index++;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private addMermaidStyles(
|
|
478
|
+
lines: string[],
|
|
479
|
+
eventNodes: Set<string>,
|
|
480
|
+
commandNodes: Set<string>,
|
|
481
|
+
settledNodes: Set<string>,
|
|
482
|
+
backLinkIndices: number[],
|
|
483
|
+
): void {
|
|
484
|
+
const failedEvents = [...eventNodes].filter((id) => id.toLowerCase().includes('failed'));
|
|
485
|
+
const normalEvents = [...eventNodes].filter((id) => !id.toLowerCase().includes('failed'));
|
|
486
|
+
|
|
487
|
+
lines.push('');
|
|
488
|
+
lines.push(' classDef event fill:#fff3e0,stroke:#e65100');
|
|
489
|
+
lines.push(' classDef eventFailed fill:#fff3e0,stroke:#e65100,color:#d32f2f');
|
|
490
|
+
lines.push(' classDef command fill:#e3f2fd,stroke:#1565c0');
|
|
491
|
+
lines.push(' classDef settled fill:#f3e5f5,stroke:#7b1fa2');
|
|
492
|
+
|
|
493
|
+
if (normalEvents.length > 0) {
|
|
494
|
+
lines.push(` class ${normalEvents.join(',')} event`);
|
|
495
|
+
}
|
|
496
|
+
if (failedEvents.length > 0) {
|
|
497
|
+
lines.push(` class ${failedEvents.join(',')} eventFailed`);
|
|
498
|
+
}
|
|
499
|
+
if (commandNodes.size > 0) {
|
|
500
|
+
lines.push(` class ${[...commandNodes].join(',')} command`);
|
|
501
|
+
}
|
|
502
|
+
if (settledNodes.size > 0) {
|
|
503
|
+
lines.push(` class ${[...settledNodes].join(',')} settled`);
|
|
504
|
+
}
|
|
505
|
+
if (backLinkIndices.length > 0) {
|
|
506
|
+
lines.push(` linkStyle ${backLinkIndices.join(',')} stroke:#d32f2f,stroke-width:2px`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private async processCommand(command: Command & { correlationId: string; requestId: string }): Promise<void> {
|
|
511
|
+
const handler = this.commandHandlers.get(command.type);
|
|
512
|
+
if (!handler) return;
|
|
513
|
+
|
|
514
|
+
this.settledTracker.onCommandStarted(command);
|
|
515
|
+
|
|
516
|
+
const resultEvent = await handler.handle(command);
|
|
517
|
+
const events = Array.isArray(resultEvent) ? resultEvent : [resultEvent];
|
|
518
|
+
|
|
519
|
+
for (const event of events) {
|
|
520
|
+
const eventWithIds: EventWithCorrelation = {
|
|
521
|
+
...event,
|
|
522
|
+
correlationId: command.correlationId,
|
|
523
|
+
requestId: command.requestId,
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
await this.messageStore.saveMessage('$all', eventWithIds, undefined, 'event');
|
|
527
|
+
|
|
528
|
+
this.sseManager.broadcast(eventWithIds);
|
|
529
|
+
|
|
530
|
+
const sourceCommand = this.eventCommandMapper.getSourceCommand(event.type);
|
|
531
|
+
if (sourceCommand !== undefined) {
|
|
532
|
+
this.settledTracker.onEventReceived(eventWithIds, sourceCommand);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
this.routeEventToPhasedExecutor(eventWithIds);
|
|
536
|
+
|
|
537
|
+
await this.routeEventToPipelines(eventWithIds);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private async dispatchFromSettled(commandType: string, data: unknown, correlationId: string): Promise<void> {
|
|
542
|
+
const command: Command & { correlationId: string; requestId: string } = {
|
|
543
|
+
type: commandType,
|
|
544
|
+
data: data as Record<string, unknown>,
|
|
545
|
+
correlationId,
|
|
546
|
+
requestId: `req-${nanoid()}`,
|
|
547
|
+
};
|
|
548
|
+
await this.messageStore.saveMessage('$all', command, undefined, 'command');
|
|
549
|
+
await this.processCommand(command);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private async handlePhasedComplete(event: Event, correlationId: string): Promise<void> {
|
|
553
|
+
const eventWithIds: EventWithCorrelation = {
|
|
554
|
+
...event,
|
|
555
|
+
correlationId,
|
|
556
|
+
};
|
|
557
|
+
await this.messageStore.saveMessage('$all', eventWithIds, undefined, 'event');
|
|
558
|
+
this.sseManager.broadcast(eventWithIds);
|
|
559
|
+
await this.routeEventToPipelines(eventWithIds);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private async routeEventToPipelines(event: EventWithCorrelation): Promise<void> {
|
|
563
|
+
const ctx = this.createContext(event.correlationId);
|
|
564
|
+
|
|
565
|
+
for (const runtime of this.runtimes.values()) {
|
|
566
|
+
await runtime.handleEvent(event, ctx);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private createContext(correlationId: string): PipelineContext {
|
|
571
|
+
return {
|
|
572
|
+
correlationId,
|
|
573
|
+
emit: async (type: string, data: unknown) => {
|
|
574
|
+
const event: EventWithCorrelation = {
|
|
575
|
+
type,
|
|
576
|
+
data: data as Record<string, unknown>,
|
|
577
|
+
correlationId,
|
|
578
|
+
};
|
|
579
|
+
await this.messageStore.saveMessage('$all', event, undefined, 'event');
|
|
580
|
+
this.sseManager.broadcast(event);
|
|
581
|
+
await this.routeEventToPipelines(event);
|
|
582
|
+
},
|
|
583
|
+
sendCommand: async (type: string, data: unknown) => {
|
|
584
|
+
const command: Command & { correlationId: string; requestId: string } = {
|
|
585
|
+
type,
|
|
586
|
+
data: data as Record<string, unknown>,
|
|
587
|
+
correlationId,
|
|
588
|
+
requestId: `req-${nanoid()}`,
|
|
589
|
+
};
|
|
590
|
+
await this.messageStore.saveMessage('$all', command, undefined, 'command');
|
|
591
|
+
await this.processCommand(command);
|
|
592
|
+
},
|
|
593
|
+
startPhased: (handler, event) => {
|
|
594
|
+
this.phasedExecutor.startPhased(handler, event, correlationId);
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
private routeEventToPhasedExecutor(event: EventWithCorrelation): void {
|
|
600
|
+
for (const pipeline of this.pipelines.values()) {
|
|
601
|
+
for (const handler of pipeline.descriptor.handlers) {
|
|
602
|
+
if (handler.type === 'foreach-phased') {
|
|
603
|
+
const itemKey = handler.completion.itemKey(event);
|
|
604
|
+
this.phasedExecutor.onEventReceived(event, itemKey);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private buildDiagramHtml(mermaidDefinition: string): string {
|
|
611
|
+
return `<!DOCTYPE html>
|
|
612
|
+
<html lang="en">
|
|
613
|
+
<head>
|
|
614
|
+
<meta charset="UTF-8">
|
|
615
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
616
|
+
<title>Pipeline Diagram</title>
|
|
617
|
+
<script type="module">
|
|
618
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
|
619
|
+
mermaid.initialize({ startOnLoad: true, theme: 'default' });
|
|
620
|
+
</script>
|
|
621
|
+
<style>
|
|
622
|
+
body {
|
|
623
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
624
|
+
margin: 0;
|
|
625
|
+
padding: 20px;
|
|
626
|
+
background: #f5f5f5;
|
|
627
|
+
}
|
|
628
|
+
.container {
|
|
629
|
+
max-width: 1200px;
|
|
630
|
+
margin: 0 auto;
|
|
631
|
+
background: white;
|
|
632
|
+
padding: 20px;
|
|
633
|
+
border-radius: 8px;
|
|
634
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
635
|
+
}
|
|
636
|
+
h1 {
|
|
637
|
+
margin-top: 0;
|
|
638
|
+
color: #333;
|
|
639
|
+
}
|
|
640
|
+
.mermaid {
|
|
641
|
+
display: flex;
|
|
642
|
+
justify-content: center;
|
|
643
|
+
}
|
|
644
|
+
</style>
|
|
645
|
+
</head>
|
|
646
|
+
<body>
|
|
647
|
+
<div class="container">
|
|
648
|
+
<h1>Pipeline Diagram</h1>
|
|
649
|
+
<div class="mermaid">
|
|
650
|
+
${mermaidDefinition}
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
</body>
|
|
654
|
+
</html>`;
|
|
655
|
+
}
|
|
656
|
+
}
|