@atolis-hq/wake 0.3.76 → 0.3.78
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/dist/src/bootstrap/persistence-composition.js +1 -0
- package/dist/src/bootstrap/surface-api-execution-applications.js +3 -3
- package/dist/src/bootstrap/surface-api-run-context.js +44 -0
- package/dist/src/bootstrap/surface-api-work-applications.js +2 -2
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/execution/application/run-repository.js +16 -10
- package/dist/src/integrations/application/artifact-registration-reactor.js +15 -9
- package/dist/src/integrations/delivery/application/delivery-projector.js +9 -3
- package/dist/src/integrations/github/contracts/config.js +8 -1
- package/dist/src/integrations/github/infrastructure/source.js +10 -1
- package/dist/src/kernel/index.js +1 -0
- package/dist/src/kernel/infrastructure/cached-journal-view.js +21 -0
- package/dist/src/orchestration/application/orchestration-repository.js +24 -19
- package/dist/src/persistence/filesystem/file-event-journal.js +4 -0
- package/dist/src/persistence/memory/in-memory-event-journal.js +3 -0
- package/dist/src/surfaces/web-assets/assets/{index-CBE1yusZ.js → index-CRUtOkcs.js} +9 -9
- package/dist/src/surfaces/web-assets/index.html +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ function serializeJournalAppends(journal) {
|
|
|
12
12
|
},
|
|
13
13
|
readStream: (stream) => journal.readStream(stream),
|
|
14
14
|
readAll: (afterGlobalPosition, limit) => journal.readAll(afterGlobalPosition, limit),
|
|
15
|
+
latestGlobalPosition: () => journal.latestGlobalPosition(),
|
|
15
16
|
...(journal.readLatest === undefined
|
|
16
17
|
? {}
|
|
17
18
|
: {
|
|
@@ -4,7 +4,7 @@ import { correlationId, EventActorKind } from '../kernel/index.js';
|
|
|
4
4
|
import { ApiCommandStatus, presentRun } from '../surfaces/index.js';
|
|
5
5
|
import { projectionMeta, sampledMeta } from './surface-api-metadata.js';
|
|
6
6
|
import { projectionPage } from './surface-api-projection-pages.js';
|
|
7
|
-
import {
|
|
7
|
+
import { enrichRun } from './surface-api-run-context.js';
|
|
8
8
|
import { readWorkTranscript } from './surface-api-transcripts.js';
|
|
9
9
|
export function createExecutionApplications(root, now) {
|
|
10
10
|
return {
|
|
@@ -43,7 +43,7 @@ export function createExecutionApplications(root, now) {
|
|
|
43
43
|
});
|
|
44
44
|
return {
|
|
45
45
|
...page,
|
|
46
|
-
items: await Promise.all(page.items.map((item) =>
|
|
46
|
+
items: await Promise.all(page.items.map((item) => enrichRun(root, item))),
|
|
47
47
|
};
|
|
48
48
|
},
|
|
49
49
|
async get(runId) {
|
|
@@ -51,7 +51,7 @@ export function createExecutionApplications(root, now) {
|
|
|
51
51
|
if (stored?.value.view == null)
|
|
52
52
|
return undefined;
|
|
53
53
|
return {
|
|
54
|
-
data: await
|
|
54
|
+
data: await enrichRun(root, presentRun(stored.value.view)),
|
|
55
55
|
meta: await projectionMeta(root.journal, [stored], now()),
|
|
56
56
|
};
|
|
57
57
|
},
|
|
@@ -1,5 +1,49 @@
|
|
|
1
|
+
import { ActivityOutcomeKind } from '../activities/index.js';
|
|
2
|
+
import { DeliveryState, IntegrationStreamKind, } from '../integrations/index.js';
|
|
1
3
|
import { workflowInstanceId } from '../orchestration/index.js';
|
|
2
4
|
export async function withWorkflowContext(root, run) {
|
|
3
5
|
const instance = await root.orchestration.get(workflowInstanceId(run.workflowInstanceId));
|
|
4
6
|
return instance === null ? run : { ...run, workflowName: instance.workflowName };
|
|
5
7
|
}
|
|
8
|
+
const deliveryResolutionSentinels = {
|
|
9
|
+
[DeliveryState.Confirmed]: 'DONE',
|
|
10
|
+
[DeliveryState.Failed]: 'FAILED',
|
|
11
|
+
[DeliveryState.Ambiguous]: 'AMBIGUOUS',
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A run whose own outcome was `waiting` on a delivery is frozen at that
|
|
15
|
+
* sentinel forever (the run itself never re-fires once terminal). Join it
|
|
16
|
+
* against the delivery intent it named to surface how that delivery has
|
|
17
|
+
* since resolved, without rewriting the run's own history.
|
|
18
|
+
*/
|
|
19
|
+
export async function withDeliveryResolution(root, run) {
|
|
20
|
+
const intentEventId = waitingDeliveryIntentEventId(run);
|
|
21
|
+
if (intentEventId === undefined)
|
|
22
|
+
return run;
|
|
23
|
+
const stored = await root.projections.read(IntegrationStreamKind.Delivery, intentEventId);
|
|
24
|
+
const delivery = stored?.value;
|
|
25
|
+
if (delivery === undefined || delivery.resolvedAt === undefined)
|
|
26
|
+
return run;
|
|
27
|
+
const sentinel = deliveryResolutionSentinels[delivery.state];
|
|
28
|
+
return sentinel === undefined
|
|
29
|
+
? run
|
|
30
|
+
: { ...run, resolution: { sentinel, resolvedAt: delivery.resolvedAt } };
|
|
31
|
+
}
|
|
32
|
+
/** Full read-time enrichment applied to every presented run. */
|
|
33
|
+
export async function enrichRun(root, run) {
|
|
34
|
+
return withDeliveryResolution(root, await withWorkflowContext(root, run));
|
|
35
|
+
}
|
|
36
|
+
function waitingDeliveryIntentEventId(run) {
|
|
37
|
+
if (typeof run.outcome !== 'object' || run.outcome === null)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (Reflect.get(run.outcome, 'kind') !== ActivityOutcomeKind.Waiting)
|
|
40
|
+
return undefined;
|
|
41
|
+
const data = Reflect.get(run.outcome, 'data');
|
|
42
|
+
if (typeof data !== 'object' || data === null)
|
|
43
|
+
return undefined;
|
|
44
|
+
const intentEventId = Reflect.get(data, 'intentEventId');
|
|
45
|
+
const signalKind = Reflect.get(data, 'signalKind');
|
|
46
|
+
return typeof intentEventId === 'string' && signalKind === 'delivery-result'
|
|
47
|
+
? intentEventId
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
@@ -8,7 +8,7 @@ import { workItemId, WorkStatus } from '../work/index.js';
|
|
|
8
8
|
import { primaryExternalRef } from './external-ref.js';
|
|
9
9
|
import { projectionMeta } from './surface-api-metadata.js';
|
|
10
10
|
import { projectionPage } from './surface-api-projection-pages.js';
|
|
11
|
-
import {
|
|
11
|
+
import { enrichRun } from './surface-api-run-context.js';
|
|
12
12
|
import { readWorkTranscript, transcriptGroups } from './surface-api-transcripts.js';
|
|
13
13
|
export function createSurfaceWorkApplications(root, now) {
|
|
14
14
|
return {
|
|
@@ -115,7 +115,7 @@ async function workDetail(root, key, now) {
|
|
|
115
115
|
.map(presentWorkflowInstance),
|
|
116
116
|
},
|
|
117
117
|
execution: {
|
|
118
|
-
runs: await Promise.all(runs.map((run) =>
|
|
118
|
+
runs: await Promise.all(runs.map((run) => enrichRun(root, presentRun(run)))),
|
|
119
119
|
transcriptGroups: await transcriptGroups(root.transcriptStore, id, runs),
|
|
120
120
|
},
|
|
121
121
|
activities: presentPullRequest(pullRequest?.value),
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { cachedJournalView, } from '../../kernel/index.js';
|
|
1
2
|
import { decodeRunExecutionEvent, } from '../contracts/events.js';
|
|
2
3
|
import { runId } from '../contracts/identifiers.js';
|
|
3
4
|
import { isRunStream, runStream } from '../contracts/streams.js';
|
|
4
5
|
import { foldRun } from '../domain/run.js';
|
|
5
6
|
export class RunRepository {
|
|
6
7
|
journal;
|
|
8
|
+
runs;
|
|
7
9
|
constructor(journal) {
|
|
8
10
|
this.journal = journal;
|
|
11
|
+
this.runs = cachedJournalView(journal, deriveRuns);
|
|
9
12
|
}
|
|
10
13
|
async load(runId) {
|
|
11
14
|
const events = await this.journal.readStream(runStream(runId));
|
|
@@ -17,18 +20,21 @@ export class RunRepository {
|
|
|
17
20
|
return events.map(decodeRunExecutionEvent);
|
|
18
21
|
}
|
|
19
22
|
async list(activationId) {
|
|
20
|
-
const
|
|
21
|
-
for (const event of await this.journal.readAll(0)) {
|
|
22
|
-
if (!isRunStream(event.stream))
|
|
23
|
-
continue;
|
|
24
|
-
const id = runId(event.stream.id);
|
|
25
|
-
const events = grouped.get(id) ?? [];
|
|
26
|
-
events.push(decodeRunExecutionEvent(event));
|
|
27
|
-
grouped.set(id, events);
|
|
28
|
-
}
|
|
29
|
-
const runs = (await Promise.all([...grouped.values()].map(foldRun))).filter((run) => run !== null);
|
|
23
|
+
const runs = await this.runs.get();
|
|
30
24
|
return activationId === undefined
|
|
31
25
|
? runs
|
|
32
26
|
: runs.filter((run) => run.activationId === activationId);
|
|
33
27
|
}
|
|
34
28
|
}
|
|
29
|
+
function deriveRuns(events) {
|
|
30
|
+
const grouped = new Map();
|
|
31
|
+
for (const event of events) {
|
|
32
|
+
if (!isRunStream(event.stream))
|
|
33
|
+
continue;
|
|
34
|
+
const id = runId(event.stream.id);
|
|
35
|
+
const decoded = grouped.get(id) ?? [];
|
|
36
|
+
decoded.push(decodeRunExecutionEvent(event));
|
|
37
|
+
grouped.set(id, decoded);
|
|
38
|
+
}
|
|
39
|
+
return [...grouped.values()].map(foldRun).filter((run) => run !== null);
|
|
40
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { reportedArtifactSchema } from '../../activities/index.js';
|
|
2
|
-
import { createEventDraft, EventActorKind, EventSourceKind, } from '../../kernel/index.js';
|
|
2
|
+
import { cachedJournalView, createEventDraft, EventActorKind, EventSourceKind, } from '../../kernel/index.js';
|
|
3
3
|
import { OrchestrationEventType, selectWorkflowOrchestrationEvent, workflowInstanceStream, } from '../../orchestration/index.js';
|
|
4
4
|
import { ResourceCorrelationProvenance, ResourceCorrelationRole, resourceId, resourceKind, ResourceStreamKind, } from '../../resources/index.js';
|
|
5
5
|
import { ArtifactEventType, decodeArtifactEvent, } from '../contracts/artifact-events.js';
|
|
@@ -7,8 +7,10 @@ import { ArtifactVerificationResult, ArtifactVerificationStatus, } from '../cont
|
|
|
7
7
|
import { integrationStream } from '../contracts/streams.js';
|
|
8
8
|
export class ArtifactRegistrationReactor {
|
|
9
9
|
dependencies;
|
|
10
|
+
latestUnresolved;
|
|
10
11
|
constructor(dependencies) {
|
|
11
12
|
this.dependencies = dependencies;
|
|
13
|
+
this.latestUnresolved = cachedJournalView(dependencies.journal, deriveLatestUnresolved);
|
|
12
14
|
}
|
|
13
15
|
async runOnce(limit = 100) {
|
|
14
16
|
const consumer = 'reactor:artifact-registration';
|
|
@@ -43,14 +45,7 @@ export class ArtifactRegistrationReactor {
|
|
|
43
45
|
}, branch, workItemId, 1);
|
|
44
46
|
}
|
|
45
47
|
async reconcileAmbiguous() {
|
|
46
|
-
const latest =
|
|
47
|
-
for (const event of await this.dependencies.journal.readAll(0)) {
|
|
48
|
-
if (event.eventType !== ArtifactEventType.VerificationUnresolved)
|
|
49
|
-
continue;
|
|
50
|
-
const decoded = decodeArtifactEvent(event);
|
|
51
|
-
const key = `${decoded.causationId}:${decoded.payload.artifact.externalKey.adapter}:${decoded.payload.artifact.externalKey.key}`;
|
|
52
|
-
latest.set(key, decoded);
|
|
53
|
-
}
|
|
48
|
+
const latest = await this.latestUnresolved.get();
|
|
54
49
|
for (const event of latest.values()) {
|
|
55
50
|
if (event.payload.status !== ArtifactVerificationStatus.Ambiguous || event.payload.escalated)
|
|
56
51
|
continue;
|
|
@@ -137,6 +132,17 @@ export class ArtifactRegistrationReactor {
|
|
|
137
132
|
return started?.payload.workItemId ?? null;
|
|
138
133
|
}
|
|
139
134
|
}
|
|
135
|
+
function deriveLatestUnresolved(events) {
|
|
136
|
+
const latest = new Map();
|
|
137
|
+
for (const event of events) {
|
|
138
|
+
if (event.eventType !== ArtifactEventType.VerificationUnresolved)
|
|
139
|
+
continue;
|
|
140
|
+
const decoded = decodeArtifactEvent(event);
|
|
141
|
+
const key = `${decoded.causationId}:${decoded.payload.artifact.externalKey.adapter}:${decoded.payload.artifact.externalKey.key}`;
|
|
142
|
+
latest.set(key, decoded);
|
|
143
|
+
}
|
|
144
|
+
return latest;
|
|
145
|
+
}
|
|
140
146
|
function reportedArtifacts(value) {
|
|
141
147
|
if (typeof value !== 'object' || value === null || !('reportedArtifacts' in value))
|
|
142
148
|
return [];
|
|
@@ -161,20 +161,26 @@ function foldDeliveryFact(previous, delivery) {
|
|
|
161
161
|
case DeliveryEventType.AttemptStarted:
|
|
162
162
|
return { ...current, attempts: current.attempts + 1 };
|
|
163
163
|
case DeliveryEventType.Confirmed:
|
|
164
|
-
return { ...current, state: DeliveryState.Confirmed };
|
|
164
|
+
return { ...current, state: DeliveryState.Confirmed, resolvedAt: delivery.occurredAt };
|
|
165
165
|
case DeliveryEventType.Failed:
|
|
166
|
-
return { ...current, state: DeliveryState.Failed };
|
|
166
|
+
return { ...current, state: DeliveryState.Failed, resolvedAt: delivery.occurredAt };
|
|
167
167
|
case DeliveryEventType.Ambiguous:
|
|
168
168
|
return {
|
|
169
169
|
...current,
|
|
170
170
|
state: DeliveryState.Ambiguous,
|
|
171
|
+
resolvedAt: delivery.occurredAt,
|
|
171
172
|
reconciliationKey: delivery.payload.reconciliationKey,
|
|
172
173
|
};
|
|
173
174
|
case DeliveryEventType.Escalated:
|
|
174
175
|
return { ...current, escalation: { reason: delivery.payload.reason } };
|
|
175
176
|
case DeliveryEventType.Reconciled:
|
|
176
177
|
if (delivery.payload.result === DeliveryResultKind.Confirmed)
|
|
177
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
...current,
|
|
180
|
+
state: DeliveryState.Confirmed,
|
|
181
|
+
resolvedAt: delivery.occurredAt,
|
|
182
|
+
escalation: undefined,
|
|
183
|
+
};
|
|
178
184
|
return delivery.payload.result === DeliveryResultKind.Unknown
|
|
179
185
|
? { ...current, reconciliationAttempts: (current.reconciliationAttempts ?? 0) + 1 }
|
|
180
186
|
: current;
|
|
@@ -37,9 +37,16 @@ export const gitHubConfigSchema = z
|
|
|
37
37
|
maxConcurrent: z.number().int().positive().default(4),
|
|
38
38
|
commentPageSize: z.number().int().positive().max(100).default(25),
|
|
39
39
|
lookbackMs: z.number().int().nonnegative().default(60_000),
|
|
40
|
+
intervalMs: z.number().int().positive().default(30_000),
|
|
40
41
|
})
|
|
41
42
|
.strict()
|
|
42
|
-
.default({
|
|
43
|
+
.default({
|
|
44
|
+
maxPerRepo: 25,
|
|
45
|
+
maxConcurrent: 4,
|
|
46
|
+
commentPageSize: 25,
|
|
47
|
+
lookbackMs: 60_000,
|
|
48
|
+
intervalMs: 30_000,
|
|
49
|
+
}),
|
|
43
50
|
intake: z.array(intakeRuleSchema).default([]),
|
|
44
51
|
publication: z
|
|
45
52
|
.object({ postStatusComments: z.boolean().default(true) })
|
|
@@ -16,8 +16,17 @@ export function createGitHubSource(config, client, adapter, requests = createGit
|
|
|
16
16
|
// so it's safe for it to reset on restart.
|
|
17
17
|
const lastEventIds = new Map();
|
|
18
18
|
const pendingWatermarks = new Map();
|
|
19
|
+
const now = state?.now ?? Date.now;
|
|
20
|
+
// In-memory only: an extra real poll after a process restart is harmless,
|
|
21
|
+
// the same tradeoff already made for lastEventIds above.
|
|
22
|
+
let lastPolledAt;
|
|
19
23
|
return {
|
|
20
24
|
async poll(signal) {
|
|
25
|
+
const currentTime = now();
|
|
26
|
+
if (lastPolledAt !== undefined && currentTime - lastPolledAt < config.polling.intervalMs) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
lastPolledAt = currentTime;
|
|
21
30
|
const perRepository = await Promise.all(config.repositories.map(async ({ owner, repo }) => pollRepository({
|
|
22
31
|
client: limitGitHubSourceClient(client, requests),
|
|
23
32
|
config,
|
|
@@ -26,7 +35,7 @@ export function createGitHubSource(config, client, adapter, requests = createGit
|
|
|
26
35
|
owner,
|
|
27
36
|
repo,
|
|
28
37
|
watermark: await loadWatermark(state?.checkpoints, adapter, owner, repo),
|
|
29
|
-
now
|
|
38
|
+
now,
|
|
30
39
|
health,
|
|
31
40
|
})));
|
|
32
41
|
for (const result of perRepository)
|
package/dist/src/kernel/index.js
CHANGED
|
@@ -13,5 +13,6 @@ export * from './contracts/vocabulary.js';
|
|
|
13
13
|
export * from './domain/event-envelope.js';
|
|
14
14
|
export * from './domain/match-mode.js';
|
|
15
15
|
export * from './domain/relation.js';
|
|
16
|
+
export * from './infrastructure/cached-journal-view.js';
|
|
16
17
|
export * from './infrastructure/system-clock.js';
|
|
17
18
|
export * from './infrastructure/ulid-id-generator.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Memoizes a full-journal derivation — a fold, decode, or index built from
|
|
2
|
+
// every event in history — so a resident loop calling it every tick only
|
|
3
|
+
// pays that cost when the journal has actually moved since the last call.
|
|
4
|
+
// Same gate ProjectionRunner keeps for itself (a last-seen global position),
|
|
5
|
+
// generalized for callers that want a materialized view rather than an
|
|
6
|
+
// incremental batch. Depends only on the EventJournal port, not on any
|
|
7
|
+
// concrete persistence implementation, so it lives in kernel rather than
|
|
8
|
+
// persistence — domain and adapter modules may call it directly.
|
|
9
|
+
export function cachedJournalView(journal, derive) {
|
|
10
|
+
let cache;
|
|
11
|
+
return {
|
|
12
|
+
async get() {
|
|
13
|
+
const position = await journal.latestGlobalPosition();
|
|
14
|
+
if (cache !== undefined && cache.position === position)
|
|
15
|
+
return cache.value;
|
|
16
|
+
const value = await derive(await journal.readAll(0));
|
|
17
|
+
cache = { position, value };
|
|
18
|
+
return value;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { cachedJournalView, } from '../../kernel/index.js';
|
|
1
2
|
import { decodeOrchestrationEvent, selectWorkflowOrchestrationEvent, } from '../contracts/event-decoder.js';
|
|
2
3
|
import { workflowInstanceId } from '../contracts/identifiers.js';
|
|
3
4
|
import { isWorkflowInstanceStream, workflowInstanceStream } from '../contracts/streams.js';
|
|
4
5
|
import { foldWorkflowInstance } from '../domain/workflow-instance.js';
|
|
5
6
|
export class OrchestrationRepository {
|
|
6
7
|
journal;
|
|
8
|
+
listed;
|
|
7
9
|
constructor(journal) {
|
|
8
10
|
this.journal = journal;
|
|
11
|
+
this.listed = cachedJournalView(journal, (events) => deriveList(events));
|
|
9
12
|
}
|
|
10
13
|
async load(id) {
|
|
11
14
|
const events = await this.journal.readStream(workflowInstanceStream(workflowInstanceId(id)));
|
|
@@ -25,27 +28,29 @@ export class OrchestrationRepository {
|
|
|
25
28
|
return events.map(decodeOrchestrationEvent).filter(isWorkflowEvent);
|
|
26
29
|
}
|
|
27
30
|
async list() {
|
|
28
|
-
|
|
29
|
-
const streams = new Map();
|
|
30
|
-
for (const event of events) {
|
|
31
|
-
if (!isWorkflowInstanceStream(event.stream))
|
|
32
|
-
continue;
|
|
33
|
-
const existing = streams.get(event.stream.id);
|
|
34
|
-
if (existing === undefined)
|
|
35
|
-
streams.set(event.stream.id, [event]);
|
|
36
|
-
else
|
|
37
|
-
existing.push(event);
|
|
38
|
-
}
|
|
39
|
-
// `sequence` counts every event on the stream, matching readStream, while the
|
|
40
|
-
// fold sees only owned orchestration events — exactly what load() computes.
|
|
41
|
-
return [...streams.values()].map((streamEvents) => ({
|
|
42
|
-
sequence: streamEvents.length,
|
|
43
|
-
view: foldWorkflowInstance(streamEvents
|
|
44
|
-
.map(selectWorkflowOrchestrationEvent)
|
|
45
|
-
.filter((event) => event !== null && isWorkflowInstanceStream(event.stream))),
|
|
46
|
-
}));
|
|
31
|
+
return this.listed.get();
|
|
47
32
|
}
|
|
48
33
|
}
|
|
49
34
|
function isWorkflowEvent(event) {
|
|
50
35
|
return isWorkflowInstanceStream(event.stream);
|
|
51
36
|
}
|
|
37
|
+
// `sequence` counts every event on the stream, matching readStream, while the
|
|
38
|
+
// fold sees only owned orchestration events — exactly what load() computes.
|
|
39
|
+
function deriveList(events) {
|
|
40
|
+
const streams = new Map();
|
|
41
|
+
for (const event of events) {
|
|
42
|
+
if (!isWorkflowInstanceStream(event.stream))
|
|
43
|
+
continue;
|
|
44
|
+
const existing = streams.get(event.stream.id);
|
|
45
|
+
if (existing === undefined)
|
|
46
|
+
streams.set(event.stream.id, [event]);
|
|
47
|
+
else
|
|
48
|
+
existing.push(event);
|
|
49
|
+
}
|
|
50
|
+
return [...streams.values()].map((streamEvents) => ({
|
|
51
|
+
sequence: streamEvents.length,
|
|
52
|
+
view: foldWorkflowInstance(streamEvents
|
|
53
|
+
.map(selectWorkflowOrchestrationEvent)
|
|
54
|
+
.filter((event) => event !== null && isWorkflowInstanceStream(event.stream))),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
@@ -73,6 +73,10 @@ export class FileEventJournal {
|
|
|
73
73
|
.slice(-limit)
|
|
74
74
|
.reverse();
|
|
75
75
|
}
|
|
76
|
+
async latestGlobalPosition() {
|
|
77
|
+
const events = await this.scan();
|
|
78
|
+
return events.at(-1)?.globalPosition ?? 0;
|
|
79
|
+
}
|
|
76
80
|
async scan() {
|
|
77
81
|
const directory = join(this.root, 'events');
|
|
78
82
|
let files;
|
|
@@ -55,6 +55,9 @@ export class InMemoryEventJournal {
|
|
|
55
55
|
.slice(-limit)
|
|
56
56
|
.reverse();
|
|
57
57
|
}
|
|
58
|
+
async latestGlobalPosition() {
|
|
59
|
+
return this.events.length;
|
|
60
|
+
}
|
|
58
61
|
rejectChangedEventIds(drafts, existingEvents) {
|
|
59
62
|
for (const [index, draft] of drafts.entries()) {
|
|
60
63
|
const existing = existingEvents[index];
|