@atolis-hq/wake 0.3.51 → 0.3.52
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/composition-root.js +2 -0
- package/dist/src/bootstrap/integration-runtime.js +1 -1
- package/dist/src/bootstrap/projection-runtime.js +24 -3
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/persistence/application/projection-runner.js +16 -2
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ import { loadFakeScenarios } from './fake-scenarios.js';
|
|
|
12
12
|
import { composeIntegrationRuntime } from './integration-runtime.js';
|
|
13
13
|
import { resolveWakePaths } from './paths.js';
|
|
14
14
|
import { composePersistence } from './persistence-composition.js';
|
|
15
|
+
import { createFileProjectionRunSerialiser, } from './projection-runtime.js';
|
|
15
16
|
import { createRunnerQuotaReporter } from './runner-quota-reporter.js';
|
|
16
17
|
import { createRunnerRegistry } from './runner-registry.js';
|
|
17
18
|
import { FileScheduleCheckpointStore } from './schedule-checkpoint-store.js';
|
|
@@ -130,6 +131,7 @@ export async function createCompositionRoot(wakeRoot, options = {}) {
|
|
|
130
131
|
work,
|
|
131
132
|
ids,
|
|
132
133
|
wakeRoot,
|
|
134
|
+
projectionRunSerialiser: createFileProjectionRunSerialiser(paths.dataRoot),
|
|
133
135
|
scheduleCheckpoints: options.scheduleCheckpoints ?? new FileScheduleCheckpointStore(paths.dataRoot),
|
|
134
136
|
...(options.decorateDeliveryAdapter === undefined
|
|
135
137
|
? {}
|
|
@@ -55,7 +55,7 @@ export async function composeIntegrationRuntime(input) {
|
|
|
55
55
|
// pipeline's own catchUpProjections, the API's manual tick, and the
|
|
56
56
|
// resident's standalone projection pump) shares this one instance, so
|
|
57
57
|
// serializing it here in-process covers all of them without a file lock.
|
|
58
|
-
const projectionRunner = serializeRunRegisteredOnce(createRuntimeProjectionRunner(input.journal, input.projections, input.checkpoints));
|
|
58
|
+
const projectionRunner = serializeRunRegisteredOnce(createRuntimeProjectionRunner(input.journal, input.projections, input.checkpoints, input.projectionRunSerialiser));
|
|
59
59
|
const delivery = new DeliveryService({
|
|
60
60
|
journal: input.journal,
|
|
61
61
|
intents: async () => (await input.projections.list(IntegrationStreamKind.Delivery)).map(({ value }) => value),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
1
2
|
import { activityProjectionDefinitions } from '../activities/index.js';
|
|
2
3
|
import { controlPlaneProjectionDefinitions } from '../control-plane/index.js';
|
|
3
4
|
import { executionProjection, runsByWorkflowInstanceProjection } from '../execution/index.js';
|
|
4
5
|
import { deliveryProjectionDefinitions } from '../integrations/index.js';
|
|
5
6
|
import { orchestrationProjection, workflowDefinitionsProjection, workflowsByWorkItemProjection, } from '../orchestration/index.js';
|
|
6
|
-
import { ProjectionRunner } from '../persistence/index.js';
|
|
7
|
+
import { acquireFileLock, ProjectionRunner, } from '../persistence/index.js';
|
|
7
8
|
import { resourceCorrelationProjection, resourceProjection, resourcesByExternalKeyProjection, workCorrelationsProjection, } from '../resources/index.js';
|
|
8
9
|
import { workProjection } from '../work/index.js';
|
|
9
10
|
import { analyticsProjection } from './analytics-projection.js';
|
|
@@ -25,6 +26,26 @@ export const runtimeProjectionDefinitions = [
|
|
|
25
26
|
boardProjection,
|
|
26
27
|
analyticsProjection,
|
|
27
28
|
];
|
|
28
|
-
export function createRuntimeProjectionRunner(journal, projections, checkpoints) {
|
|
29
|
-
return new ProjectionRunner(journal, projections, checkpoints, runtimeProjectionDefinitions);
|
|
29
|
+
export function createRuntimeProjectionRunner(journal, projections, checkpoints, serialiseRun) {
|
|
30
|
+
return new ProjectionRunner(journal, projections, checkpoints, runtimeProjectionDefinitions, serialiseRun);
|
|
31
|
+
}
|
|
32
|
+
export function createFileProjectionRunSerialiser(dataRoot) {
|
|
33
|
+
const path = join(dataRoot, 'locks', 'projection-runner.lock');
|
|
34
|
+
return async (operation) => {
|
|
35
|
+
while (true) {
|
|
36
|
+
const lock = await acquireFileLock(path, {
|
|
37
|
+
staleAfterMs: 60_000,
|
|
38
|
+
staleRequiresDeadProcess: true,
|
|
39
|
+
});
|
|
40
|
+
if (lock.acquired) {
|
|
41
|
+
try {
|
|
42
|
+
return await operation();
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await lock.release();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
30
51
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
async function runImmediately(operation) {
|
|
2
|
+
return operation();
|
|
3
|
+
}
|
|
1
4
|
export class ProjectionRunner {
|
|
2
5
|
journal;
|
|
3
6
|
projections;
|
|
4
7
|
checkpoints;
|
|
5
8
|
registered;
|
|
9
|
+
serialiseRun;
|
|
6
10
|
caughtUpToGlobalPosition;
|
|
7
|
-
constructor(journal, projections, checkpoints, registered = []) {
|
|
11
|
+
constructor(journal, projections, checkpoints, registered = [], serialiseRun = runImmediately) {
|
|
8
12
|
this.journal = journal;
|
|
9
13
|
this.projections = projections;
|
|
10
14
|
this.checkpoints = checkpoints;
|
|
11
15
|
this.registered = registered;
|
|
16
|
+
this.serialiseRun = serialiseRun;
|
|
12
17
|
}
|
|
13
18
|
// One shared journal read for every registered definition, not one per
|
|
14
19
|
// definition: readAll ultimately re-derives its content-fingerprint via
|
|
@@ -26,6 +31,9 @@ export class ProjectionRunner {
|
|
|
26
31
|
// batch — otherwise a backlog bigger than `limit` would get marked caught
|
|
27
32
|
// up after its first (partial) batch and never finish draining.
|
|
28
33
|
async runRegisteredOnce(limit = 100) {
|
|
34
|
+
return this.serialiseRun(() => this.runRegisteredOnceUnlocked(limit));
|
|
35
|
+
}
|
|
36
|
+
async runRegisteredOnceUnlocked(limit) {
|
|
29
37
|
const allEvents = await this.journal.readAll(0);
|
|
30
38
|
const latestGlobalPosition = allEvents.at(-1)?.globalPosition ?? 0;
|
|
31
39
|
if (this.caughtUpToGlobalPosition !== undefined &&
|
|
@@ -37,6 +45,9 @@ export class ProjectionRunner {
|
|
|
37
45
|
return counts.reduce((total, count) => total + count, 0);
|
|
38
46
|
}
|
|
39
47
|
async runOnce(definition, limit = 100) {
|
|
48
|
+
return this.serialiseRun(() => this.runOnceUnlocked(definition, limit));
|
|
49
|
+
}
|
|
50
|
+
async runOnceUnlocked(definition, limit) {
|
|
40
51
|
const consumer = `projection:${definition.name}`;
|
|
41
52
|
const events = await this.journal.readAll(await this.checkpoints.load(consumer), limit);
|
|
42
53
|
return this.apply(definition, consumer, events);
|
|
@@ -66,11 +77,14 @@ export class ProjectionRunner {
|
|
|
66
77
|
return events.length;
|
|
67
78
|
}
|
|
68
79
|
async rebuild(definition) {
|
|
80
|
+
return this.serialiseRun(() => this.rebuildUnlocked(definition));
|
|
81
|
+
}
|
|
82
|
+
async rebuildUnlocked(definition) {
|
|
69
83
|
await this.projections.clear(definition.name);
|
|
70
84
|
await this.checkpoints.reset(`projection:${definition.name}`);
|
|
71
85
|
let total = 0;
|
|
72
86
|
while (true) {
|
|
73
|
-
const count = await this.
|
|
87
|
+
const count = await this.runOnceUnlocked(definition, 100);
|
|
74
88
|
total += count;
|
|
75
89
|
if (count < 100)
|
|
76
90
|
return total;
|