@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,505 @@
1
+ import { define } from '../builder/define';
2
+ import { type CommandHandlerWithMetadata, PipelineServer } from './pipeline-server';
3
+
4
+ interface RegistryResponse {
5
+ eventHandlers: string[];
6
+ commandHandlers: string[];
7
+ commandsWithMetadata: Array<{
8
+ id: string;
9
+ name: string;
10
+ alias: string;
11
+ description: string;
12
+ }>;
13
+ folds: string[];
14
+ }
15
+
16
+ interface PipelineNode {
17
+ id: string;
18
+ name?: string;
19
+ title?: string;
20
+ status?: string;
21
+ }
22
+
23
+ interface PipelineResponse {
24
+ nodes: PipelineNode[];
25
+ edges: Array<{ from: string; to: string }>;
26
+ commandToEvents: Record<string, string[]>;
27
+ eventToCommand: Record<string, string>;
28
+ }
29
+
30
+ interface CommandAck {
31
+ status: string;
32
+ commandId?: string;
33
+ }
34
+
35
+ interface StoredMessage {
36
+ message: { type: string; data?: Record<string, unknown> };
37
+ messageType: string;
38
+ }
39
+
40
+ interface StatsResponse {
41
+ totalMessages: number;
42
+ }
43
+
44
+ async function fetchJson<T>(url: string, options?: RequestInit): Promise<T> {
45
+ const res = await fetch(url, options);
46
+ return res.json() as Promise<T>;
47
+ }
48
+
49
+ describe('PipelineServer E2E', () => {
50
+ describe('baseline endpoints (CLI E2E parity)', () => {
51
+ it('should return registry with expected shape', async () => {
52
+ const handler: CommandHandlerWithMetadata = {
53
+ name: 'ExportSchema',
54
+ alias: 'export:schema',
55
+ description: 'Export flow schemas to context directory',
56
+ events: ['SchemaExported'],
57
+ handle: async () => ({ type: 'SchemaExported', data: {} }),
58
+ };
59
+
60
+ const pipeline = define('kanban')
61
+ .on('SchemaExported')
62
+ .emit('GenerateServer', { modelPath: './.context/schema.json' })
63
+ .build();
64
+
65
+ const server = new PipelineServer({ port: 0 });
66
+ server.registerCommandHandlers([handler]);
67
+ server.registerPipeline(pipeline);
68
+ await server.start();
69
+
70
+ const registry = await fetchJson<RegistryResponse>(`http://localhost:${server.port}/registry`);
71
+
72
+ expect(registry.eventHandlers).toContain('SchemaExported');
73
+ expect(registry.commandHandlers).toContain('ExportSchema');
74
+ expect(registry.folds).toEqual([]);
75
+ expect(registry.commandsWithMetadata).toHaveLength(1);
76
+ expect(registry.commandsWithMetadata[0].alias).toBe('export:schema');
77
+ expect(registry.commandsWithMetadata[0].description).toBe('Export flow schemas to context directory');
78
+
79
+ await server.stop();
80
+ });
81
+
82
+ it('should return pipeline with expected shape', async () => {
83
+ const handler: CommandHandlerWithMetadata = {
84
+ name: 'ExportSchema',
85
+ alias: 'export:schema',
86
+ description: 'Export schemas',
87
+ events: ['SchemaExported'],
88
+ handle: async () => ({ type: 'SchemaExported', data: {} }),
89
+ };
90
+
91
+ const pipeline = define('kanban').on('SchemaExported').emit('GenerateServer', {}).build();
92
+
93
+ const server = new PipelineServer({ port: 0 });
94
+ server.registerCommandHandlers([handler]);
95
+ server.registerPipeline(pipeline);
96
+ await server.start();
97
+
98
+ const pipelineRes = await fetchJson<PipelineResponse>(`http://localhost:${server.port}/pipeline`);
99
+
100
+ expect(pipelineRes.commandToEvents).toEqual({ ExportSchema: ['SchemaExported'] });
101
+ expect(pipelineRes.eventToCommand).toEqual({ SchemaExported: 'GenerateServer' });
102
+ expect(pipelineRes.nodes.some((n) => n.id === 'ExportSchema')).toBe(true);
103
+ expect(pipelineRes.nodes.some((n) => n.id === 'evt:SchemaExported')).toBe(true);
104
+
105
+ await server.stop();
106
+ });
107
+
108
+ it('should return sessions array', async () => {
109
+ const server = new PipelineServer({ port: 0 });
110
+ await server.start();
111
+
112
+ const sessions = await fetchJson<unknown[]>(`http://localhost:${server.port}/sessions`);
113
+ expect(Array.isArray(sessions)).toBe(true);
114
+
115
+ await server.stop();
116
+ });
117
+
118
+ it('should return messages array', async () => {
119
+ const server = new PipelineServer({ port: 0 });
120
+ await server.start();
121
+
122
+ const messages = await fetchJson<StoredMessage[]>(`http://localhost:${server.port}/messages`);
123
+ expect(Array.isArray(messages)).toBe(true);
124
+
125
+ await server.stop();
126
+ });
127
+
128
+ it('should return stats with totalMessages', async () => {
129
+ const server = new PipelineServer({ port: 0 });
130
+ await server.start();
131
+
132
+ const stats = await fetchJson<StatsResponse>(`http://localhost:${server.port}/stats`);
133
+ expect(stats.totalMessages).toBeDefined();
134
+
135
+ await server.stop();
136
+ });
137
+
138
+ it('should accept command and return ack', async () => {
139
+ const handler: CommandHandlerWithMetadata = {
140
+ name: 'ExportSchema',
141
+ handle: async () => ({ type: 'SchemaExported', data: {} }),
142
+ };
143
+
144
+ const server = new PipelineServer({ port: 0 });
145
+ server.registerCommandHandlers([handler]);
146
+ await server.start();
147
+
148
+ const ack = await fetchJson<CommandAck>(`http://localhost:${server.port}/command`, {
149
+ method: 'POST',
150
+ headers: { 'Content-Type': 'application/json' },
151
+ body: JSON.stringify({ type: 'ExportSchema', data: {} }),
152
+ });
153
+
154
+ expect(ack.status).toBe('ack');
155
+ expect(ack.commandId).toBeDefined();
156
+
157
+ await server.stop();
158
+ });
159
+ });
160
+
161
+ describe('command execution and event routing', () => {
162
+ it('should execute command and route resulting event through pipeline', async () => {
163
+ const exportHandler: CommandHandlerWithMetadata = {
164
+ name: 'ExportSchema',
165
+ events: ['SchemaExported'],
166
+ handle: async () => ({ type: 'SchemaExported', data: { path: './schema.json' } }),
167
+ };
168
+
169
+ const generateHandler: CommandHandlerWithMetadata = {
170
+ name: 'GenerateServer',
171
+ events: ['ServerGenerated'],
172
+ handle: async () => ({ type: 'ServerGenerated', data: { slices: 3 } }),
173
+ };
174
+
175
+ const pipeline = define('kanban')
176
+ .on('SchemaExported')
177
+ .emit('GenerateServer', (e: { data: { path: string } }) => ({ modelPath: e.data.path }))
178
+ .build();
179
+
180
+ const server = new PipelineServer({ port: 0 });
181
+ server.registerCommandHandlers([exportHandler, generateHandler]);
182
+ server.registerPipeline(pipeline);
183
+ await server.start();
184
+
185
+ await fetchJson(`http://localhost:${server.port}/command`, {
186
+ method: 'POST',
187
+ headers: { 'Content-Type': 'application/json' },
188
+ body: JSON.stringify({ type: 'ExportSchema', data: {} }),
189
+ });
190
+
191
+ await new Promise((r) => setTimeout(r, 200));
192
+
193
+ const messages = await fetchJson<StoredMessage[]>(`http://localhost:${server.port}/messages`);
194
+ const eventTypes = messages.filter((m) => m.messageType === 'event').map((m) => m.message.type);
195
+
196
+ expect(eventTypes).toContain('SchemaExported');
197
+ expect(eventTypes).toContain('ServerGenerated');
198
+
199
+ await server.stop();
200
+ });
201
+
202
+ it('should handle pipeline chain with multiple handlers', async () => {
203
+ const handlers: CommandHandlerWithMetadata[] = [
204
+ {
205
+ name: 'Start',
206
+ events: ['Started'],
207
+ handle: async () => ({ type: 'Started', data: {} }),
208
+ },
209
+ {
210
+ name: 'Process',
211
+ events: ['Processed'],
212
+ handle: async () => ({ type: 'Processed', data: {} }),
213
+ },
214
+ {
215
+ name: 'Finish',
216
+ events: ['Finished'],
217
+ handle: async () => ({ type: 'Finished', data: {} }),
218
+ },
219
+ ];
220
+
221
+ const pipeline = define('chain').on('Started').emit('Process', {}).on('Processed').emit('Finish', {}).build();
222
+
223
+ const server = new PipelineServer({ port: 0 });
224
+ server.registerCommandHandlers(handlers);
225
+ server.registerPipeline(pipeline);
226
+ await server.start();
227
+
228
+ await fetchJson(`http://localhost:${server.port}/command`, {
229
+ method: 'POST',
230
+ headers: { 'Content-Type': 'application/json' },
231
+ body: JSON.stringify({ type: 'Start', data: {} }),
232
+ });
233
+
234
+ await new Promise((r) => setTimeout(r, 300));
235
+
236
+ const messages = await fetchJson<StoredMessage[]>(`http://localhost:${server.port}/messages`);
237
+ const eventTypes = messages.filter((m) => m.messageType === 'event').map((m) => m.message.type);
238
+
239
+ expect(eventTypes).toContain('Started');
240
+ expect(eventTypes).toContain('Processed');
241
+ expect(eventTypes).toContain('Finished');
242
+
243
+ await server.stop();
244
+ });
245
+ });
246
+
247
+ describe('settled handler execution', () => {
248
+ it('should execute settled handler when all commands complete', async () => {
249
+ let settledHandlerCalled = false;
250
+ let receivedEvents: Record<string, Array<{ type: string }>> = {};
251
+
252
+ const handlers: CommandHandlerWithMetadata[] = [
253
+ {
254
+ name: 'CheckTests',
255
+ events: ['TestsCheckPassed', 'TestsCheckFailed'],
256
+ handle: async () => ({ type: 'TestsCheckPassed', data: { result: 'pass' } }),
257
+ },
258
+ {
259
+ name: 'CheckTypes',
260
+ events: ['TypeCheckPassed', 'TypeCheckFailed'],
261
+ handle: async () => ({ type: 'TypeCheckPassed', data: { result: 'pass' } }),
262
+ },
263
+ {
264
+ name: 'CheckLint',
265
+ events: ['LintCheckPassed', 'LintCheckFailed'],
266
+ handle: async () => ({ type: 'LintCheckPassed', data: { result: 'pass' } }),
267
+ },
268
+ {
269
+ name: 'ImplementSlice',
270
+ events: ['SliceImplemented'],
271
+ handle: async () => ({ type: 'SliceImplemented', data: {} }),
272
+ },
273
+ ];
274
+
275
+ const pipeline = define('check-settle')
276
+ .on('SliceImplemented')
277
+ .emit('CheckTests', { target: './src' })
278
+ .emit('CheckTypes', { target: './src' })
279
+ .emit('CheckLint', { target: './src' })
280
+ .settled(['CheckTests', 'CheckTypes', 'CheckLint'])
281
+ .dispatch({ dispatches: [] }, (events) => {
282
+ settledHandlerCalled = true;
283
+ receivedEvents = events as Record<string, Array<{ type: string }>>;
284
+ })
285
+ .build();
286
+
287
+ const server = new PipelineServer({ port: 0 });
288
+ server.registerCommandHandlers(handlers);
289
+ server.registerPipeline(pipeline);
290
+ await server.start();
291
+
292
+ await fetchJson(`http://localhost:${server.port}/command`, {
293
+ method: 'POST',
294
+ headers: { 'Content-Type': 'application/json' },
295
+ body: JSON.stringify({ type: 'ImplementSlice', data: { slicePath: './adds-todo' } }),
296
+ });
297
+
298
+ await new Promise((r) => setTimeout(r, 300));
299
+
300
+ expect(settledHandlerCalled).toBe(true);
301
+ expect(receivedEvents.CheckTests).toBeDefined();
302
+ expect(receivedEvents.CheckTypes).toBeDefined();
303
+ expect(receivedEvents.CheckLint).toBeDefined();
304
+ expect(receivedEvents.CheckTests[0].type).toBe('TestsCheckPassed');
305
+ expect(receivedEvents.CheckTypes[0].type).toBe('TypeCheckPassed');
306
+ expect(receivedEvents.CheckLint[0].type).toBe('LintCheckPassed');
307
+
308
+ await server.stop();
309
+ });
310
+
311
+ it('should dispatch command from settled handler', async () => {
312
+ const handlers: CommandHandlerWithMetadata[] = [
313
+ {
314
+ name: 'CheckA',
315
+ events: ['ADone'],
316
+ handle: async () => ({ type: 'ADone', data: {} }),
317
+ },
318
+ {
319
+ name: 'CheckB',
320
+ events: ['BDone'],
321
+ handle: async () => ({ type: 'BDone', data: {} }),
322
+ },
323
+ {
324
+ name: 'FollowUp',
325
+ events: ['FollowUpDone'],
326
+ handle: async () => ({ type: 'FollowUpDone', data: { message: 'completed' } }),
327
+ },
328
+ {
329
+ name: 'Trigger',
330
+ events: ['Triggered'],
331
+ handle: async () => ({ type: 'Triggered', data: {} }),
332
+ },
333
+ ];
334
+
335
+ const pipeline = define('dispatch-test')
336
+ .on('Triggered')
337
+ .emit('CheckA', {})
338
+ .emit('CheckB', {})
339
+ .settled(['CheckA', 'CheckB'])
340
+ .dispatch({ dispatches: ['FollowUp'] }, (_events, send) => {
341
+ send('FollowUp', { reason: 'all checks passed' });
342
+ })
343
+ .build();
344
+
345
+ const server = new PipelineServer({ port: 0 });
346
+ server.registerCommandHandlers(handlers);
347
+ server.registerPipeline(pipeline);
348
+ await server.start();
349
+
350
+ await fetchJson(`http://localhost:${server.port}/command`, {
351
+ method: 'POST',
352
+ headers: { 'Content-Type': 'application/json' },
353
+ body: JSON.stringify({ type: 'Trigger', data: {} }),
354
+ });
355
+
356
+ await new Promise((r) => setTimeout(r, 300));
357
+
358
+ const messages = await fetchJson<StoredMessage[]>(`http://localhost:${server.port}/messages`);
359
+ const eventTypes = messages.filter((m) => m.messageType === 'event').map((m) => m.message.type);
360
+
361
+ expect(eventTypes).toContain('Triggered');
362
+ expect(eventTypes).toContain('ADone');
363
+ expect(eventTypes).toContain('BDone');
364
+ expect(eventTypes).toContain('FollowUpDone');
365
+
366
+ await server.stop();
367
+ });
368
+
369
+ it('should retry when settled handler returns persist: true', async () => {
370
+ let checkCallCount = 0;
371
+ let settledCallCount = 0;
372
+
373
+ const handlers: CommandHandlerWithMetadata[] = [
374
+ {
375
+ name: 'RunCheck',
376
+ events: ['CheckPassed', 'CheckFailed'],
377
+ handle: async () => {
378
+ checkCallCount++;
379
+ if (checkCallCount < 3) {
380
+ return { type: 'CheckFailed', data: { attempt: checkCallCount } };
381
+ }
382
+ return { type: 'CheckPassed', data: { attempt: checkCallCount } };
383
+ },
384
+ },
385
+ {
386
+ name: 'Start',
387
+ events: ['Started'],
388
+ handle: async () => ({ type: 'Started', data: {} }),
389
+ },
390
+ ];
391
+
392
+ const pipeline = define('retry-test')
393
+ .on('Started')
394
+ .emit('RunCheck', {})
395
+ .settled(['RunCheck'])
396
+ .dispatch({ dispatches: ['RunCheck'] }, (events, send) => {
397
+ settledCallCount++;
398
+ const checkEvents = events.RunCheck;
399
+ const hasFailure = checkEvents.some((e) => e.type === 'CheckFailed');
400
+
401
+ if (hasFailure && settledCallCount < 3) {
402
+ send('RunCheck', { retryAttempt: settledCallCount });
403
+ return { persist: true };
404
+ }
405
+ })
406
+ .build();
407
+
408
+ const server = new PipelineServer({ port: 0 });
409
+ server.registerCommandHandlers(handlers);
410
+ server.registerPipeline(pipeline);
411
+ await server.start();
412
+
413
+ await fetchJson(`http://localhost:${server.port}/command`, {
414
+ method: 'POST',
415
+ headers: { 'Content-Type': 'application/json' },
416
+ body: JSON.stringify({ type: 'Start', data: {} }),
417
+ });
418
+
419
+ await new Promise((r) => setTimeout(r, 500));
420
+
421
+ const messages = await fetchJson<StoredMessage[]>(`http://localhost:${server.port}/messages`);
422
+ const eventTypes = messages.filter((m) => m.messageType === 'event').map((m) => m.message.type);
423
+
424
+ expect(eventTypes.filter((t) => t === 'CheckFailed')).toHaveLength(2);
425
+ expect(eventTypes).toContain('CheckPassed');
426
+ expect(checkCallCount).toBe(3);
427
+ expect(settledCallCount).toBe(3);
428
+
429
+ await server.stop();
430
+ });
431
+ });
432
+
433
+ describe('phased execution', () => {
434
+ it('should execute items in phase order with waiting', async () => {
435
+ const dispatchedOrder: string[] = [];
436
+
437
+ interface Component {
438
+ id: string;
439
+ type: 'molecule' | 'organism' | 'page';
440
+ }
441
+
442
+ const handlers: CommandHandlerWithMetadata[] = [
443
+ {
444
+ name: 'GenerateClient',
445
+ events: ['ClientGenerated'],
446
+ handle: async () => ({
447
+ type: 'ClientGenerated',
448
+ data: {
449
+ components: [
450
+ { id: 'page1', type: 'page' },
451
+ { id: 'mol1', type: 'molecule' },
452
+ { id: 'org1', type: 'organism' },
453
+ { id: 'mol2', type: 'molecule' },
454
+ ],
455
+ },
456
+ }),
457
+ },
458
+ {
459
+ name: 'ImplementComponent',
460
+ events: ['ComponentImplemented'],
461
+ handle: async (cmd) => {
462
+ const filePath = (cmd.data as { filePath: string }).filePath;
463
+ dispatchedOrder.push(filePath);
464
+ return { type: 'ComponentImplemented', data: { filePath } };
465
+ },
466
+ },
467
+ ];
468
+
469
+ const pipeline = define('phased-test')
470
+ .on('ClientGenerated')
471
+ .forEach((e: { data: { components: Component[] } }) => e.data.components)
472
+ .groupInto(['molecule', 'organism', 'page'], (c: Component) => c.type)
473
+ .process('ImplementComponent', (c: Component) => ({ filePath: c.id }))
474
+ .onComplete({
475
+ success: 'AllComponentsImplemented',
476
+ failure: 'ComponentsFailed',
477
+ itemKey: (e) => (e.data as { filePath?: string; id?: string }).filePath ?? (e.data as { id: string }).id,
478
+ })
479
+ .build();
480
+
481
+ const server = new PipelineServer({ port: 0 });
482
+ server.registerCommandHandlers(handlers);
483
+ server.registerPipeline(pipeline);
484
+ await server.start();
485
+
486
+ await fetchJson(`http://localhost:${server.port}/command`, {
487
+ method: 'POST',
488
+ headers: { 'Content-Type': 'application/json' },
489
+ body: JSON.stringify({ type: 'GenerateClient', data: {} }),
490
+ });
491
+
492
+ await new Promise((r) => setTimeout(r, 500));
493
+
494
+ const messages = await fetchJson<StoredMessage[]>(`http://localhost:${server.port}/messages`);
495
+ const eventTypes = messages.filter((m) => m.messageType === 'event').map((m) => m.message.type);
496
+
497
+ expect(dispatchedOrder).toEqual(['mol1', 'mol2', 'org1', 'page1']);
498
+
499
+ expect(eventTypes).toContain('ComponentImplemented');
500
+ expect(eventTypes).toContain('AllComponentsImplemented');
501
+
502
+ await server.stop();
503
+ });
504
+ });
505
+ });