@auto-engineer/pipeline 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -6
- package/CHANGELOG.md +12 -0
- package/README.md +279 -0
- package/dist/src/builder/define.d.ts +6 -2
- package/dist/src/builder/define.d.ts.map +1 -1
- package/dist/src/builder/define.js +17 -7
- package/dist/src/builder/define.js.map +1 -1
- package/dist/src/builder/define.specs.js +3 -3
- package/dist/src/builder/define.specs.js.map +1 -1
- package/dist/src/core/descriptors.d.ts +6 -2
- package/dist/src/core/descriptors.d.ts.map +1 -1
- package/dist/src/graph/filter-graph.d.ts +3 -0
- package/dist/src/graph/filter-graph.d.ts.map +1 -0
- package/dist/src/graph/filter-graph.js +80 -0
- package/dist/src/graph/filter-graph.js.map +1 -0
- package/dist/src/graph/filter-graph.specs.d.ts +2 -0
- package/dist/src/graph/filter-graph.specs.d.ts.map +1 -0
- package/dist/src/graph/filter-graph.specs.js +204 -0
- package/dist/src/graph/filter-graph.specs.js.map +1 -0
- package/dist/src/graph/types.d.ts +8 -0
- package/dist/src/graph/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/projections/await-tracker-projection.d.ts +31 -0
- package/dist/src/projections/await-tracker-projection.d.ts.map +1 -0
- package/dist/src/projections/await-tracker-projection.js +35 -0
- package/dist/src/projections/await-tracker-projection.js.map +1 -0
- package/dist/src/projections/index.d.ts +4 -0
- package/dist/src/projections/index.d.ts.map +1 -0
- package/dist/src/projections/index.js +4 -0
- package/dist/src/projections/index.js.map +1 -0
- package/dist/src/projections/item-status-projection.d.ts +22 -0
- package/dist/src/projections/item-status-projection.d.ts.map +1 -0
- package/dist/src/projections/item-status-projection.js +11 -0
- package/dist/src/projections/item-status-projection.js.map +1 -0
- package/dist/src/projections/item-status-projection.specs.d.ts +2 -0
- package/dist/src/projections/item-status-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/item-status-projection.specs.js +119 -0
- package/dist/src/projections/item-status-projection.specs.js.map +1 -0
- package/dist/src/projections/latest-run-projection.d.ts +15 -0
- package/dist/src/projections/latest-run-projection.d.ts.map +1 -0
- package/dist/src/projections/latest-run-projection.js +7 -0
- package/dist/src/projections/latest-run-projection.js.map +1 -0
- package/dist/src/projections/latest-run-projection.specs.d.ts +2 -0
- package/dist/src/projections/latest-run-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/latest-run-projection.specs.js +33 -0
- package/dist/src/projections/latest-run-projection.specs.js.map +1 -0
- package/dist/src/projections/message-log-projection.d.ts +51 -0
- package/dist/src/projections/message-log-projection.d.ts.map +1 -0
- package/dist/src/projections/message-log-projection.js +51 -0
- package/dist/src/projections/message-log-projection.js.map +1 -0
- package/dist/src/projections/message-log-projection.specs.d.ts +2 -0
- package/dist/src/projections/message-log-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/message-log-projection.specs.js +101 -0
- package/dist/src/projections/message-log-projection.specs.js.map +1 -0
- package/dist/src/projections/node-status-projection.d.ts +23 -0
- package/dist/src/projections/node-status-projection.d.ts.map +1 -0
- package/dist/src/projections/node-status-projection.js +10 -0
- package/dist/src/projections/node-status-projection.js.map +1 -0
- package/dist/src/projections/node-status-projection.specs.d.ts +2 -0
- package/dist/src/projections/node-status-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/node-status-projection.specs.js +116 -0
- package/dist/src/projections/node-status-projection.specs.js.map +1 -0
- package/dist/src/projections/phased-execution-projection.d.ts +77 -0
- package/dist/src/projections/phased-execution-projection.d.ts.map +1 -0
- package/dist/src/projections/phased-execution-projection.js +54 -0
- package/dist/src/projections/phased-execution-projection.js.map +1 -0
- package/dist/src/projections/phased-execution-projection.specs.d.ts +2 -0
- package/dist/src/projections/phased-execution-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/phased-execution-projection.specs.js +171 -0
- package/dist/src/projections/phased-execution-projection.specs.js.map +1 -0
- package/dist/src/projections/settled-instance-projection.d.ts +67 -0
- package/dist/src/projections/settled-instance-projection.d.ts.map +1 -0
- package/dist/src/projections/settled-instance-projection.js +66 -0
- package/dist/src/projections/settled-instance-projection.js.map +1 -0
- package/dist/src/projections/settled-instance-projection.specs.d.ts +2 -0
- package/dist/src/projections/settled-instance-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/settled-instance-projection.specs.js +217 -0
- package/dist/src/projections/settled-instance-projection.specs.js.map +1 -0
- package/dist/src/projections/stats-projection.d.ts +9 -0
- package/dist/src/projections/stats-projection.d.ts.map +1 -0
- package/dist/src/projections/stats-projection.js +16 -0
- package/dist/src/projections/stats-projection.js.map +1 -0
- package/dist/src/projections/stats-projection.specs.d.ts +2 -0
- package/dist/src/projections/stats-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/stats-projection.specs.js +91 -0
- package/dist/src/projections/stats-projection.specs.js.map +1 -0
- package/dist/src/runtime/await-tracker.d.ts +17 -7
- package/dist/src/runtime/await-tracker.d.ts.map +1 -1
- package/dist/src/runtime/await-tracker.js +32 -29
- package/dist/src/runtime/await-tracker.js.map +1 -1
- package/dist/src/runtime/await-tracker.specs.js +56 -38
- package/dist/src/runtime/await-tracker.specs.js.map +1 -1
- package/dist/src/runtime/context.d.ts +1 -1
- package/dist/src/runtime/context.d.ts.map +1 -1
- package/dist/src/runtime/event-command-map.d.ts +3 -3
- package/dist/src/runtime/event-command-map.d.ts.map +1 -1
- package/dist/src/runtime/event-command-map.js +6 -2
- package/dist/src/runtime/event-command-map.js.map +1 -1
- package/dist/src/runtime/phased-executor.d.ts +15 -9
- package/dist/src/runtime/phased-executor.d.ts.map +1 -1
- package/dist/src/runtime/phased-executor.js +126 -104
- package/dist/src/runtime/phased-executor.js.map +1 -1
- package/dist/src/runtime/phased-executor.specs.js +243 -81
- package/dist/src/runtime/phased-executor.specs.js.map +1 -1
- package/dist/src/runtime/pipeline-runtime.d.ts.map +1 -1
- package/dist/src/runtime/pipeline-runtime.js +2 -2
- package/dist/src/runtime/pipeline-runtime.js.map +1 -1
- package/dist/src/runtime/pipeline-runtime.specs.js +35 -0
- package/dist/src/runtime/pipeline-runtime.specs.js.map +1 -1
- package/dist/src/runtime/settled-tracker.d.ts +12 -9
- package/dist/src/runtime/settled-tracker.d.ts.map +1 -1
- package/dist/src/runtime/settled-tracker.js +92 -77
- package/dist/src/runtime/settled-tracker.js.map +1 -1
- package/dist/src/runtime/settled-tracker.specs.js +568 -118
- package/dist/src/runtime/settled-tracker.specs.js.map +1 -1
- package/dist/src/server/pipeline-server.d.ts +31 -9
- package/dist/src/server/pipeline-server.d.ts.map +1 -1
- package/dist/src/server/pipeline-server.e2e.specs.js +2 -10
- package/dist/src/server/pipeline-server.e2e.specs.js.map +1 -1
- package/dist/src/server/pipeline-server.js +408 -123
- package/dist/src/server/pipeline-server.js.map +1 -1
- package/dist/src/server/pipeline-server.specs.js +777 -32
- package/dist/src/server/pipeline-server.specs.js.map +1 -1
- package/dist/src/server/sse-manager.specs.js +55 -35
- package/dist/src/server/sse-manager.specs.js.map +1 -1
- package/dist/src/store/index.d.ts +3 -0
- package/dist/src/store/index.d.ts.map +1 -0
- package/dist/src/store/index.js +3 -0
- package/dist/src/store/index.js.map +1 -0
- package/dist/src/store/pipeline-event-store.d.ts +10 -0
- package/dist/src/store/pipeline-event-store.d.ts.map +1 -0
- package/dist/src/store/pipeline-event-store.js +112 -0
- package/dist/src/store/pipeline-event-store.js.map +1 -0
- package/dist/src/store/pipeline-event-store.specs.d.ts +2 -0
- package/dist/src/store/pipeline-event-store.specs.d.ts.map +1 -0
- package/dist/src/store/pipeline-event-store.specs.js +287 -0
- package/dist/src/store/pipeline-event-store.specs.js.map +1 -0
- package/dist/src/store/pipeline-read-model.d.ts +49 -0
- package/dist/src/store/pipeline-read-model.d.ts.map +1 -0
- package/dist/src/store/pipeline-read-model.js +157 -0
- package/dist/src/store/pipeline-read-model.js.map +1 -0
- package/dist/src/store/pipeline-read-model.specs.d.ts +2 -0
- package/dist/src/store/pipeline-read-model.specs.d.ts.map +1 -0
- package/dist/src/store/pipeline-read-model.specs.js +830 -0
- package/dist/src/store/pipeline-read-model.specs.js.map +1 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.js +2 -2
- package/dist/src/testing/fixtures/kanban-full.pipeline.js.map +1 -1
- package/dist/src/testing/fixtures/kanban.pipeline.js +2 -2
- package/dist/src/testing/fixtures/kanban.pipeline.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +960 -0
- package/package.json +5 -4
- package/src/builder/define.specs.ts +3 -3
- package/src/builder/define.ts +24 -11
- package/src/core/descriptors.ts +7 -2
- package/src/graph/filter-graph.specs.ts +241 -0
- package/src/graph/filter-graph.ts +111 -0
- package/src/graph/types.ts +10 -0
- package/src/index.ts +1 -0
- package/src/projections/await-tracker-projection.ts +68 -0
- package/src/projections/index.ts +11 -0
- package/src/projections/item-status-projection.specs.ts +130 -0
- package/src/projections/item-status-projection.ts +32 -0
- package/src/projections/latest-run-projection.specs.ts +38 -0
- package/src/projections/latest-run-projection.ts +20 -0
- package/src/projections/message-log-projection.specs.ts +118 -0
- package/src/projections/message-log-projection.ts +113 -0
- package/src/projections/node-status-projection.specs.ts +127 -0
- package/src/projections/node-status-projection.ts +33 -0
- package/src/projections/phased-execution-projection.specs.ts +202 -0
- package/src/projections/phased-execution-projection.ts +146 -0
- package/src/projections/settled-instance-projection.specs.ts +249 -0
- package/src/projections/settled-instance-projection.ts +160 -0
- package/src/projections/stats-projection.specs.ts +105 -0
- package/src/projections/stats-projection.ts +26 -0
- package/src/runtime/await-tracker.specs.ts +57 -34
- package/src/runtime/await-tracker.ts +43 -31
- package/src/runtime/context.ts +1 -1
- package/src/runtime/event-command-map.ts +11 -4
- package/src/runtime/phased-executor.specs.ts +357 -81
- package/src/runtime/phased-executor.ts +142 -126
- package/src/runtime/pipeline-runtime.specs.ts +42 -0
- package/src/runtime/pipeline-runtime.ts +6 -4
- package/src/runtime/settled-tracker.specs.ts +716 -120
- package/src/runtime/settled-tracker.ts +104 -98
- package/src/server/pipeline-server.e2e.specs.ts +10 -16
- package/src/server/pipeline-server.specs.ts +964 -49
- package/src/server/pipeline-server.ts +512 -144
- package/src/server/sse-manager.specs.ts +67 -36
- package/src/store/index.ts +2 -0
- package/src/store/pipeline-event-store.specs.ts +309 -0
- package/src/store/pipeline-event-store.ts +156 -0
- package/src/store/pipeline-read-model.specs.ts +967 -0
- package/src/store/pipeline-read-model.ts +223 -0
- package/src/testing/fixtures/kanban-full.pipeline.ts +2 -2
- package/src/testing/fixtures/kanban.pipeline.ts +2 -2
- package/claude.md +0 -160
- package/dist/src/__tests__/e2e/helpers.d.ts +0 -48
- package/dist/src/__tests__/e2e/helpers.d.ts.map +0 -1
- package/dist/src/__tests__/e2e/helpers.js +0 -253
- package/dist/src/__tests__/e2e/helpers.js.map +0 -1
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.d.ts +0 -2
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.d.ts.map +0 -1
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.js +0 -195
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.js.map +0 -1
- package/dist/src/__tests__/e2e/types.d.ts +0 -107
- package/dist/src/__tests__/e2e/types.d.ts.map +0 -1
- package/dist/src/__tests__/e2e/types.js +0 -2
- package/dist/src/__tests__/e2e/types.js.map +0 -1
- package/dist/src/file-syncer/crypto/jwe-encryptor.d.ts +0 -15
- package/dist/src/file-syncer/crypto/jwe-encryptor.d.ts.map +0 -1
- package/dist/src/file-syncer/crypto/jwe-encryptor.js +0 -64
- package/dist/src/file-syncer/crypto/jwe-encryptor.js.map +0 -1
- package/dist/src/file-syncer/crypto/provider-resolver.d.ts +0 -24
- package/dist/src/file-syncer/crypto/provider-resolver.d.ts.map +0 -1
- package/dist/src/file-syncer/crypto/provider-resolver.js +0 -71
- package/dist/src/file-syncer/crypto/provider-resolver.js.map +0 -1
- package/dist/src/file-syncer/discovery/bareImports.d.ts +0 -3
- package/dist/src/file-syncer/discovery/bareImports.d.ts.map +0 -1
- package/dist/src/file-syncer/discovery/bareImports.js +0 -36
- package/dist/src/file-syncer/discovery/bareImports.js.map +0 -1
- package/dist/src/file-syncer/discovery/dts.d.ts +0 -8
- package/dist/src/file-syncer/discovery/dts.d.ts.map +0 -1
- package/dist/src/file-syncer/discovery/dts.js +0 -99
- package/dist/src/file-syncer/discovery/dts.js.map +0 -1
- package/dist/src/file-syncer/index.d.ts +0 -46
- package/dist/src/file-syncer/index.d.ts.map +0 -1
- package/dist/src/file-syncer/index.js +0 -392
- package/dist/src/file-syncer/index.js.map +0 -1
- package/dist/src/file-syncer/sync/resolveSyncFileSet.d.ts +0 -7
- package/dist/src/file-syncer/sync/resolveSyncFileSet.d.ts.map +0 -1
- package/dist/src/file-syncer/sync/resolveSyncFileSet.js +0 -86
- package/dist/src/file-syncer/sync/resolveSyncFileSet.js.map +0 -1
- package/dist/src/file-syncer/types/wire.d.ts +0 -14
- package/dist/src/file-syncer/types/wire.d.ts.map +0 -1
- package/dist/src/file-syncer/types/wire.js +0 -2
- package/dist/src/file-syncer/types/wire.js.map +0 -1
- package/dist/src/file-syncer/utils/hash.d.ts +0 -5
- package/dist/src/file-syncer/utils/hash.d.ts.map +0 -1
- package/dist/src/file-syncer/utils/hash.js +0 -19
- package/dist/src/file-syncer/utils/hash.js.map +0 -1
- package/dist/src/file-syncer/utils/path.d.ts +0 -13
- package/dist/src/file-syncer/utils/path.d.ts.map +0 -1
- package/dist/src/file-syncer/utils/path.js +0 -74
- package/dist/src/file-syncer/utils/path.js.map +0 -1
- package/docs/testing-analysis.md +0 -395
- package/pomodoro-plan.md +0 -651
|
@@ -1,40 +1,101 @@
|
|
|
1
1
|
import type { Command, Event } from '@auto-engineer/message-bus';
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import type { SettledEvent } from '../projections/settled-instance-projection';
|
|
4
|
+
import type { PipelineEventStoreContext } from '../store/pipeline-event-store';
|
|
5
|
+
import { createPipelineEventStore } from '../store/pipeline-event-store';
|
|
3
6
|
import { SettledTracker } from './settled-tracker';
|
|
4
7
|
|
|
8
|
+
interface ESTrackerOptions {
|
|
9
|
+
onError?: (error: unknown, context: { commandTypes: readonly string[]; correlationId: string }) => void;
|
|
10
|
+
onDispatch?: (commandType: string, data: unknown, correlationId: string) => void;
|
|
11
|
+
onEventEmit?: (event: SettledEvent) => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createESTracker(ctx: PipelineEventStoreContext, options: ESTrackerOptions = {}): SettledTracker {
|
|
15
|
+
return new SettledTracker({
|
|
16
|
+
readModel: ctx.readModel,
|
|
17
|
+
onError: options.onError,
|
|
18
|
+
onDispatch: options.onDispatch,
|
|
19
|
+
onEventEmit: async (event) => {
|
|
20
|
+
await ctx.eventStore.appendToStream(`settled-${event.data.correlationId}`, [
|
|
21
|
+
{ type: event.type, data: event.data },
|
|
22
|
+
]);
|
|
23
|
+
await options.onEventEmit?.(event);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
5
28
|
describe('SettledTracker', () => {
|
|
6
29
|
let tracker: SettledTracker;
|
|
30
|
+
let ctx: PipelineEventStoreContext;
|
|
7
31
|
|
|
8
32
|
beforeEach(() => {
|
|
9
|
-
|
|
33
|
+
ctx = createPipelineEventStore();
|
|
34
|
+
tracker = createESTracker(ctx);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
await ctx.close();
|
|
10
39
|
});
|
|
11
40
|
|
|
12
41
|
describe('handler registration', () => {
|
|
13
|
-
it('should
|
|
42
|
+
it('should fire handler when registered command completes', async () => {
|
|
43
|
+
let fired = false;
|
|
44
|
+
|
|
14
45
|
tracker.registerHandler({
|
|
15
46
|
commandTypes: ['CheckTests', 'CheckTypes', 'CheckLint'],
|
|
16
|
-
handler: () => {
|
|
47
|
+
handler: () => {
|
|
48
|
+
fired = true;
|
|
49
|
+
},
|
|
17
50
|
});
|
|
18
51
|
|
|
19
|
-
|
|
52
|
+
await tracker.onCommandStarted({ type: 'CheckTests', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
53
|
+
await tracker.onCommandStarted({ type: 'CheckTypes', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
54
|
+
await tracker.onCommandStarted({ type: 'CheckLint', correlationId: 'c1', requestId: 'r3', data: {} });
|
|
55
|
+
|
|
56
|
+
await tracker.onEventReceived({ type: 'TestsCheckPassed', correlationId: 'c1', data: {} }, 'CheckTests');
|
|
57
|
+
await tracker.onEventReceived({ type: 'TypeCheckPassed', correlationId: 'c1', data: {} }, 'CheckTypes');
|
|
58
|
+
await tracker.onEventReceived({ type: 'LintCheckPassed', correlationId: 'c1', data: {} }, 'CheckLint');
|
|
59
|
+
|
|
60
|
+
expect(fired).toBe(true);
|
|
20
61
|
});
|
|
21
62
|
|
|
22
|
-
it('should
|
|
63
|
+
it('should fire multiple registered handlers independently', async () => {
|
|
64
|
+
let handler1Fired = false;
|
|
65
|
+
let handler2Fired = false;
|
|
66
|
+
|
|
23
67
|
tracker.registerHandler({
|
|
24
68
|
commandTypes: ['A', 'B'],
|
|
25
|
-
handler: () => {
|
|
69
|
+
handler: () => {
|
|
70
|
+
handler1Fired = true;
|
|
71
|
+
},
|
|
26
72
|
});
|
|
27
73
|
tracker.registerHandler({
|
|
28
74
|
commandTypes: ['C', 'D'],
|
|
29
|
-
handler: () => {
|
|
75
|
+
handler: () => {
|
|
76
|
+
handler2Fired = true;
|
|
77
|
+
},
|
|
30
78
|
});
|
|
31
79
|
|
|
32
|
-
|
|
80
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
81
|
+
await tracker.onCommandStarted({ type: 'B', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
82
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
83
|
+
await tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: {} }, 'B');
|
|
84
|
+
|
|
85
|
+
expect(handler1Fired).toBe(true);
|
|
86
|
+
expect(handler2Fired).toBe(false);
|
|
87
|
+
|
|
88
|
+
await tracker.onCommandStarted({ type: 'C', correlationId: 'c1', requestId: 'r3', data: {} });
|
|
89
|
+
await tracker.onCommandStarted({ type: 'D', correlationId: 'c1', requestId: 'r4', data: {} });
|
|
90
|
+
await tracker.onEventReceived({ type: 'CDone', correlationId: 'c1', data: {} }, 'C');
|
|
91
|
+
await tracker.onEventReceived({ type: 'DDone', correlationId: 'c1', data: {} }, 'D');
|
|
92
|
+
|
|
93
|
+
expect(handler2Fired).toBe(true);
|
|
33
94
|
});
|
|
34
95
|
});
|
|
35
96
|
|
|
36
97
|
describe('command tracking', () => {
|
|
37
|
-
it('should track multiple commands by correlationId', () => {
|
|
98
|
+
it('should track multiple commands by correlationId', async () => {
|
|
38
99
|
tracker.registerHandler({
|
|
39
100
|
commandTypes: ['CheckTests', 'CheckTypes', 'CheckLint'],
|
|
40
101
|
handler: () => {},
|
|
@@ -47,89 +108,98 @@ describe('SettledTracker', () => {
|
|
|
47
108
|
data: {},
|
|
48
109
|
};
|
|
49
110
|
|
|
50
|
-
tracker.onCommandStarted(command);
|
|
111
|
+
await tracker.onCommandStarted(command);
|
|
51
112
|
|
|
52
|
-
expect(tracker.
|
|
53
|
-
expect(tracker.
|
|
113
|
+
expect(await tracker.isWaitingForAsync('c1', 'CheckTests')).toBe(true);
|
|
114
|
+
expect(await tracker.isWaitingForAsync('c1', 'CheckTypes')).toBe(false);
|
|
54
115
|
});
|
|
55
116
|
|
|
56
|
-
it('should
|
|
117
|
+
it('should create handler instance when first tracked command arrives', async () => {
|
|
118
|
+
let fired = false;
|
|
119
|
+
|
|
57
120
|
tracker.registerHandler({
|
|
58
121
|
commandTypes: ['A', 'B'],
|
|
59
|
-
handler: () => {
|
|
122
|
+
handler: () => {
|
|
123
|
+
fired = true;
|
|
124
|
+
},
|
|
60
125
|
});
|
|
61
126
|
|
|
62
|
-
tracker.onCommandStarted({
|
|
63
|
-
type: 'A',
|
|
64
|
-
correlationId: 'c1',
|
|
65
|
-
requestId: 'r1',
|
|
66
|
-
data: {},
|
|
67
|
-
});
|
|
127
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
68
128
|
|
|
69
|
-
expect(tracker.
|
|
129
|
+
expect(await tracker.isWaitingForAsync('c1', 'A')).toBe(true);
|
|
130
|
+
expect(fired).toBe(false);
|
|
70
131
|
});
|
|
71
132
|
|
|
72
|
-
it('should not
|
|
133
|
+
it('should not fire handler multiple times for same correlationId when commands arrive separately', async () => {
|
|
134
|
+
let fireCount = 0;
|
|
135
|
+
|
|
73
136
|
tracker.registerHandler({
|
|
74
137
|
commandTypes: ['A', 'B'],
|
|
75
|
-
handler: () => {
|
|
138
|
+
handler: () => {
|
|
139
|
+
fireCount++;
|
|
140
|
+
},
|
|
76
141
|
});
|
|
77
142
|
|
|
78
|
-
tracker.onCommandStarted({
|
|
79
|
-
|
|
80
|
-
correlationId: 'c1',
|
|
81
|
-
requestId: 'r1',
|
|
82
|
-
data: {},
|
|
83
|
-
});
|
|
84
|
-
tracker.onCommandStarted({
|
|
85
|
-
type: 'B',
|
|
86
|
-
correlationId: 'c1',
|
|
87
|
-
requestId: 'r2',
|
|
88
|
-
data: {},
|
|
89
|
-
});
|
|
143
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
144
|
+
await tracker.onCommandStarted({ type: 'B', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
90
145
|
|
|
91
|
-
|
|
146
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
147
|
+
await tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: {} }, 'B');
|
|
148
|
+
|
|
149
|
+
expect(fireCount).toBe(1);
|
|
92
150
|
});
|
|
93
151
|
|
|
94
|
-
it('should ignore commands without correlationId', () => {
|
|
152
|
+
it('should ignore commands without correlationId', async () => {
|
|
153
|
+
let fired = false;
|
|
154
|
+
|
|
95
155
|
tracker.registerHandler({
|
|
96
156
|
commandTypes: ['A'],
|
|
97
|
-
handler: () => {
|
|
157
|
+
handler: () => {
|
|
158
|
+
fired = true;
|
|
159
|
+
},
|
|
98
160
|
});
|
|
99
161
|
|
|
100
|
-
tracker.onCommandStarted({
|
|
162
|
+
await tracker.onCommandStarted({
|
|
101
163
|
type: 'A',
|
|
102
164
|
requestId: 'r1',
|
|
103
165
|
data: {},
|
|
104
166
|
} as Command);
|
|
105
167
|
|
|
106
|
-
|
|
168
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
169
|
+
|
|
170
|
+
expect(fired).toBe(false);
|
|
107
171
|
});
|
|
108
172
|
|
|
109
|
-
it('should ignore commands without requestId', () => {
|
|
173
|
+
it('should ignore commands without requestId', async () => {
|
|
174
|
+
let fired = false;
|
|
175
|
+
|
|
110
176
|
tracker.registerHandler({
|
|
111
177
|
commandTypes: ['A'],
|
|
112
|
-
handler: () => {
|
|
178
|
+
handler: () => {
|
|
179
|
+
fired = true;
|
|
180
|
+
},
|
|
113
181
|
});
|
|
114
182
|
|
|
115
|
-
tracker.onCommandStarted({
|
|
183
|
+
await tracker.onCommandStarted({
|
|
116
184
|
type: 'A',
|
|
117
185
|
correlationId: 'c1',
|
|
118
186
|
data: {},
|
|
119
187
|
} as Command);
|
|
120
188
|
|
|
121
|
-
|
|
189
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
190
|
+
|
|
191
|
+
expect(fired).toBe(false);
|
|
122
192
|
});
|
|
123
193
|
});
|
|
124
194
|
|
|
125
195
|
describe('event routing', () => {
|
|
126
|
-
it('should mark command as complete when event received', () => {
|
|
196
|
+
it('should mark command as complete when event received', async () => {
|
|
127
197
|
tracker.registerHandler({
|
|
128
198
|
commandTypes: ['A'],
|
|
129
199
|
handler: () => {},
|
|
130
200
|
});
|
|
131
201
|
|
|
132
|
-
tracker.onCommandStarted({
|
|
202
|
+
await tracker.onCommandStarted({
|
|
133
203
|
type: 'A',
|
|
134
204
|
correlationId: 'c1',
|
|
135
205
|
requestId: 'r1',
|
|
@@ -142,12 +212,12 @@ describe('SettledTracker', () => {
|
|
|
142
212
|
data: {},
|
|
143
213
|
};
|
|
144
214
|
|
|
145
|
-
tracker.onEventReceived(event, 'A');
|
|
215
|
+
await tracker.onEventReceived(event, 'A');
|
|
146
216
|
|
|
147
|
-
expect(tracker.
|
|
217
|
+
expect(await tracker.isWaitingForAsync('c1', 'A')).toBe(true);
|
|
148
218
|
});
|
|
149
219
|
|
|
150
|
-
it('should collect events for each command type', () => {
|
|
220
|
+
it('should collect events for each command type', async () => {
|
|
151
221
|
let receivedEvents: Record<string, Event[]> = {};
|
|
152
222
|
|
|
153
223
|
tracker.registerHandler({
|
|
@@ -157,21 +227,21 @@ describe('SettledTracker', () => {
|
|
|
157
227
|
},
|
|
158
228
|
});
|
|
159
229
|
|
|
160
|
-
tracker.onCommandStarted({
|
|
230
|
+
await tracker.onCommandStarted({
|
|
161
231
|
type: 'A',
|
|
162
232
|
correlationId: 'c1',
|
|
163
233
|
requestId: 'r1',
|
|
164
234
|
data: {},
|
|
165
235
|
});
|
|
166
|
-
tracker.onCommandStarted({
|
|
236
|
+
await tracker.onCommandStarted({
|
|
167
237
|
type: 'B',
|
|
168
238
|
correlationId: 'c1',
|
|
169
239
|
requestId: 'r2',
|
|
170
240
|
data: {},
|
|
171
241
|
});
|
|
172
242
|
|
|
173
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: { foo: 1 } }, 'A');
|
|
174
|
-
tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: { bar: 2 } }, 'B');
|
|
243
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: { foo: 1 } }, 'A');
|
|
244
|
+
await tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: { bar: 2 } }, 'B');
|
|
175
245
|
|
|
176
246
|
expect(receivedEvents.A).toHaveLength(1);
|
|
177
247
|
expect(receivedEvents.A[0].type).toBe('ADone');
|
|
@@ -179,7 +249,7 @@ describe('SettledTracker', () => {
|
|
|
179
249
|
expect(receivedEvents.B[0].type).toBe('BDone');
|
|
180
250
|
});
|
|
181
251
|
|
|
182
|
-
it('should ignore events without correlationId', () => {
|
|
252
|
+
it('should ignore events without correlationId', async () => {
|
|
183
253
|
let handlerCalled = false;
|
|
184
254
|
|
|
185
255
|
tracker.registerHandler({
|
|
@@ -189,21 +259,21 @@ describe('SettledTracker', () => {
|
|
|
189
259
|
},
|
|
190
260
|
});
|
|
191
261
|
|
|
192
|
-
tracker.onCommandStarted({
|
|
262
|
+
await tracker.onCommandStarted({
|
|
193
263
|
type: 'A',
|
|
194
264
|
correlationId: 'c1',
|
|
195
265
|
requestId: 'r1',
|
|
196
266
|
data: {},
|
|
197
267
|
});
|
|
198
268
|
|
|
199
|
-
tracker.onEventReceived({ type: 'ADone', data: {} } as Event, 'A');
|
|
269
|
+
await tracker.onEventReceived({ type: 'ADone', data: {} } as Event, 'A');
|
|
200
270
|
|
|
201
271
|
expect(handlerCalled).toBe(false);
|
|
202
272
|
});
|
|
203
273
|
});
|
|
204
274
|
|
|
205
275
|
describe('handler execution', () => {
|
|
206
|
-
it('should fire handler when all commands complete', () => {
|
|
276
|
+
it('should fire handler when all commands complete', async () => {
|
|
207
277
|
let fired = false;
|
|
208
278
|
|
|
209
279
|
tracker.registerHandler({
|
|
@@ -213,27 +283,27 @@ describe('SettledTracker', () => {
|
|
|
213
283
|
},
|
|
214
284
|
});
|
|
215
285
|
|
|
216
|
-
tracker.onCommandStarted({
|
|
286
|
+
await tracker.onCommandStarted({
|
|
217
287
|
type: 'A',
|
|
218
288
|
correlationId: 'c1',
|
|
219
289
|
requestId: 'r1',
|
|
220
290
|
data: {},
|
|
221
291
|
});
|
|
222
|
-
tracker.onCommandStarted({
|
|
292
|
+
await tracker.onCommandStarted({
|
|
223
293
|
type: 'B',
|
|
224
294
|
correlationId: 'c1',
|
|
225
295
|
requestId: 'r2',
|
|
226
296
|
data: {},
|
|
227
297
|
});
|
|
228
298
|
|
|
229
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
299
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
230
300
|
expect(fired).toBe(false);
|
|
231
301
|
|
|
232
|
-
tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: {} }, 'B');
|
|
302
|
+
await tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: {} }, 'B');
|
|
233
303
|
expect(fired).toBe(true);
|
|
234
304
|
});
|
|
235
305
|
|
|
236
|
-
it('should not fire handler until all tracked commands have events', () => {
|
|
306
|
+
it('should not fire handler until all tracked commands have events', async () => {
|
|
237
307
|
let fireCount = 0;
|
|
238
308
|
|
|
239
309
|
tracker.registerHandler({
|
|
@@ -243,56 +313,53 @@ describe('SettledTracker', () => {
|
|
|
243
313
|
},
|
|
244
314
|
});
|
|
245
315
|
|
|
246
|
-
tracker.onCommandStarted({
|
|
316
|
+
await tracker.onCommandStarted({
|
|
247
317
|
type: 'CheckTests',
|
|
248
318
|
correlationId: 'c1',
|
|
249
319
|
requestId: 'r1',
|
|
250
320
|
data: {},
|
|
251
321
|
});
|
|
252
|
-
tracker.onCommandStarted({
|
|
322
|
+
await tracker.onCommandStarted({
|
|
253
323
|
type: 'CheckTypes',
|
|
254
324
|
correlationId: 'c1',
|
|
255
325
|
requestId: 'r2',
|
|
256
326
|
data: {},
|
|
257
327
|
});
|
|
258
|
-
tracker.onCommandStarted({
|
|
328
|
+
await tracker.onCommandStarted({
|
|
259
329
|
type: 'CheckLint',
|
|
260
330
|
correlationId: 'c1',
|
|
261
331
|
requestId: 'r3',
|
|
262
332
|
data: {},
|
|
263
333
|
});
|
|
264
334
|
|
|
265
|
-
tracker.onEventReceived({ type: 'TestsCheckPassed', correlationId: 'c1', data: {} }, 'CheckTests');
|
|
335
|
+
await tracker.onEventReceived({ type: 'TestsCheckPassed', correlationId: 'c1', data: {} }, 'CheckTests');
|
|
266
336
|
expect(fireCount).toBe(0);
|
|
267
337
|
|
|
268
|
-
tracker.onEventReceived({ type: 'TypeCheckPassed', correlationId: 'c1', data: {} }, 'CheckTypes');
|
|
338
|
+
await tracker.onEventReceived({ type: 'TypeCheckPassed', correlationId: 'c1', data: {} }, 'CheckTypes');
|
|
269
339
|
expect(fireCount).toBe(0);
|
|
270
340
|
|
|
271
|
-
tracker.onEventReceived({ type: 'LintCheckPassed', correlationId: 'c1', data: {} }, 'CheckLint');
|
|
341
|
+
await tracker.onEventReceived({ type: 'LintCheckPassed', correlationId: 'c1', data: {} }, 'CheckLint');
|
|
272
342
|
expect(fireCount).toBe(1);
|
|
273
343
|
});
|
|
274
344
|
|
|
275
|
-
it('should cleanup
|
|
345
|
+
it('should cleanup after handler fires by allowing new tracking for same correlationId', async () => {
|
|
346
|
+
let fireCount = 0;
|
|
347
|
+
|
|
276
348
|
tracker.registerHandler({
|
|
277
349
|
commandTypes: ['A'],
|
|
278
|
-
handler: () => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
tracker.onCommandStarted({
|
|
282
|
-
type: 'A',
|
|
283
|
-
correlationId: 'c1',
|
|
284
|
-
requestId: 'r1',
|
|
285
|
-
data: {},
|
|
350
|
+
handler: () => {
|
|
351
|
+
fireCount++;
|
|
352
|
+
},
|
|
286
353
|
});
|
|
287
354
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
355
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
356
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
357
|
+
expect(fireCount).toBe(1);
|
|
291
358
|
|
|
292
|
-
expect(tracker.
|
|
359
|
+
expect(await tracker.isWaitingForAsync('c1', 'A')).toBe(true);
|
|
293
360
|
});
|
|
294
361
|
|
|
295
|
-
it('should handle separate correlationIds independently', () => {
|
|
362
|
+
it('should handle separate correlationIds independently', async () => {
|
|
296
363
|
const firedFor: string[] = [];
|
|
297
364
|
|
|
298
365
|
tracker.registerHandler({
|
|
@@ -302,26 +369,26 @@ describe('SettledTracker', () => {
|
|
|
302
369
|
},
|
|
303
370
|
});
|
|
304
371
|
|
|
305
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
306
|
-
tracker.onCommandStarted({ type: 'B', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
307
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c2', requestId: 'r3', data: {} });
|
|
308
|
-
tracker.onCommandStarted({ type: 'B', correlationId: 'c2', requestId: 'r4', data: {} });
|
|
372
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
373
|
+
await tracker.onCommandStarted({ type: 'B', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
374
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c2', requestId: 'r3', data: {} });
|
|
375
|
+
await tracker.onCommandStarted({ type: 'B', correlationId: 'c2', requestId: 'r4', data: {} });
|
|
309
376
|
|
|
310
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
311
|
-
tracker.onEventReceived({ type: 'BDone', correlationId: 'c2', data: {} }, 'B');
|
|
377
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
378
|
+
await tracker.onEventReceived({ type: 'BDone', correlationId: 'c2', data: {} }, 'B');
|
|
312
379
|
|
|
313
380
|
expect(firedFor).toHaveLength(0);
|
|
314
381
|
|
|
315
|
-
tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: {} }, 'B');
|
|
382
|
+
await tracker.onEventReceived({ type: 'BDone', correlationId: 'c1', data: {} }, 'B');
|
|
316
383
|
expect(firedFor).toEqual(['c1']);
|
|
317
384
|
|
|
318
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c2', data: {} }, 'A');
|
|
385
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c2', data: {} }, 'A');
|
|
319
386
|
expect(firedFor).toEqual(['c1', 'c2']);
|
|
320
387
|
});
|
|
321
388
|
});
|
|
322
389
|
|
|
323
390
|
describe('persist for retry', () => {
|
|
324
|
-
it('should reset trackers when handler returns persist: true', () => {
|
|
391
|
+
it('should reset trackers when handler returns persist: true', async () => {
|
|
325
392
|
let callCount = 0;
|
|
326
393
|
|
|
327
394
|
tracker.registerHandler({
|
|
@@ -332,37 +399,42 @@ describe('SettledTracker', () => {
|
|
|
332
399
|
},
|
|
333
400
|
});
|
|
334
401
|
|
|
335
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
336
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
402
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
403
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
337
404
|
expect(callCount).toBe(1);
|
|
338
|
-
expect(tracker.getActiveInstanceCount()).toBe(1);
|
|
339
405
|
|
|
340
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
341
|
-
tracker.
|
|
406
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
407
|
+
expect(await tracker.isWaitingForAsync('c1', 'A')).toBe(true);
|
|
408
|
+
|
|
409
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
342
410
|
expect(callCount).toBe(2);
|
|
343
|
-
expect(tracker.getActiveInstanceCount()).toBe(1);
|
|
344
411
|
|
|
345
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r3', data: {} });
|
|
346
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
412
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r3', data: {} });
|
|
413
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
347
414
|
expect(callCount).toBe(3);
|
|
348
|
-
|
|
415
|
+
|
|
416
|
+
expect(await tracker.isWaitingForAsync('c1', 'A')).toBe(true);
|
|
349
417
|
});
|
|
350
418
|
|
|
351
|
-
it('should cleanup on handler error', () => {
|
|
419
|
+
it('should cleanup on handler error and not throw', async () => {
|
|
420
|
+
let handlerCalls = 0;
|
|
421
|
+
|
|
352
422
|
tracker.registerHandler({
|
|
353
423
|
commandTypes: ['A'],
|
|
354
424
|
handler: () => {
|
|
425
|
+
handlerCalls++;
|
|
355
426
|
throw new Error('Handler error');
|
|
356
427
|
},
|
|
357
428
|
});
|
|
358
429
|
|
|
359
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
430
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
360
431
|
|
|
361
|
-
expect(
|
|
362
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A')
|
|
363
|
-
|
|
432
|
+
await expect(
|
|
433
|
+
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A'),
|
|
434
|
+
).resolves.not.toThrow();
|
|
364
435
|
|
|
365
|
-
expect(
|
|
436
|
+
expect(handlerCalls).toBe(1);
|
|
437
|
+
expect(await tracker.isWaitingForAsync('c1', 'A')).toBe(false);
|
|
366
438
|
});
|
|
367
439
|
});
|
|
368
440
|
|
|
@@ -370,16 +442,16 @@ describe('SettledTracker', () => {
|
|
|
370
442
|
it('should accept onError callback in options', () => {
|
|
371
443
|
const onError = vi.fn();
|
|
372
444
|
|
|
373
|
-
tracker =
|
|
445
|
+
tracker = createESTracker(ctx, { onError });
|
|
374
446
|
|
|
375
447
|
expect(tracker).toBeInstanceOf(SettledTracker);
|
|
376
448
|
});
|
|
377
449
|
|
|
378
|
-
it('should call onError when handler throws', () => {
|
|
450
|
+
it('should call onError when handler throws', async () => {
|
|
379
451
|
const onError = vi.fn();
|
|
380
452
|
const thrownError = new Error('Handler failed');
|
|
381
453
|
|
|
382
|
-
tracker =
|
|
454
|
+
tracker = createESTracker(ctx, { onError });
|
|
383
455
|
|
|
384
456
|
tracker.registerHandler({
|
|
385
457
|
commandTypes: ['A'],
|
|
@@ -388,8 +460,8 @@ describe('SettledTracker', () => {
|
|
|
388
460
|
},
|
|
389
461
|
});
|
|
390
462
|
|
|
391
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
392
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
463
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
464
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
393
465
|
|
|
394
466
|
expect(onError).toHaveBeenCalledTimes(1);
|
|
395
467
|
expect(onError).toHaveBeenCalledWith(thrownError, {
|
|
@@ -400,10 +472,10 @@ describe('SettledTracker', () => {
|
|
|
400
472
|
});
|
|
401
473
|
|
|
402
474
|
describe('dispatch callback', () => {
|
|
403
|
-
it('should call onDispatch when provided', () => {
|
|
475
|
+
it('should call onDispatch when provided', async () => {
|
|
404
476
|
const dispatched: Array<{ type: string; data: unknown }> = [];
|
|
405
477
|
|
|
406
|
-
tracker =
|
|
478
|
+
tracker = createESTracker(ctx, {
|
|
407
479
|
onDispatch: (commandType, data, _correlationId) => {
|
|
408
480
|
dispatched.push({ type: commandType, data });
|
|
409
481
|
},
|
|
@@ -416,17 +488,17 @@ describe('SettledTracker', () => {
|
|
|
416
488
|
},
|
|
417
489
|
});
|
|
418
490
|
|
|
419
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
420
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
491
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
492
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
421
493
|
|
|
422
494
|
expect(dispatched).toHaveLength(1);
|
|
423
495
|
expect(dispatched[0]).toEqual({ type: 'FollowUp', data: { foo: 'bar' } });
|
|
424
496
|
});
|
|
425
497
|
|
|
426
|
-
it('should pass correlationId to onDispatch', () => {
|
|
498
|
+
it('should pass correlationId to onDispatch', async () => {
|
|
427
499
|
let receivedCorrelationId: string | undefined;
|
|
428
500
|
|
|
429
|
-
tracker =
|
|
501
|
+
tracker = createESTracker(ctx, {
|
|
430
502
|
onDispatch: (_commandType, _data, correlationId) => {
|
|
431
503
|
receivedCorrelationId = correlationId;
|
|
432
504
|
},
|
|
@@ -439,10 +511,534 @@ describe('SettledTracker', () => {
|
|
|
439
511
|
},
|
|
440
512
|
});
|
|
441
513
|
|
|
442
|
-
tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
443
|
-
tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
514
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
515
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
444
516
|
|
|
445
517
|
expect(receivedCorrelationId).toBe('c1');
|
|
446
518
|
});
|
|
447
519
|
});
|
|
520
|
+
|
|
521
|
+
describe('event emission', () => {
|
|
522
|
+
it('should emit SettledInstanceCreated when first command starts for a template', async () => {
|
|
523
|
+
const emittedEvents: SettledEvent[] = [];
|
|
524
|
+
|
|
525
|
+
tracker = createESTracker(ctx, {
|
|
526
|
+
onEventEmit: (event) => {
|
|
527
|
+
emittedEvents.push(event);
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
tracker.registerHandler({
|
|
532
|
+
commandTypes: ['A', 'B'],
|
|
533
|
+
handler: () => {},
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
537
|
+
|
|
538
|
+
expect(emittedEvents).toHaveLength(2);
|
|
539
|
+
expect(emittedEvents[0].type).toBe('SettledInstanceCreated');
|
|
540
|
+
expect(emittedEvents[0].data).toEqual({
|
|
541
|
+
templateId: 'template-A,B',
|
|
542
|
+
correlationId: 'c1',
|
|
543
|
+
commandTypes: ['A', 'B'],
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should emit SettledCommandStarted when command starts', async () => {
|
|
548
|
+
const emittedEvents: SettledEvent[] = [];
|
|
549
|
+
|
|
550
|
+
tracker = createESTracker(ctx, {
|
|
551
|
+
onEventEmit: (event) => {
|
|
552
|
+
emittedEvents.push(event);
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
tracker.registerHandler({
|
|
557
|
+
commandTypes: ['A', 'B'],
|
|
558
|
+
handler: () => {},
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
562
|
+
|
|
563
|
+
expect(emittedEvents[1].type).toBe('SettledCommandStarted');
|
|
564
|
+
expect(emittedEvents[1].data).toEqual({
|
|
565
|
+
templateId: 'template-A,B',
|
|
566
|
+
correlationId: 'c1',
|
|
567
|
+
commandType: 'A',
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should not emit SettledInstanceCreated for subsequent commands in same instance', async () => {
|
|
572
|
+
const emittedEvents: SettledEvent[] = [];
|
|
573
|
+
|
|
574
|
+
tracker = createESTracker(ctx, {
|
|
575
|
+
onEventEmit: (event) => {
|
|
576
|
+
emittedEvents.push(event);
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
tracker.registerHandler({
|
|
581
|
+
commandTypes: ['A', 'B'],
|
|
582
|
+
handler: () => {},
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
586
|
+
await tracker.onCommandStarted({ type: 'B', correlationId: 'c1', requestId: 'r2', data: {} });
|
|
587
|
+
|
|
588
|
+
const createdEvents = emittedEvents.filter((e) => e.type === 'SettledInstanceCreated');
|
|
589
|
+
expect(createdEvents).toHaveLength(1);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should emit SettledEventReceived when event is received', async () => {
|
|
593
|
+
const emittedEvents: SettledEvent[] = [];
|
|
594
|
+
|
|
595
|
+
tracker = createESTracker(ctx, {
|
|
596
|
+
onEventEmit: (event) => {
|
|
597
|
+
emittedEvents.push(event);
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
tracker.registerHandler({
|
|
602
|
+
commandTypes: ['A'],
|
|
603
|
+
handler: () => {},
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
607
|
+
emittedEvents.length = 0;
|
|
608
|
+
|
|
609
|
+
const domainEvent: Event = { type: 'ADone', correlationId: 'c1', data: { result: 'ok' } };
|
|
610
|
+
await tracker.onEventReceived(domainEvent, 'A');
|
|
611
|
+
|
|
612
|
+
expect(emittedEvents[0].type).toBe('SettledEventReceived');
|
|
613
|
+
expect(emittedEvents[0].data).toEqual({
|
|
614
|
+
templateId: 'template-A',
|
|
615
|
+
correlationId: 'c1',
|
|
616
|
+
commandType: 'A',
|
|
617
|
+
event: domainEvent,
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('should emit SettledHandlerFired when handler fires', async () => {
|
|
622
|
+
const emittedEvents: SettledEvent[] = [];
|
|
623
|
+
|
|
624
|
+
tracker = createESTracker(ctx, {
|
|
625
|
+
onEventEmit: (event) => {
|
|
626
|
+
emittedEvents.push(event);
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
tracker.registerHandler({
|
|
631
|
+
commandTypes: ['A'],
|
|
632
|
+
handler: () => {},
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
636
|
+
emittedEvents.length = 0;
|
|
637
|
+
|
|
638
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
639
|
+
|
|
640
|
+
const firedEvent = emittedEvents.find((e) => e.type === 'SettledHandlerFired');
|
|
641
|
+
expect(firedEvent).toBeDefined();
|
|
642
|
+
expect(firedEvent?.data).toEqual({
|
|
643
|
+
templateId: 'template-A',
|
|
644
|
+
correlationId: 'c1',
|
|
645
|
+
persist: false,
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('should emit SettledInstanceReset when handler fires without persist', async () => {
|
|
650
|
+
const emittedEvents: SettledEvent[] = [];
|
|
651
|
+
|
|
652
|
+
tracker = createESTracker(ctx, {
|
|
653
|
+
onEventEmit: (event) => {
|
|
654
|
+
emittedEvents.push(event);
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
tracker.registerHandler({
|
|
659
|
+
commandTypes: ['A'],
|
|
660
|
+
handler: () => {},
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
664
|
+
emittedEvents.length = 0;
|
|
665
|
+
|
|
666
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
667
|
+
|
|
668
|
+
const resetEvent = emittedEvents.find((e) => e.type === 'SettledInstanceReset');
|
|
669
|
+
expect(resetEvent).toBeDefined();
|
|
670
|
+
expect(resetEvent?.data).toEqual({
|
|
671
|
+
templateId: 'template-A',
|
|
672
|
+
correlationId: 'c1',
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('should emit SettledInstanceReset when handler returns persist: true', async () => {
|
|
677
|
+
const emittedEvents: SettledEvent[] = [];
|
|
678
|
+
|
|
679
|
+
tracker = createESTracker(ctx, {
|
|
680
|
+
onEventEmit: (event) => {
|
|
681
|
+
emittedEvents.push(event);
|
|
682
|
+
},
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
tracker.registerHandler({
|
|
686
|
+
commandTypes: ['A'],
|
|
687
|
+
handler: () => ({ persist: true }),
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
691
|
+
emittedEvents.length = 0;
|
|
692
|
+
|
|
693
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
694
|
+
|
|
695
|
+
const firedEvent = emittedEvents.find((e) => e.type === 'SettledHandlerFired');
|
|
696
|
+
expect(firedEvent?.data).toEqual({
|
|
697
|
+
templateId: 'template-A',
|
|
698
|
+
correlationId: 'c1',
|
|
699
|
+
persist: true,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
const resetEvent = emittedEvents.find((e) => e.type === 'SettledInstanceReset');
|
|
703
|
+
expect(resetEvent).toBeDefined();
|
|
704
|
+
expect(resetEvent?.data).toEqual({
|
|
705
|
+
templateId: 'template-A',
|
|
706
|
+
correlationId: 'c1',
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it('should emit SettledInstanceCleaned on handler error', async () => {
|
|
711
|
+
const emittedEvents: SettledEvent[] = [];
|
|
712
|
+
|
|
713
|
+
tracker = createESTracker(ctx, {
|
|
714
|
+
onEventEmit: (event) => {
|
|
715
|
+
emittedEvents.push(event);
|
|
716
|
+
},
|
|
717
|
+
onError: () => {},
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
tracker.registerHandler({
|
|
721
|
+
commandTypes: ['A'],
|
|
722
|
+
handler: () => {
|
|
723
|
+
throw new Error('Handler error');
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
await tracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
728
|
+
emittedEvents.length = 0;
|
|
729
|
+
|
|
730
|
+
await tracker.onEventReceived({ type: 'ADone', correlationId: 'c1', data: {} }, 'A');
|
|
731
|
+
|
|
732
|
+
const cleanedEvent = emittedEvents.find((e) => e.type === 'SettledInstanceCleaned');
|
|
733
|
+
expect(cleanedEvent).toBeDefined();
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
describe('projection-based state (full ES)', () => {
|
|
738
|
+
it('should query instance state from readModel after emitting events', async () => {
|
|
739
|
+
const { eventStore, readModel, close } = createPipelineEventStore();
|
|
740
|
+
|
|
741
|
+
try {
|
|
742
|
+
const emittedEvents: SettledEvent[] = [];
|
|
743
|
+
|
|
744
|
+
const esTracker = new SettledTracker({
|
|
745
|
+
readModel,
|
|
746
|
+
onEventEmit: async (event) => {
|
|
747
|
+
emittedEvents.push(event);
|
|
748
|
+
await eventStore.appendToStream(`settled-${event.data.correlationId}`, [
|
|
749
|
+
{ type: event.type, data: event.data },
|
|
750
|
+
]);
|
|
751
|
+
},
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
esTracker.registerHandler({
|
|
755
|
+
commandTypes: ['A', 'B'],
|
|
756
|
+
handler: () => {},
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
await esTracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
760
|
+
|
|
761
|
+
const instance = await readModel.getSettledInstance('template-A,B', 'c1');
|
|
762
|
+
|
|
763
|
+
expect(instance).not.toBeNull();
|
|
764
|
+
expect(instance?.status).toBe('active');
|
|
765
|
+
expect(instance?.commandTrackers).toHaveLength(2);
|
|
766
|
+
|
|
767
|
+
const trackerA = instance?.commandTrackers.find((t) => t.commandType === 'A');
|
|
768
|
+
expect(trackerA?.hasStarted).toBe(true);
|
|
769
|
+
expect(trackerA?.hasCompleted).toBe(false);
|
|
770
|
+
} finally {
|
|
771
|
+
await close();
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('should derive isWaitingFor from projection query', async () => {
|
|
776
|
+
const { eventStore, readModel, close } = createPipelineEventStore();
|
|
777
|
+
|
|
778
|
+
try {
|
|
779
|
+
const esTracker = new SettledTracker({
|
|
780
|
+
readModel,
|
|
781
|
+
onEventEmit: async (event) => {
|
|
782
|
+
await eventStore.appendToStream(`settled-${event.data.correlationId}`, [
|
|
783
|
+
{ type: event.type, data: event.data },
|
|
784
|
+
]);
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
esTracker.registerHandler({
|
|
789
|
+
commandTypes: ['A'],
|
|
790
|
+
handler: () => {},
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
await esTracker.onCommandStarted({ type: 'A', correlationId: 'c1', requestId: 'r1', data: {} });
|
|
794
|
+
|
|
795
|
+
const isWaiting = await esTracker.isWaitingForAsync('c1', 'A');
|
|
796
|
+
expect(isWaiting).toBe(true);
|
|
797
|
+
|
|
798
|
+
const isWaitingB = await esTracker.isWaitingForAsync('c1', 'B');
|
|
799
|
+
expect(isWaitingB).toBe(false);
|
|
800
|
+
} finally {
|
|
801
|
+
await close();
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('should create new instance after previous instance was cleaned (retry scenario)', async () => {
|
|
806
|
+
const { eventStore, readModel, close } = createPipelineEventStore();
|
|
807
|
+
|
|
808
|
+
try {
|
|
809
|
+
let handlerCallCount = 0;
|
|
810
|
+
const dispatched: Array<{ commandType: string; data: unknown }> = [];
|
|
811
|
+
|
|
812
|
+
const esTracker = new SettledTracker({
|
|
813
|
+
readModel,
|
|
814
|
+
onDispatch: (commandType, data) => {
|
|
815
|
+
dispatched.push({ commandType, data });
|
|
816
|
+
},
|
|
817
|
+
onEventEmit: async (event) => {
|
|
818
|
+
await eventStore.appendToStream(`settled-${event.data.correlationId}`, [
|
|
819
|
+
{ type: event.type, data: event.data },
|
|
820
|
+
]);
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
esTracker.registerHandler({
|
|
825
|
+
commandTypes: ['CheckTypes'],
|
|
826
|
+
handler: (events, send) => {
|
|
827
|
+
handlerCallCount++;
|
|
828
|
+
const checkTypesEvents = events.CheckTypes ?? [];
|
|
829
|
+
const failedEvent = checkTypesEvents.find((e) => e.type === 'TypeCheckFailed');
|
|
830
|
+
if (failedEvent) {
|
|
831
|
+
send('ImplementSlice', { retry: true, errors: failedEvent.data });
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
await esTracker.onCommandStarted({
|
|
837
|
+
type: 'CheckTypes',
|
|
838
|
+
correlationId: 'c1',
|
|
839
|
+
requestId: 'r1',
|
|
840
|
+
data: { target: './slice1' },
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
await esTracker.onEventReceived(
|
|
844
|
+
{ type: 'TypeCheckFailed', correlationId: 'c1', data: { errors: 'TS2322' } },
|
|
845
|
+
'CheckTypes',
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
expect(handlerCallCount).toBe(1);
|
|
849
|
+
expect(dispatched).toHaveLength(1);
|
|
850
|
+
expect(dispatched[0].commandType).toBe('ImplementSlice');
|
|
851
|
+
|
|
852
|
+
dispatched.length = 0;
|
|
853
|
+
|
|
854
|
+
await esTracker.onCommandStarted({
|
|
855
|
+
type: 'CheckTypes',
|
|
856
|
+
correlationId: 'c1',
|
|
857
|
+
requestId: 'r2',
|
|
858
|
+
data: { target: './slice1' },
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
await esTracker.onEventReceived(
|
|
862
|
+
{ type: 'TypeCheckPassed', correlationId: 'c1', data: { target: './slice1' } },
|
|
863
|
+
'CheckTypes',
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
expect(handlerCallCount).toBe(2);
|
|
867
|
+
} finally {
|
|
868
|
+
await close();
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
it('should fire handler for each completion when concurrent commands run (concurrent slices scenario)', async () => {
|
|
873
|
+
const { eventStore, readModel, close } = createPipelineEventStore();
|
|
874
|
+
|
|
875
|
+
try {
|
|
876
|
+
let handlerCallCount = 0;
|
|
877
|
+
const dispatched: Array<{ commandType: string; data: unknown }> = [];
|
|
878
|
+
|
|
879
|
+
const esTracker = new SettledTracker({
|
|
880
|
+
readModel,
|
|
881
|
+
onDispatch: (commandType, data) => {
|
|
882
|
+
dispatched.push({ commandType, data });
|
|
883
|
+
},
|
|
884
|
+
onEventEmit: async (event) => {
|
|
885
|
+
await eventStore.appendToStream(`settled-${event.data.correlationId}`, [
|
|
886
|
+
{ type: event.type, data: event.data },
|
|
887
|
+
]);
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
esTracker.registerHandler({
|
|
892
|
+
commandTypes: ['CheckTypes'],
|
|
893
|
+
handler: (events, send) => {
|
|
894
|
+
handlerCallCount++;
|
|
895
|
+
const checkTypesEvents = events.CheckTypes ?? [];
|
|
896
|
+
const failedEvent = checkTypesEvents.find((e) => e.type === 'TypeCheckFailed');
|
|
897
|
+
if (failedEvent) {
|
|
898
|
+
send('ImplementSlice', { retry: true, errors: failedEvent.data });
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
await esTracker.onCommandStarted({
|
|
904
|
+
type: 'CheckTypes',
|
|
905
|
+
correlationId: 'c1',
|
|
906
|
+
requestId: 'r1',
|
|
907
|
+
data: { target: './slice1' },
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
await esTracker.onCommandStarted({
|
|
911
|
+
type: 'CheckTypes',
|
|
912
|
+
correlationId: 'c1',
|
|
913
|
+
requestId: 'r2',
|
|
914
|
+
data: { target: './slice2' },
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
await esTracker.onEventReceived(
|
|
918
|
+
{ type: 'TypeCheckPassed', correlationId: 'c1', data: { target: './slice1' } },
|
|
919
|
+
'CheckTypes',
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
expect(handlerCallCount).toBe(1);
|
|
923
|
+
|
|
924
|
+
await esTracker.onEventReceived(
|
|
925
|
+
{ type: 'TypeCheckFailed', correlationId: 'c1', data: { errors: 'TS2322', target: './slice2' } },
|
|
926
|
+
'CheckTypes',
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
expect(handlerCallCount).toBe(2);
|
|
930
|
+
expect(dispatched).toHaveLength(1);
|
|
931
|
+
expect(dispatched[0].commandType).toBe('ImplementSlice');
|
|
932
|
+
} finally {
|
|
933
|
+
await close();
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
it('should fire handler for interleaved slices when one fails (production scenario)', async () => {
|
|
938
|
+
const { eventStore, readModel, close } = createPipelineEventStore();
|
|
939
|
+
|
|
940
|
+
try {
|
|
941
|
+
let handlerCallCount = 0;
|
|
942
|
+
const dispatched: Array<{ commandType: string; data: unknown }> = [];
|
|
943
|
+
|
|
944
|
+
const esTracker = new SettledTracker({
|
|
945
|
+
readModel,
|
|
946
|
+
onDispatch: (commandType, data) => {
|
|
947
|
+
dispatched.push({ commandType, data });
|
|
948
|
+
},
|
|
949
|
+
onEventEmit: async (event) => {
|
|
950
|
+
await eventStore.appendToStream(`settled-${event.data.correlationId}`, [
|
|
951
|
+
{ type: event.type, data: event.data },
|
|
952
|
+
]);
|
|
953
|
+
},
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
esTracker.registerHandler({
|
|
957
|
+
commandTypes: ['CheckTests', 'CheckTypes', 'CheckLint'],
|
|
958
|
+
handler: (events, send) => {
|
|
959
|
+
handlerCallCount++;
|
|
960
|
+
const allEvents = [...(events.CheckTests ?? []), ...(events.CheckTypes ?? []), ...(events.CheckLint ?? [])];
|
|
961
|
+
const failedEvent = allEvents.find((e) => e.type.includes('Failed'));
|
|
962
|
+
if (failedEvent) {
|
|
963
|
+
send('ImplementSlice', { retry: true, errors: failedEvent.data });
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
await esTracker.onCommandStarted({
|
|
969
|
+
type: 'CheckTests',
|
|
970
|
+
correlationId: 'c1',
|
|
971
|
+
requestId: 'slice2-tests',
|
|
972
|
+
data: { target: './slice2' },
|
|
973
|
+
});
|
|
974
|
+
await esTracker.onCommandStarted({
|
|
975
|
+
type: 'CheckTypes',
|
|
976
|
+
correlationId: 'c1',
|
|
977
|
+
requestId: 'slice2-types',
|
|
978
|
+
data: { target: './slice2' },
|
|
979
|
+
});
|
|
980
|
+
await esTracker.onCommandStarted({
|
|
981
|
+
type: 'CheckLint',
|
|
982
|
+
correlationId: 'c1',
|
|
983
|
+
requestId: 'slice2-lint',
|
|
984
|
+
data: { target: './slice2' },
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
await esTracker.onEventReceived(
|
|
988
|
+
{ type: 'LintCheckPassed', correlationId: 'c1', data: { target: './slice2' } },
|
|
989
|
+
'CheckLint',
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
await esTracker.onCommandStarted({
|
|
993
|
+
type: 'CheckTests',
|
|
994
|
+
correlationId: 'c1',
|
|
995
|
+
requestId: 'slice3-tests',
|
|
996
|
+
data: { target: './slice3' },
|
|
997
|
+
});
|
|
998
|
+
await esTracker.onCommandStarted({
|
|
999
|
+
type: 'CheckTypes',
|
|
1000
|
+
correlationId: 'c1',
|
|
1001
|
+
requestId: 'slice3-types',
|
|
1002
|
+
data: { target: './slice3' },
|
|
1003
|
+
});
|
|
1004
|
+
await esTracker.onCommandStarted({
|
|
1005
|
+
type: 'CheckLint',
|
|
1006
|
+
correlationId: 'c1',
|
|
1007
|
+
requestId: 'slice3-lint',
|
|
1008
|
+
data: { target: './slice3' },
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
await esTracker.onEventReceived(
|
|
1012
|
+
{ type: 'TypeCheckPassed', correlationId: 'c1', data: { target: './slice2' } },
|
|
1013
|
+
'CheckTypes',
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
await esTracker.onEventReceived(
|
|
1017
|
+
{ type: 'TestsCheckPassed', correlationId: 'c1', data: { target: './slice2' } },
|
|
1018
|
+
'CheckTests',
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
expect(handlerCallCount).toBe(1);
|
|
1022
|
+
|
|
1023
|
+
await esTracker.onEventReceived(
|
|
1024
|
+
{ type: 'LintCheckPassed', correlationId: 'c1', data: { target: './slice3' } },
|
|
1025
|
+
'CheckLint',
|
|
1026
|
+
);
|
|
1027
|
+
await esTracker.onEventReceived(
|
|
1028
|
+
{ type: 'TypeCheckFailed', correlationId: 'c1', data: { errors: 'TS2322', target: './slice3' } },
|
|
1029
|
+
'CheckTypes',
|
|
1030
|
+
);
|
|
1031
|
+
await esTracker.onEventReceived(
|
|
1032
|
+
{ type: 'TestsCheckPassed', correlationId: 'c1', data: { target: './slice3' } },
|
|
1033
|
+
'CheckTests',
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
expect(handlerCallCount).toBe(2);
|
|
1037
|
+
expect(dispatched).toHaveLength(1);
|
|
1038
|
+
expect(dispatched[0].commandType).toBe('ImplementSlice');
|
|
1039
|
+
} finally {
|
|
1040
|
+
await close();
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
448
1044
|
});
|