@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.
Files changed (270) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/LICENSE +10 -0
  3. package/claude.md +160 -0
  4. package/dist/src/builder/define.d.ts +90 -0
  5. package/dist/src/builder/define.d.ts.map +1 -0
  6. package/dist/src/builder/define.js +425 -0
  7. package/dist/src/builder/define.js.map +1 -0
  8. package/dist/src/builder/define.specs.d.ts +2 -0
  9. package/dist/src/builder/define.specs.d.ts.map +1 -0
  10. package/dist/src/builder/define.specs.js +435 -0
  11. package/dist/src/builder/define.specs.js.map +1 -0
  12. package/dist/src/config/pipeline-config.d.ts +13 -0
  13. package/dist/src/config/pipeline-config.d.ts.map +1 -0
  14. package/dist/src/config/pipeline-config.js +15 -0
  15. package/dist/src/config/pipeline-config.js.map +1 -0
  16. package/dist/src/core/descriptors.d.ts +84 -0
  17. package/dist/src/core/descriptors.d.ts.map +1 -0
  18. package/dist/src/core/descriptors.js +2 -0
  19. package/dist/src/core/descriptors.js.map +1 -0
  20. package/dist/src/core/descriptors.specs.d.ts +2 -0
  21. package/dist/src/core/descriptors.specs.d.ts.map +1 -0
  22. package/dist/src/core/descriptors.specs.js +24 -0
  23. package/dist/src/core/descriptors.specs.js.map +1 -0
  24. package/dist/src/core/types.d.ts +10 -0
  25. package/dist/src/core/types.d.ts.map +1 -0
  26. package/dist/src/core/types.js +4 -0
  27. package/dist/src/core/types.js.map +1 -0
  28. package/dist/src/core/types.specs.d.ts +2 -0
  29. package/dist/src/core/types.specs.d.ts.map +1 -0
  30. package/dist/src/core/types.specs.js +40 -0
  31. package/dist/src/core/types.specs.js.map +1 -0
  32. package/dist/src/graph/types.d.ts +17 -0
  33. package/dist/src/graph/types.d.ts.map +1 -0
  34. package/dist/src/graph/types.js +2 -0
  35. package/dist/src/graph/types.js.map +1 -0
  36. package/dist/src/graph/types.specs.d.ts +2 -0
  37. package/dist/src/graph/types.specs.d.ts.map +1 -0
  38. package/dist/src/graph/types.specs.js +148 -0
  39. package/dist/src/graph/types.specs.js.map +1 -0
  40. package/dist/src/index.d.ts +20 -0
  41. package/dist/src/index.d.ts.map +1 -0
  42. package/dist/src/index.js +12 -0
  43. package/dist/src/index.js.map +1 -0
  44. package/dist/src/logging/event-logger.d.ts +21 -0
  45. package/dist/src/logging/event-logger.d.ts.map +1 -0
  46. package/dist/src/logging/event-logger.js +31 -0
  47. package/dist/src/logging/event-logger.js.map +1 -0
  48. package/dist/src/logging/event-logger.specs.d.ts +2 -0
  49. package/dist/src/logging/event-logger.specs.d.ts.map +1 -0
  50. package/dist/src/logging/event-logger.specs.js +81 -0
  51. package/dist/src/logging/event-logger.specs.js.map +1 -0
  52. package/dist/src/plugins/handler-adapter.d.ts +5 -0
  53. package/dist/src/plugins/handler-adapter.d.ts.map +1 -0
  54. package/dist/src/plugins/handler-adapter.js +17 -0
  55. package/dist/src/plugins/handler-adapter.js.map +1 -0
  56. package/dist/src/plugins/handler-adapter.specs.d.ts +2 -0
  57. package/dist/src/plugins/handler-adapter.specs.d.ts.map +1 -0
  58. package/dist/src/plugins/handler-adapter.specs.js +129 -0
  59. package/dist/src/plugins/handler-adapter.specs.js.map +1 -0
  60. package/dist/src/plugins/plugin-loader.d.ts +25 -0
  61. package/dist/src/plugins/plugin-loader.d.ts.map +1 -0
  62. package/dist/src/plugins/plugin-loader.js +150 -0
  63. package/dist/src/plugins/plugin-loader.js.map +1 -0
  64. package/dist/src/plugins/plugin-loader.specs.d.ts +2 -0
  65. package/dist/src/plugins/plugin-loader.specs.d.ts.map +1 -0
  66. package/dist/src/plugins/plugin-loader.specs.js +246 -0
  67. package/dist/src/plugins/plugin-loader.specs.js.map +1 -0
  68. package/dist/src/runtime/await-tracker.d.ts +10 -0
  69. package/dist/src/runtime/await-tracker.d.ts.map +1 -0
  70. package/dist/src/runtime/await-tracker.js +42 -0
  71. package/dist/src/runtime/await-tracker.js.map +1 -0
  72. package/dist/src/runtime/await-tracker.specs.d.ts +2 -0
  73. package/dist/src/runtime/await-tracker.specs.d.ts.map +1 -0
  74. package/dist/src/runtime/await-tracker.specs.js +46 -0
  75. package/dist/src/runtime/await-tracker.specs.js.map +1 -0
  76. package/dist/src/runtime/context.d.ts +12 -0
  77. package/dist/src/runtime/context.d.ts.map +1 -0
  78. package/dist/src/runtime/context.js +2 -0
  79. package/dist/src/runtime/context.js.map +1 -0
  80. package/dist/src/runtime/context.specs.d.ts +2 -0
  81. package/dist/src/runtime/context.specs.d.ts.map +1 -0
  82. package/dist/src/runtime/context.specs.js +26 -0
  83. package/dist/src/runtime/context.specs.js.map +1 -0
  84. package/dist/src/runtime/event-command-map.d.ts +15 -0
  85. package/dist/src/runtime/event-command-map.d.ts.map +1 -0
  86. package/dist/src/runtime/event-command-map.js +26 -0
  87. package/dist/src/runtime/event-command-map.js.map +1 -0
  88. package/dist/src/runtime/event-command-map.specs.d.ts +2 -0
  89. package/dist/src/runtime/event-command-map.specs.d.ts.map +1 -0
  90. package/dist/src/runtime/event-command-map.specs.js +108 -0
  91. package/dist/src/runtime/event-command-map.specs.js.map +1 -0
  92. package/dist/src/runtime/phased-executor.d.ts +29 -0
  93. package/dist/src/runtime/phased-executor.d.ts.map +1 -0
  94. package/dist/src/runtime/phased-executor.js +164 -0
  95. package/dist/src/runtime/phased-executor.js.map +1 -0
  96. package/dist/src/runtime/phased-executor.specs.d.ts +2 -0
  97. package/dist/src/runtime/phased-executor.specs.d.ts.map +1 -0
  98. package/dist/src/runtime/phased-executor.specs.js +256 -0
  99. package/dist/src/runtime/phased-executor.specs.js.map +1 -0
  100. package/dist/src/runtime/pipeline-runtime.d.ts +17 -0
  101. package/dist/src/runtime/pipeline-runtime.d.ts.map +1 -0
  102. package/dist/src/runtime/pipeline-runtime.js +87 -0
  103. package/dist/src/runtime/pipeline-runtime.js.map +1 -0
  104. package/dist/src/runtime/pipeline-runtime.specs.d.ts +2 -0
  105. package/dist/src/runtime/pipeline-runtime.specs.d.ts.map +1 -0
  106. package/dist/src/runtime/pipeline-runtime.specs.js +192 -0
  107. package/dist/src/runtime/pipeline-runtime.specs.js.map +1 -0
  108. package/dist/src/runtime/settled-tracker.d.ts +42 -0
  109. package/dist/src/runtime/settled-tracker.d.ts.map +1 -0
  110. package/dist/src/runtime/settled-tracker.js +161 -0
  111. package/dist/src/runtime/settled-tracker.js.map +1 -0
  112. package/dist/src/runtime/settled-tracker.specs.d.ts +2 -0
  113. package/dist/src/runtime/settled-tracker.specs.d.ts.map +1 -0
  114. package/dist/src/runtime/settled-tracker.specs.js +361 -0
  115. package/dist/src/runtime/settled-tracker.specs.js.map +1 -0
  116. package/dist/src/server/full-orchestration.e2e.specs.d.ts +2 -0
  117. package/dist/src/server/full-orchestration.e2e.specs.d.ts.map +1 -0
  118. package/dist/src/server/full-orchestration.e2e.specs.js +561 -0
  119. package/dist/src/server/full-orchestration.e2e.specs.js.map +1 -0
  120. package/dist/src/server/pipeline-server.d.ts +59 -0
  121. package/dist/src/server/pipeline-server.d.ts.map +1 -0
  122. package/dist/src/server/pipeline-server.e2e.specs.d.ts +2 -0
  123. package/dist/src/server/pipeline-server.e2e.specs.d.ts.map +1 -0
  124. package/dist/src/server/pipeline-server.e2e.specs.js +381 -0
  125. package/dist/src/server/pipeline-server.e2e.specs.js.map +1 -0
  126. package/dist/src/server/pipeline-server.js +527 -0
  127. package/dist/src/server/pipeline-server.js.map +1 -0
  128. package/dist/src/server/pipeline-server.specs.d.ts +2 -0
  129. package/dist/src/server/pipeline-server.specs.d.ts.map +1 -0
  130. package/dist/src/server/pipeline-server.specs.js +662 -0
  131. package/dist/src/server/pipeline-server.specs.js.map +1 -0
  132. package/dist/src/server/sse-manager.d.ts +12 -0
  133. package/dist/src/server/sse-manager.d.ts.map +1 -0
  134. package/dist/src/server/sse-manager.js +63 -0
  135. package/dist/src/server/sse-manager.js.map +1 -0
  136. package/dist/src/server/sse-manager.specs.d.ts +2 -0
  137. package/dist/src/server/sse-manager.specs.d.ts.map +1 -0
  138. package/dist/src/server/sse-manager.specs.js +158 -0
  139. package/dist/src/server/sse-manager.specs.js.map +1 -0
  140. package/dist/src/testing/event-capture.d.ts +14 -0
  141. package/dist/src/testing/event-capture.d.ts.map +1 -0
  142. package/dist/src/testing/event-capture.js +55 -0
  143. package/dist/src/testing/event-capture.js.map +1 -0
  144. package/dist/src/testing/event-capture.specs.d.ts +2 -0
  145. package/dist/src/testing/event-capture.specs.d.ts.map +1 -0
  146. package/dist/src/testing/event-capture.specs.js +114 -0
  147. package/dist/src/testing/event-capture.specs.js.map +1 -0
  148. package/dist/src/testing/fixtures/kanban-full.pipeline.d.ts +7 -0
  149. package/dist/src/testing/fixtures/kanban-full.pipeline.d.ts.map +1 -0
  150. package/dist/src/testing/fixtures/kanban-full.pipeline.js +168 -0
  151. package/dist/src/testing/fixtures/kanban-full.pipeline.js.map +1 -0
  152. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts +2 -0
  153. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.d.ts.map +1 -0
  154. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js +263 -0
  155. package/dist/src/testing/fixtures/kanban-full.pipeline.specs.js.map +1 -0
  156. package/dist/src/testing/fixtures/kanban-todo.config.d.ts +3 -0
  157. package/dist/src/testing/fixtures/kanban-todo.config.d.ts.map +1 -0
  158. package/dist/src/testing/fixtures/kanban-todo.config.js +19 -0
  159. package/dist/src/testing/fixtures/kanban-todo.config.js.map +1 -0
  160. package/dist/src/testing/fixtures/kanban.pipeline.d.ts +5 -0
  161. package/dist/src/testing/fixtures/kanban.pipeline.d.ts.map +1 -0
  162. package/dist/src/testing/fixtures/kanban.pipeline.js +76 -0
  163. package/dist/src/testing/fixtures/kanban.pipeline.js.map +1 -0
  164. package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts +2 -0
  165. package/dist/src/testing/fixtures/kanban.pipeline.specs.d.ts.map +1 -0
  166. package/dist/src/testing/fixtures/kanban.pipeline.specs.js +29 -0
  167. package/dist/src/testing/fixtures/kanban.pipeline.specs.js.map +1 -0
  168. package/dist/src/testing/kanban-todo.e2e.specs.d.ts +2 -0
  169. package/dist/src/testing/kanban-todo.e2e.specs.d.ts.map +1 -0
  170. package/dist/src/testing/kanban-todo.e2e.specs.js +160 -0
  171. package/dist/src/testing/kanban-todo.e2e.specs.js.map +1 -0
  172. package/dist/src/testing/mock-handlers.d.ts +21 -0
  173. package/dist/src/testing/mock-handlers.d.ts.map +1 -0
  174. package/dist/src/testing/mock-handlers.js +34 -0
  175. package/dist/src/testing/mock-handlers.js.map +1 -0
  176. package/dist/src/testing/mock-handlers.specs.d.ts +2 -0
  177. package/dist/src/testing/mock-handlers.specs.d.ts.map +1 -0
  178. package/dist/src/testing/mock-handlers.specs.js +193 -0
  179. package/dist/src/testing/mock-handlers.specs.js.map +1 -0
  180. package/dist/src/testing/real-execution.e2e.specs.d.ts +2 -0
  181. package/dist/src/testing/real-execution.e2e.specs.d.ts.map +1 -0
  182. package/dist/src/testing/real-execution.e2e.specs.js +140 -0
  183. package/dist/src/testing/real-execution.e2e.specs.js.map +1 -0
  184. package/dist/src/testing/real-plugin.e2e.specs.d.ts +2 -0
  185. package/dist/src/testing/real-plugin.e2e.specs.d.ts.map +1 -0
  186. package/dist/src/testing/real-plugin.e2e.specs.js +65 -0
  187. package/dist/src/testing/real-plugin.e2e.specs.js.map +1 -0
  188. package/dist/src/testing/server-startup.e2e.specs.d.ts +2 -0
  189. package/dist/src/testing/server-startup.e2e.specs.d.ts.map +1 -0
  190. package/dist/src/testing/server-startup.e2e.specs.js +104 -0
  191. package/dist/src/testing/server-startup.e2e.specs.js.map +1 -0
  192. package/dist/src/testing/snapshot-compare.d.ts +18 -0
  193. package/dist/src/testing/snapshot-compare.d.ts.map +1 -0
  194. package/dist/src/testing/snapshot-compare.js +86 -0
  195. package/dist/src/testing/snapshot-compare.js.map +1 -0
  196. package/dist/src/testing/snapshot-compare.specs.d.ts +2 -0
  197. package/dist/src/testing/snapshot-compare.specs.d.ts.map +1 -0
  198. package/dist/src/testing/snapshot-compare.specs.js +112 -0
  199. package/dist/src/testing/snapshot-compare.specs.js.map +1 -0
  200. package/dist/src/testing/snapshot-sanitize.d.ts +8 -0
  201. package/dist/src/testing/snapshot-sanitize.d.ts.map +1 -0
  202. package/dist/src/testing/snapshot-sanitize.js +10 -0
  203. package/dist/src/testing/snapshot-sanitize.js.map +1 -0
  204. package/dist/src/testing/snapshot-sanitize.specs.d.ts +2 -0
  205. package/dist/src/testing/snapshot-sanitize.specs.d.ts.map +1 -0
  206. package/dist/src/testing/snapshot-sanitize.specs.js +104 -0
  207. package/dist/src/testing/snapshot-sanitize.specs.js.map +1 -0
  208. package/dist/tsconfig.tsbuildinfo +1 -0
  209. package/docs/testing-analysis.md +395 -0
  210. package/package.json +31 -0
  211. package/pipeline-api-new.md +1078 -0
  212. package/pomodoro-plan.md +651 -0
  213. package/scripts/run-kanban-e2e.ts +219 -0
  214. package/scripts/start-server.ts +64 -0
  215. package/snapshots/e2e-run-2025-12-22T15-52-03.json +613 -0
  216. package/snapshots/e2e-run-2025-12-22T16-51-30.json +699 -0
  217. package/src/builder/define.specs.ts +531 -0
  218. package/src/builder/define.ts +700 -0
  219. package/src/config/pipeline-config.ts +32 -0
  220. package/src/core/descriptors.specs.ts +28 -0
  221. package/src/core/descriptors.ts +99 -0
  222. package/src/core/types.specs.ts +44 -0
  223. package/src/core/types.ts +16 -0
  224. package/src/graph/types.specs.ts +176 -0
  225. package/src/graph/types.ts +19 -0
  226. package/src/index.ts +54 -0
  227. package/src/logging/event-logger.specs.ts +100 -0
  228. package/src/logging/event-logger.ts +50 -0
  229. package/src/plugins/handler-adapter.specs.ts +164 -0
  230. package/src/plugins/handler-adapter.ts +21 -0
  231. package/src/plugins/plugin-loader.specs.ts +295 -0
  232. package/src/plugins/plugin-loader.ts +202 -0
  233. package/src/runtime/await-tracker.specs.ts +52 -0
  234. package/src/runtime/await-tracker.ts +50 -0
  235. package/src/runtime/context.specs.ts +28 -0
  236. package/src/runtime/context.ts +13 -0
  237. package/src/runtime/event-command-map.specs.ts +136 -0
  238. package/src/runtime/event-command-map.ts +38 -0
  239. package/src/runtime/phased-executor.specs.ts +358 -0
  240. package/src/runtime/phased-executor.ts +224 -0
  241. package/src/runtime/pipeline-runtime.specs.ts +214 -0
  242. package/src/runtime/pipeline-runtime.ts +119 -0
  243. package/src/runtime/settled-tracker.specs.ts +448 -0
  244. package/src/runtime/settled-tracker.ts +237 -0
  245. package/src/server/full-orchestration.e2e.specs.ts +672 -0
  246. package/src/server/pipeline-server.e2e.specs.ts +505 -0
  247. package/src/server/pipeline-server.specs.ts +761 -0
  248. package/src/server/pipeline-server.ts +656 -0
  249. package/src/server/sse-manager.specs.ts +208 -0
  250. package/src/server/sse-manager.ts +79 -0
  251. package/src/testing/event-capture.specs.ts +143 -0
  252. package/src/testing/event-capture.ts +65 -0
  253. package/src/testing/fixtures/kanban-full.pipeline.specs.ts +337 -0
  254. package/src/testing/fixtures/kanban-full.pipeline.ts +225 -0
  255. package/src/testing/fixtures/kanban-todo.config.ts +19 -0
  256. package/src/testing/fixtures/kanban.pipeline.specs.ts +33 -0
  257. package/src/testing/fixtures/kanban.pipeline.ts +124 -0
  258. package/src/testing/kanban-todo.e2e.specs.ts +209 -0
  259. package/src/testing/mock-handlers.specs.ts +229 -0
  260. package/src/testing/mock-handlers.ts +58 -0
  261. package/src/testing/real-execution.e2e.specs.ts +193 -0
  262. package/src/testing/real-plugin.e2e.specs.ts +94 -0
  263. package/src/testing/server-startup.e2e.specs.ts +162 -0
  264. package/src/testing/snapshot-compare.specs.ts +136 -0
  265. package/src/testing/snapshot-compare.ts +106 -0
  266. package/src/testing/snapshot-sanitize.specs.ts +131 -0
  267. package/src/testing/snapshot-sanitize.ts +17 -0
  268. package/tsconfig.json +11 -0
  269. package/tsconfig.test.json +9 -0
  270. package/vitest.config.ts +29 -0
@@ -0,0 +1,761 @@
1
+ import { define } from '../builder/define';
2
+ import { PipelineServer } from './pipeline-server';
3
+
4
+ interface HealthResponse {
5
+ status: string;
6
+ }
7
+
8
+ interface CommandMetadata {
9
+ id: string;
10
+ name: string;
11
+ alias: string;
12
+ description: string;
13
+ fields: Record<string, unknown>;
14
+ examples: unknown[];
15
+ }
16
+
17
+ interface RegistryResponse {
18
+ eventHandlers: string[];
19
+ commandHandlers: string[];
20
+ commandsWithMetadata: CommandMetadata[];
21
+ folds: string[];
22
+ }
23
+
24
+ interface GraphNode {
25
+ id: string;
26
+ type: string;
27
+ label: string;
28
+ }
29
+
30
+ interface PipelineNode {
31
+ id: string;
32
+ name: string;
33
+ title: string;
34
+ status: string;
35
+ }
36
+
37
+ interface PipelineResponse {
38
+ nodes: PipelineNode[];
39
+ edges: Array<{ from: string; to: string }>;
40
+ commandToEvents: Record<string, string[]>;
41
+ eventToCommand: Record<string, string>;
42
+ }
43
+
44
+ interface GraphResponse {
45
+ nodes: GraphNode[];
46
+ edges: Array<{ from: string; to: string }>;
47
+ }
48
+
49
+ interface CommandResponse {
50
+ status: string;
51
+ }
52
+
53
+ interface StoredMessage {
54
+ message: { type: string };
55
+ }
56
+
57
+ interface StatsResponse {
58
+ totalMessages: number;
59
+ }
60
+
61
+ async function fetchAs<T>(url: string, options?: RequestInit): Promise<T> {
62
+ const res = await fetch(url, options);
63
+ return res.json() as Promise<T>;
64
+ }
65
+
66
+ async function fetchWithStatus(
67
+ url: string,
68
+ options?: RequestInit,
69
+ ): Promise<{ status: number; json: <T>() => Promise<T> }> {
70
+ const res = await fetch(url, options);
71
+ return {
72
+ status: res.status,
73
+ json: <T>() => res.json() as Promise<T>,
74
+ };
75
+ }
76
+
77
+ describe('PipelineServer', () => {
78
+ describe('health endpoint', () => {
79
+ it('should respond to /health', async () => {
80
+ const server = new PipelineServer({ port: 0 });
81
+ await server.start();
82
+ const data = await fetchAs<HealthResponse>(`http://localhost:${server.port}/health`);
83
+ expect(data.status).toBe('healthy');
84
+ await server.stop();
85
+ });
86
+ });
87
+
88
+ describe('command handlers', () => {
89
+ it('should register command handlers', () => {
90
+ const handler = {
91
+ name: 'Cmd',
92
+ handle: async () => ({ type: 'Done', data: {} }),
93
+ };
94
+ const server = new PipelineServer({ port: 0 });
95
+ server.registerCommandHandlers([handler]);
96
+ expect(server.getRegisteredCommands()).toContain('Cmd');
97
+ });
98
+ });
99
+
100
+ describe('pipeline registration', () => {
101
+ it('should register pipeline', () => {
102
+ const pipeline = define('test').on('A').emit('B', {}).build();
103
+ const server = new PipelineServer({ port: 0 });
104
+ server.registerPipeline(pipeline);
105
+ expect(server.getPipelineNames()).toContain('test');
106
+ });
107
+ });
108
+
109
+ describe('GET /registry', () => {
110
+ it('should return registry with event handlers', async () => {
111
+ const pipeline = define('test').on('Start').emit('Cmd', {}).build();
112
+ const server = new PipelineServer({ port: 0 });
113
+ server.registerPipeline(pipeline);
114
+ await server.start();
115
+ const data = await fetchAs<RegistryResponse>(`http://localhost:${server.port}/registry`);
116
+ expect(data.eventHandlers).toContain('Start');
117
+ await server.stop();
118
+ });
119
+
120
+ it('should return registry with command metadata defaults', async () => {
121
+ const handler = {
122
+ name: 'MinimalCmd',
123
+ handle: async () => ({ type: 'Done', data: {} }),
124
+ };
125
+ const server = new PipelineServer({ port: 0 });
126
+ server.registerCommandHandlers([handler]);
127
+ await server.start();
128
+ const data = await fetchAs<RegistryResponse>(`http://localhost:${server.port}/registry`);
129
+ const metadata = data.commandsWithMetadata[0];
130
+ expect(metadata.alias).toBe('MinimalCmd');
131
+ expect(metadata.description).toBe('');
132
+ expect(metadata.fields).toEqual({});
133
+ expect(metadata.examples).toEqual([]);
134
+ await server.stop();
135
+ });
136
+
137
+ it('should return registry with command metadata', async () => {
138
+ const handler = {
139
+ name: 'Cmd',
140
+ alias: 'cmd',
141
+ description: 'Test',
142
+ fields: { x: 1 },
143
+ examples: ['ex'],
144
+ handle: async () => ({ type: 'Done', data: {} }),
145
+ };
146
+ const server = new PipelineServer({ port: 0 });
147
+ server.registerCommandHandlers([handler]);
148
+ await server.start();
149
+ const data = await fetchAs<RegistryResponse>(`http://localhost:${server.port}/registry`);
150
+ const metadata = data.commandsWithMetadata[0];
151
+ expect(metadata.alias).toBe('cmd');
152
+ expect(metadata.description).toBe('Test');
153
+ expect(metadata.fields).toEqual({ x: 1 });
154
+ expect(metadata.examples).toEqual(['ex']);
155
+ await server.stop();
156
+ });
157
+
158
+ it('should return registry with folds array', async () => {
159
+ const server = new PipelineServer({ port: 0 });
160
+ await server.start();
161
+ const data = await fetchAs<RegistryResponse>(`http://localhost:${server.port}/registry`);
162
+ expect(data.folds).toEqual([]);
163
+ await server.stop();
164
+ });
165
+ });
166
+
167
+ describe('GET /pipeline', () => {
168
+ it('should return pipeline graph', async () => {
169
+ const pipeline = define('test').on('Start').emit('Cmd', {}).build();
170
+ const server = new PipelineServer({ port: 0 });
171
+ server.registerPipeline(pipeline);
172
+ await server.start();
173
+ const data = await fetchAs<GraphResponse>(`http://localhost:${server.port}/pipeline`);
174
+ expect(data.nodes.some((n) => n.id === 'evt:Start')).toBe(true);
175
+ await server.stop();
176
+ });
177
+
178
+ it('should return pipeline response with commandToEvents', async () => {
179
+ const handler = {
180
+ name: 'Gen',
181
+ events: ['GenDone', 'GenProgress'],
182
+ handle: async () => ({ type: 'GenDone', data: {} }),
183
+ };
184
+ const server = new PipelineServer({ port: 0 });
185
+ server.registerCommandHandlers([handler]);
186
+ await server.start();
187
+ const data = await fetchAs<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
188
+ expect(data.commandToEvents).toEqual({ Gen: ['GenDone', 'GenProgress'] });
189
+ await server.stop();
190
+ });
191
+
192
+ it('should return pipeline response with eventToCommand', async () => {
193
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
194
+ const server = new PipelineServer({ port: 0 });
195
+ server.registerPipeline(pipeline);
196
+ await server.start();
197
+ const data = await fetchAs<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
198
+ expect(data.eventToCommand).toEqual({ Start: 'Process' });
199
+ await server.stop();
200
+ });
201
+
202
+ it('should return pipeline nodes with name, title, and status', async () => {
203
+ const handler = {
204
+ name: 'Cmd',
205
+ alias: 'cmd',
206
+ description: 'Test command',
207
+ handle: async () => ({ type: 'Done', data: {} }),
208
+ };
209
+ const server = new PipelineServer({ port: 0 });
210
+ server.registerCommandHandlers([handler]);
211
+ await server.start();
212
+ const data = await fetchAs<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
213
+ const cmdNode = data.nodes.find((n) => n.id === 'Cmd');
214
+ expect(cmdNode).toBeDefined();
215
+ expect(cmdNode?.name).toBe('Cmd');
216
+ expect(cmdNode?.title).toBe('Test command');
217
+ expect(cmdNode?.status).toBe('None');
218
+ await server.stop();
219
+ });
220
+ });
221
+
222
+ describe('POST /command', () => {
223
+ it('should accept command', async () => {
224
+ const handler = {
225
+ name: 'Cmd',
226
+ handle: async () => ({ type: 'Done', data: {} }),
227
+ };
228
+ const server = new PipelineServer({ port: 0 });
229
+ server.registerCommandHandlers([handler]);
230
+ await server.start();
231
+ const data = await fetchAs<CommandResponse>(`http://localhost:${server.port}/command`, {
232
+ method: 'POST',
233
+ headers: { 'Content-Type': 'application/json' },
234
+ body: JSON.stringify({ type: 'Cmd', data: {} }),
235
+ });
236
+ expect(data.status).toBe('ack');
237
+ await server.stop();
238
+ });
239
+
240
+ it('should return 404 for unknown command', async () => {
241
+ const server = new PipelineServer({ port: 0 });
242
+ await server.start();
243
+ const res = await fetchWithStatus(`http://localhost:${server.port}/command`, {
244
+ method: 'POST',
245
+ headers: { 'Content-Type': 'application/json' },
246
+ body: JSON.stringify({ type: 'UnknownCmd', data: {} }),
247
+ });
248
+ expect(res.status).toBe(404);
249
+ const data = await res.json<CommandResponse>();
250
+ expect(data.status).toBe('nack');
251
+ await server.stop();
252
+ });
253
+
254
+ it('should handle command that returns multiple events', async () => {
255
+ const handler = {
256
+ name: 'Multi',
257
+ handle: async () => [
258
+ { type: 'EventA', data: { a: 1 } },
259
+ { type: 'EventB', data: { b: 2 } },
260
+ ],
261
+ };
262
+ const server = new PipelineServer({ port: 0 });
263
+ server.registerCommandHandlers([handler]);
264
+ await server.start();
265
+ await fetch(`http://localhost:${server.port}/command`, {
266
+ method: 'POST',
267
+ headers: { 'Content-Type': 'application/json' },
268
+ body: JSON.stringify({ type: 'Multi', data: {} }),
269
+ });
270
+ await new Promise((r) => setTimeout(r, 100));
271
+ const msgs = await fetchAs<StoredMessage[]>(`http://localhost:${server.port}/messages`);
272
+ expect(msgs.some((m) => m.message.type === 'EventA')).toBe(true);
273
+ expect(msgs.some((m) => m.message.type === 'EventB')).toBe(true);
274
+ await server.stop();
275
+ });
276
+ });
277
+
278
+ describe('GET /messages', () => {
279
+ it('should return messages', async () => {
280
+ const server = new PipelineServer({ port: 0 });
281
+ await server.start();
282
+ const data = await fetchAs<StoredMessage[]>(`http://localhost:${server.port}/messages`);
283
+ expect(Array.isArray(data)).toBe(true);
284
+ await server.stop();
285
+ });
286
+ });
287
+
288
+ describe('GET /stats', () => {
289
+ it('should return stats', async () => {
290
+ const server = new PipelineServer({ port: 0 });
291
+ await server.start();
292
+ const data = await fetchAs<StatsResponse>(`http://localhost:${server.port}/stats`);
293
+ expect(data.totalMessages).toBeDefined();
294
+ await server.stop();
295
+ });
296
+ });
297
+
298
+ describe('GET /sessions', () => {
299
+ it('should return sessions', async () => {
300
+ const server = new PipelineServer({ port: 0 });
301
+ await server.start();
302
+ const data = await fetchAs<unknown[]>(`http://localhost:${server.port}/sessions`);
303
+ expect(Array.isArray(data)).toBe(true);
304
+ await server.stop();
305
+ });
306
+ });
307
+
308
+ describe('event routing', () => {
309
+ it('should route events through pipeline', async () => {
310
+ const handler = {
311
+ name: 'Init',
312
+ handle: async () => ({ type: 'Ready', data: {} }),
313
+ };
314
+ const pipeline = define('test').on('Ready').emit('Next', {}).build();
315
+ const server = new PipelineServer({ port: 0 });
316
+ server.registerCommandHandlers([handler]);
317
+ server.registerPipeline(pipeline);
318
+ await server.start();
319
+ await fetch(`http://localhost:${server.port}/command`, {
320
+ method: 'POST',
321
+ headers: { 'Content-Type': 'application/json' },
322
+ body: JSON.stringify({ type: 'Init', data: {} }),
323
+ });
324
+ await new Promise((r) => setTimeout(r, 100));
325
+ const msgs = await fetchAs<StoredMessage[]>(`http://localhost:${server.port}/messages`);
326
+ expect(msgs.some((m) => m.message.type === 'Next')).toBe(true);
327
+ await server.stop();
328
+ });
329
+
330
+ it('should handle custom handler that emits events', async () => {
331
+ const handler = {
332
+ name: 'Start',
333
+ handle: async () => ({ type: 'Started', data: {} }),
334
+ };
335
+ const pipeline = define('test')
336
+ .on('Started')
337
+ .handle(async (_e, ctx) => {
338
+ await ctx.emit('CustomEvent', { emitted: true });
339
+ })
340
+ .build();
341
+ const server = new PipelineServer({ port: 0 });
342
+ server.registerCommandHandlers([handler]);
343
+ server.registerPipeline(pipeline);
344
+ await server.start();
345
+ await fetch(`http://localhost:${server.port}/command`, {
346
+ method: 'POST',
347
+ headers: { 'Content-Type': 'application/json' },
348
+ body: JSON.stringify({ type: 'Start', data: {} }),
349
+ });
350
+ await new Promise((r) => setTimeout(r, 100));
351
+ const msgs = await fetchAs<StoredMessage[]>(`http://localhost:${server.port}/messages`);
352
+ expect(msgs.some((m) => m.message.type === 'CustomEvent')).toBe(true);
353
+ await server.stop();
354
+ });
355
+ });
356
+
357
+ describe('GET /pipeline/mermaid', () => {
358
+ it('should return mermaid diagram as text', async () => {
359
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
360
+ const server = new PipelineServer({ port: 0 });
361
+ server.registerPipeline(pipeline);
362
+ await server.start();
363
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
364
+ expect(res.headers.get('content-type')).toContain('text/plain');
365
+ const mermaid = await res.text();
366
+ expect(mermaid).toContain('flowchart LR');
367
+ await server.stop();
368
+ });
369
+
370
+ it('should include event nodes in mermaid diagram', async () => {
371
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
372
+ const server = new PipelineServer({ port: 0 });
373
+ server.registerPipeline(pipeline);
374
+ await server.start();
375
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
376
+ const mermaid = await res.text();
377
+ expect(mermaid).toContain('evt_Start');
378
+ await server.stop();
379
+ });
380
+
381
+ it('should include command nodes in mermaid diagram', async () => {
382
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
383
+ const server = new PipelineServer({ port: 0 });
384
+ server.registerPipeline(pipeline);
385
+ await server.start();
386
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
387
+ const mermaid = await res.text();
388
+ expect(mermaid).toContain('Process[Process]');
389
+ await server.stop();
390
+ });
391
+
392
+ it('should include edges in mermaid diagram', async () => {
393
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
394
+ const server = new PipelineServer({ port: 0 });
395
+ server.registerPipeline(pipeline);
396
+ await server.start();
397
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
398
+ const mermaid = await res.text();
399
+ expect(mermaid).toContain('-->');
400
+ await server.stop();
401
+ });
402
+
403
+ it('should style commands as blue and events as orange', async () => {
404
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
405
+ const server = new PipelineServer({ port: 0 });
406
+ server.registerPipeline(pipeline);
407
+ await server.start();
408
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
409
+ const mermaid = await res.text();
410
+ expect(mermaid).toContain('classDef event fill:#fff3e0,stroke:#e65100');
411
+ expect(mermaid).toContain('classDef command fill:#e3f2fd,stroke:#1565c0');
412
+ await server.stop();
413
+ });
414
+
415
+ it('should style failed events with red text', async () => {
416
+ const handler = {
417
+ name: 'Gen',
418
+ events: ['GenDone', 'GenFailed'],
419
+ handle: async () => ({ type: 'GenDone', data: {} }),
420
+ };
421
+ const retryHandler = {
422
+ name: 'Retry',
423
+ events: ['RetryDone'],
424
+ handle: async () => ({ type: 'RetryDone', data: {} }),
425
+ };
426
+ const pipeline = define('test').on('Start').emit('Gen', {}).on('GenFailed').emit('Retry', {}).build();
427
+ const server = new PipelineServer({ port: 0 });
428
+ server.registerCommandHandlers([handler, retryHandler]);
429
+ server.registerPipeline(pipeline);
430
+ await server.start();
431
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
432
+ const mermaid = await res.text();
433
+ expect(mermaid).toContain('classDef eventFailed fill:#fff3e0,stroke:#e65100,color:#d32f2f');
434
+ expect(mermaid).toContain('class evt_GenFailed eventFailed');
435
+ await server.stop();
436
+ });
437
+
438
+ it('should include edges from commands to their pipeline events only', async () => {
439
+ const handler = {
440
+ name: 'Gen',
441
+ events: ['GenDone', 'GenFailed'],
442
+ handle: async () => ({ type: 'GenDone', data: {} }),
443
+ };
444
+ const nextHandler = {
445
+ name: 'Next',
446
+ events: ['NextDone'],
447
+ handle: async () => ({ type: 'NextDone', data: {} }),
448
+ };
449
+ const pipeline = define('test').on('Start').emit('Gen', {}).on('GenDone').emit('Next', {}).build();
450
+ const server = new PipelineServer({ port: 0 });
451
+ server.registerCommandHandlers([handler, nextHandler]);
452
+ server.registerPipeline(pipeline);
453
+ await server.start();
454
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
455
+ const mermaid = await res.text();
456
+ expect(mermaid).toContain('Gen --> evt_GenDone');
457
+ expect(mermaid).not.toContain('GenFailed');
458
+ await server.stop();
459
+ });
460
+
461
+ it('should show complete flow from event to command with command events', async () => {
462
+ const genHandler = {
463
+ name: 'GenerateServer',
464
+ events: ['ServerGenerated', 'SliceGenerated'],
465
+ handle: async () => ({ type: 'ServerGenerated', data: {} }),
466
+ };
467
+ const iaHandler = {
468
+ name: 'GenerateIA',
469
+ events: ['IAGenerated'],
470
+ handle: async () => ({ type: 'IAGenerated', data: {} }),
471
+ };
472
+ const implHandler = {
473
+ name: 'ImplementSlice',
474
+ events: ['SliceImplemented'],
475
+ handle: async () => ({ type: 'SliceImplemented', data: {} }),
476
+ };
477
+ const pipeline = define('test')
478
+ .on('SchemaExported')
479
+ .emit('GenerateServer', {})
480
+ .on('ServerGenerated')
481
+ .emit('GenerateIA', {})
482
+ .on('SliceGenerated')
483
+ .emit('ImplementSlice', {})
484
+ .build();
485
+ const server = new PipelineServer({ port: 0 });
486
+ server.registerCommandHandlers([genHandler, iaHandler, implHandler]);
487
+ server.registerPipeline(pipeline);
488
+ await server.start();
489
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
490
+ const mermaid = await res.text();
491
+ expect(mermaid).toContain('evt_SchemaExported --> GenerateServer');
492
+ expect(mermaid).toContain('GenerateServer --> evt_ServerGenerated');
493
+ expect(mermaid).toContain('GenerateServer --> evt_SliceGenerated');
494
+ await server.stop();
495
+ });
496
+
497
+ it('should only show commands and events that are used in the pipeline', async () => {
498
+ const usedHandler = {
499
+ name: 'UsedCommand',
500
+ events: ['UsedEvent'],
501
+ handle: async () => ({ type: 'UsedEvent', data: {} }),
502
+ };
503
+ const unusedHandler = {
504
+ name: 'UnusedCommand',
505
+ events: ['UnusedEvent', 'AnotherUnusedEvent'],
506
+ handle: async () => ({ type: 'UnusedEvent', data: {} }),
507
+ };
508
+ const nextHandler = {
509
+ name: 'NextCommand',
510
+ events: ['NextDone'],
511
+ handle: async () => ({ type: 'NextDone', data: {} }),
512
+ };
513
+ const pipeline = define('test')
514
+ .on('TriggerEvent')
515
+ .emit('UsedCommand', {})
516
+ .on('UsedEvent')
517
+ .emit('NextCommand', {})
518
+ .build();
519
+ const server = new PipelineServer({ port: 0 });
520
+ server.registerCommandHandlers([usedHandler, unusedHandler, nextHandler]);
521
+ server.registerPipeline(pipeline);
522
+ await server.start();
523
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
524
+ const mermaid = await res.text();
525
+ expect(mermaid).toContain('evt_TriggerEvent');
526
+ expect(mermaid).toContain('UsedCommand');
527
+ expect(mermaid).toContain('evt_UsedEvent');
528
+ expect(mermaid).not.toContain('UnusedCommand');
529
+ expect(mermaid).not.toContain('UnusedEvent');
530
+ expect(mermaid).not.toContain('AnotherUnusedEvent');
531
+ await server.stop();
532
+ });
533
+
534
+ it('should only show events that have handlers in the pipeline, not unhandled command events', async () => {
535
+ const startHandler = {
536
+ name: 'StartServer',
537
+ events: ['ServerStarted', 'ServerStartFailed'],
538
+ handle: async () => ({ type: 'ServerStarted', data: {} }),
539
+ };
540
+ const processHandler = {
541
+ name: 'ProcessRequest',
542
+ events: ['RequestProcessed'],
543
+ handle: async () => ({ type: 'RequestProcessed', data: {} }),
544
+ };
545
+ const pipeline = define('test')
546
+ .on('TriggerEvent')
547
+ .emit('StartServer', {})
548
+ .on('ServerStarted')
549
+ .emit('ProcessRequest', {})
550
+ .build();
551
+ const server = new PipelineServer({ port: 0 });
552
+ server.registerCommandHandlers([startHandler, processHandler]);
553
+ server.registerPipeline(pipeline);
554
+ await server.start();
555
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
556
+ const mermaid = await res.text();
557
+ expect(mermaid).toContain('evt_ServerStarted');
558
+ expect(mermaid).toContain('StartServer --> evt_ServerStarted');
559
+ expect(mermaid).not.toContain('ServerStartFailed');
560
+ await server.stop();
561
+ });
562
+
563
+ it('should show edges from command events to settled node, not from commands', async () => {
564
+ const checkAHandler = {
565
+ name: 'CheckA',
566
+ events: ['CheckAPassed', 'CheckAFailed'],
567
+ handle: async () => ({ type: 'CheckAPassed', data: {} }),
568
+ };
569
+ const checkBHandler = {
570
+ name: 'CheckB',
571
+ events: ['CheckBPassed', 'CheckBFailed'],
572
+ handle: async () => ({ type: 'CheckBPassed', data: {} }),
573
+ };
574
+ const pipeline = define('test')
575
+ .on('Start')
576
+ .emit('CheckA', {})
577
+ .emit('CheckB', {})
578
+ .settled(['CheckA', 'CheckB'])
579
+ .dispatch({ dispatches: [] }, () => {})
580
+ .build();
581
+ const server = new PipelineServer({ port: 0 });
582
+ server.registerCommandHandlers([checkAHandler, checkBHandler]);
583
+ server.registerPipeline(pipeline);
584
+ await server.start();
585
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
586
+ const mermaid = await res.text();
587
+ expect(mermaid).toContain('evt_CheckAPassed --> settled_CheckA_CheckB');
588
+ expect(mermaid).toContain('evt_CheckAFailed --> settled_CheckA_CheckB');
589
+ expect(mermaid).toContain('evt_CheckBPassed --> settled_CheckA_CheckB');
590
+ expect(mermaid).toContain('evt_CheckBFailed --> settled_CheckA_CheckB');
591
+ expect(mermaid).not.toMatch(/CheckA --> settled_/);
592
+ expect(mermaid).not.toMatch(/CheckB --> settled_/);
593
+ await server.stop();
594
+ });
595
+
596
+ it('should show edges from settled node to dispatched commands', async () => {
597
+ const checkHandler = {
598
+ name: 'CheckA',
599
+ events: ['CheckAPassed', 'CheckAFailed'],
600
+ handle: async () => ({ type: 'CheckAPassed', data: {} }),
601
+ };
602
+ const retryHandler = {
603
+ name: 'RetryCommand',
604
+ events: ['RetryDone'],
605
+ handle: async () => ({ type: 'RetryDone', data: {} }),
606
+ };
607
+ const pipeline = define('test')
608
+ .on('Start')
609
+ .emit('CheckA', {})
610
+ .settled(['CheckA'])
611
+ .dispatch({ dispatches: ['RetryCommand'] }, () => {})
612
+ .build();
613
+ const server = new PipelineServer({ port: 0 });
614
+ server.registerCommandHandlers([checkHandler, retryHandler]);
615
+ server.registerPipeline(pipeline);
616
+ await server.start();
617
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
618
+ const mermaid = await res.text();
619
+ expect(mermaid).toContain('settled_CheckA --> RetryCommand');
620
+ await server.stop();
621
+ });
622
+
623
+ it('should style backLink edges in red', async () => {
624
+ const checkHandler = {
625
+ name: 'CheckA',
626
+ events: ['CheckAPassed', 'CheckAFailed'],
627
+ handle: async () => ({ type: 'CheckAPassed', data: {} }),
628
+ };
629
+ const retryHandler = {
630
+ name: 'RetryCommand',
631
+ events: ['RetryDone'],
632
+ handle: async () => ({ type: 'RetryDone', data: {} }),
633
+ };
634
+ const pipeline = define('test')
635
+ .on('Start')
636
+ .emit('CheckA', {})
637
+ .settled(['CheckA'])
638
+ .dispatch({ dispatches: ['RetryCommand'] }, () => {})
639
+ .build();
640
+ const server = new PipelineServer({ port: 0 });
641
+ server.registerCommandHandlers([checkHandler, retryHandler]);
642
+ server.registerPipeline(pipeline);
643
+ await server.start();
644
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
645
+ const mermaid = await res.text();
646
+ expect(mermaid).toContain('linkStyle');
647
+ expect(mermaid).toMatch(/stroke:#[a-fA-F0-9]{6}|stroke:red/);
648
+ await server.stop();
649
+ });
650
+
651
+ it('should add event nodes from settled handler commandToEvents when not already added', async () => {
652
+ const checkAHandler = {
653
+ name: 'CheckA',
654
+ events: ['CheckAPassed', 'CheckAFailed'],
655
+ handle: async () => ({ type: 'CheckAPassed', data: {} }),
656
+ };
657
+ const checkBHandler = {
658
+ name: 'CheckB',
659
+ events: ['CheckBPassed', 'CheckBFailed'],
660
+ handle: async () => ({ type: 'CheckBPassed', data: {} }),
661
+ };
662
+ const pipeline = define('test')
663
+ .on('Start')
664
+ .emit('CheckA', {})
665
+ .emit('CheckB', {})
666
+ .settled(['CheckA', 'CheckB'])
667
+ .dispatch({ dispatches: [] }, () => {})
668
+ .build();
669
+ const server = new PipelineServer({ port: 0 });
670
+ server.registerCommandHandlers([checkAHandler, checkBHandler]);
671
+ server.registerPipeline(pipeline);
672
+ await server.start();
673
+ const res = await fetch(`http://localhost:${server.port}/pipeline/mermaid`);
674
+ const mermaid = await res.text();
675
+ expect(mermaid).toContain('evt_CheckAPassed');
676
+ expect(mermaid).toContain('evt_CheckAFailed');
677
+ expect(mermaid).toContain('evt_CheckBPassed');
678
+ expect(mermaid).toContain('evt_CheckBFailed');
679
+ await server.stop();
680
+ });
681
+ });
682
+
683
+ describe('GET /pipeline/diagram', () => {
684
+ it('should return HTML content type', async () => {
685
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
686
+ const server = new PipelineServer({ port: 0 });
687
+ server.registerPipeline(pipeline);
688
+ await server.start();
689
+ const res = await fetch(`http://localhost:${server.port}/pipeline/diagram`);
690
+ expect(res.headers.get('content-type')).toContain('text/html');
691
+ await server.stop();
692
+ });
693
+
694
+ it('should include mermaid.js script', async () => {
695
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
696
+ const server = new PipelineServer({ port: 0 });
697
+ server.registerPipeline(pipeline);
698
+ await server.start();
699
+ const res = await fetch(`http://localhost:${server.port}/pipeline/diagram`);
700
+ const html = await res.text();
701
+ expect(html).toContain('mermaid');
702
+ await server.stop();
703
+ });
704
+
705
+ it('should include the pipeline mermaid definition', async () => {
706
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
707
+ const server = new PipelineServer({ port: 0 });
708
+ server.registerPipeline(pipeline);
709
+ await server.start();
710
+ const res = await fetch(`http://localhost:${server.port}/pipeline/diagram`);
711
+ const html = await res.text();
712
+ expect(html).toContain('flowchart LR');
713
+ expect(html).toContain('evt_Start');
714
+ await server.stop();
715
+ });
716
+
717
+ it('should have a valid HTML structure', async () => {
718
+ const pipeline = define('test').on('Start').emit('Process', {}).build();
719
+ const server = new PipelineServer({ port: 0 });
720
+ server.registerPipeline(pipeline);
721
+ await server.start();
722
+ const res = await fetch(`http://localhost:${server.port}/pipeline/diagram`);
723
+ const html = await res.text();
724
+ expect(html).toContain('<!DOCTYPE html>');
725
+ expect(html).toContain('<html');
726
+ expect(html).toContain('</html>');
727
+ await server.stop();
728
+ });
729
+ });
730
+
731
+ describe('integration', () => {
732
+ it('should execute complete workflow', async () => {
733
+ const handler = {
734
+ name: 'Gen',
735
+ alias: 'gen',
736
+ description: '',
737
+ fields: {},
738
+ examples: [],
739
+ events: ['Done'],
740
+ handle: async () => ({ type: 'Done', data: { id: '1' } }),
741
+ };
742
+ const pipeline = define('wf')
743
+ .on('Done')
744
+ .emit('Process', (e: { data: { id: string } }) => ({ x: e.data.id }))
745
+ .build();
746
+ const server = new PipelineServer({ port: 0 });
747
+ server.registerCommandHandlers([handler]);
748
+ server.registerPipeline(pipeline);
749
+ await server.start();
750
+ await fetch(`http://localhost:${server.port}/command`, {
751
+ method: 'POST',
752
+ headers: { 'Content-Type': 'application/json' },
753
+ body: JSON.stringify({ type: 'Gen', data: {} }),
754
+ });
755
+ await new Promise((r) => setTimeout(r, 200));
756
+ const msgs = await fetchAs<StoredMessage[]>(`http://localhost:${server.port}/messages`);
757
+ expect(msgs.some((m) => m.message.type === 'Process')).toBe(true);
758
+ await server.stop();
759
+ });
760
+ });
761
+ });