@atolis-hq/wake 0.3.38 → 0.3.40
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.
|
@@ -1,39 +1,46 @@
|
|
|
1
1
|
import { correlationId, EventActorKind, } from '../../kernel/index.js';
|
|
2
2
|
import { selectOrchestrationEvent } from '../contracts/event-decoder.js';
|
|
3
|
+
import { OrchestrationEventType, } from '../contracts/events.js';
|
|
3
4
|
import { workflowInstanceId, workflowName, } from '../contracts/identifiers.js';
|
|
5
|
+
import { workflowInstanceStream } from '../contracts/streams.js';
|
|
6
|
+
import { ApprovalAuthorityKind } from '../contracts/vocabulary.js';
|
|
4
7
|
import { resolveTriggerWorkflowInstanceId } from './trigger-workflow-instance.js';
|
|
5
8
|
const checkpoint = 'reactor:orchestration.watch';
|
|
9
|
+
const reconciliationCheckpoint = 'reconciler:orchestration.watch';
|
|
6
10
|
export function createWatchReactor(orchestration, journal, checkpoints, runs) {
|
|
7
11
|
return {
|
|
8
12
|
async react(event, context) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
13
|
+
await dispatch(orchestration, runs, event, context, await orchestration.listWatchMatches(event, context));
|
|
14
|
+
},
|
|
15
|
+
async reconcileOnce(limit = 100) {
|
|
16
|
+
if (journal === undefined || checkpoints === undefined)
|
|
17
|
+
throw new Error('WatchReactor journal and checkpoints are required to reconcile');
|
|
18
|
+
if (orchestration.listWaiting === undefined)
|
|
19
|
+
throw new Error('WatchReactor listWaiting is required to reconcile');
|
|
20
|
+
const events = await journal.readAll(await checkpoints.load(reconciliationCheckpoint), limit);
|
|
21
|
+
const waiting = new Map((await orchestration.listWaiting()).map((parent) => [
|
|
22
|
+
parent.workflowInstanceId,
|
|
23
|
+
new Set((parent.waitingFor?.from ?? []).flatMap((authority) => authority.kind === ApprovalAuthorityKind.Watch && authority.watch !== undefined
|
|
24
|
+
? [authority.watch]
|
|
25
|
+
: [])),
|
|
26
|
+
]));
|
|
27
|
+
let reconciled = 0;
|
|
28
|
+
for (const event of events) {
|
|
29
|
+
const context = commandContext(event);
|
|
30
|
+
const matches = (await Promise.all((await orchestration.listWatchMatches(event, context)).map(async (match) => {
|
|
31
|
+
const watches = waiting.get(match.parent.workflowInstanceId);
|
|
32
|
+
return watches?.has(match.watch.id) === true &&
|
|
33
|
+
!(await hasDurableWatchOutcome(orchestration, journal, match, event.eventId))
|
|
34
|
+
? match
|
|
35
|
+
: null;
|
|
36
|
+
}))).filter((match) => match !== null);
|
|
37
|
+
if (matches.length > 0) {
|
|
38
|
+
await dispatch(orchestration, runs, event, context, matches);
|
|
39
|
+
reconciled += 1;
|
|
32
40
|
}
|
|
33
|
-
|
|
34
|
-
if (result === undefined || result === null)
|
|
35
|
-
throw new Error(`Watch child request ${request.requestId} did not complete durably`);
|
|
41
|
+
await checkpoints.save(reconciliationCheckpoint, event.globalPosition);
|
|
36
42
|
}
|
|
43
|
+
return reconciled;
|
|
37
44
|
},
|
|
38
45
|
async runOnce(limit = 100) {
|
|
39
46
|
if (journal === undefined || checkpoints === undefined)
|
|
@@ -47,6 +54,45 @@ export function createWatchReactor(orchestration, journal, checkpoints, runs) {
|
|
|
47
54
|
},
|
|
48
55
|
};
|
|
49
56
|
}
|
|
57
|
+
async function dispatch(orchestration, runs, event, context, matches) {
|
|
58
|
+
const causalCycle = orchestrationCausalCycleId(selectOrchestrationEvent(event));
|
|
59
|
+
const sourceWorkflowInstanceId = await resolveTriggerWorkflowInstanceId(event, runs);
|
|
60
|
+
for (const match of matches) {
|
|
61
|
+
if (sourceWorkflowInstanceId !== undefined &&
|
|
62
|
+
match.parent.workflowInstanceId !== sourceWorkflowInstanceId)
|
|
63
|
+
continue;
|
|
64
|
+
const requestId = workflowInstanceId(`${match.parent.workflowInstanceId}:watch:${match.watch.id}:trigger:${event.eventId}`);
|
|
65
|
+
const request = {
|
|
66
|
+
parentWorkflowInstanceId: match.parent.workflowInstanceId,
|
|
67
|
+
watchId: match.watch.id,
|
|
68
|
+
triggerId: event.eventId,
|
|
69
|
+
workflowName: workflowName(match.watch.workflow),
|
|
70
|
+
causalCycleId: causalCycle ?? requestId,
|
|
71
|
+
requestId,
|
|
72
|
+
maxPerGroup: match.watch.maxPerGroup,
|
|
73
|
+
};
|
|
74
|
+
const requestContext = { ...context, commandId: watchCommandId(context, match, event) };
|
|
75
|
+
if ((await orchestration.isCausalRepeat?.(match.parent.workflowInstanceId, event.eventId, causalCycle, request.requestId)) === true) {
|
|
76
|
+
await orchestration.rejectCausalActivation(request, requestContext);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const result = await orchestration.requestChild(request, requestContext);
|
|
80
|
+
if (result === undefined || result === null)
|
|
81
|
+
throw new Error(`Watch child request ${request.requestId} did not complete durably`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function hasDurableWatchOutcome(orchestration, journal, match, triggerId) {
|
|
85
|
+
const requestId = workflowInstanceId(`${match.parent.workflowInstanceId}:watch:${match.watch.id}:trigger:${triggerId}`);
|
|
86
|
+
const child = await orchestration.get?.(requestId);
|
|
87
|
+
if (child !== undefined && child !== null)
|
|
88
|
+
return true;
|
|
89
|
+
const parentEvents = await journal.readStream(workflowInstanceStream(match.parent.workflowInstanceId));
|
|
90
|
+
return parentEvents.some((event) => {
|
|
91
|
+
const owned = selectOrchestrationEvent(event);
|
|
92
|
+
return (owned?.eventType === OrchestrationEventType.GroupBudgetExhausted &&
|
|
93
|
+
owned.payload.requestId === requestId);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
50
96
|
function commandContext(event) {
|
|
51
97
|
return {
|
|
52
98
|
commandId: `${event.eventId}:watch`,
|