@zudojs/runtime 0.1.0 → 1.0.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/LICENSE +21 -0
- package/README.md +127 -6
- package/dist/dependencyGraph/dependencyGraph.core.d.ts +11 -5
- package/dist/dependencyGraph/dependencyGraph.core.js +13 -12
- package/dist/dependencyGraph/dependencyGraph.type.d.ts +0 -7
- package/dist/dependencyGraph/index.d.ts +2 -2
- package/dist/dependencyGraph/index.js +1 -1
- package/dist/health/health.core.d.ts +12 -0
- package/dist/health/health.core.js +55 -0
- package/dist/health/index.d.ts +5 -0
- package/dist/health/index.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -1
- package/dist/lifecycle/index.d.ts +1 -1
- package/dist/lifecycle/lifecycle.core.d.ts +41 -10
- package/dist/lifecycle/lifecycle.core.js +238 -61
- package/dist/lifecycle/lifecycle.type.d.ts +48 -1
- package/dist/readiness/index.d.ts +1 -1
- package/dist/readiness/readiness.core.d.ts +36 -4
- package/dist/readiness/readiness.core.js +109 -6
- package/dist/readiness/readiness.type.d.ts +15 -1
- package/dist/registry/registry.core.d.ts +14 -1
- package/dist/registry/registry.core.js +41 -2
- package/dist/runtime/runtime.core.d.ts +75 -5
- package/dist/runtime/runtime.core.js +199 -43
- package/dist/runtimeContext/index.d.ts +2 -2
- package/dist/runtimeContext/index.js +1 -1
- package/dist/runtimeContext/runtimeContext.core.d.ts +13 -4
- package/dist/runtimeContext/runtimeContext.core.js +22 -11
- package/dist/runtimeContext/runtimeContext.type.d.ts +12 -13
- package/dist/runtimeError/runtimeError.base.js +1 -1
- package/dist/runtimeEvents/index.d.ts +2 -2
- package/dist/runtimeEvents/index.js +1 -1
- package/dist/runtimeEvents/runtimeEvents.core.d.ts +15 -1
- package/dist/runtimeEvents/runtimeEvents.core.js +26 -0
- package/dist/runtimeEvents/runtimeEvents.type.d.ts +19 -4
- package/dist/runtimeOptions/runtimeOptions.core.js +8 -2
- package/dist/runtimeOptions/runtimeOptions.type.d.ts +29 -1
- package/dist/runtimeOptions/runtimeOptions.type.js +6 -0
- package/dist/runtimeState/index.d.ts +1 -1
- package/dist/runtimeState/runtimeState.core.d.ts +4 -0
- package/dist/runtimeState/runtimeState.core.js +17 -4
- package/dist/runtimeState/runtimeState.type.d.ts +14 -17
- package/dist/shutdown/shutdown.core.d.ts +20 -1
- package/dist/shutdown/shutdown.core.js +55 -29
- package/dist/signalHandler/signalHandler.core.d.ts +53 -13
- package/dist/signalHandler/signalHandler.core.js +97 -27
- package/dist/startup/startup.core.d.ts +3 -2
- package/dist/startup/startup.core.js +52 -46
- package/dist/testRuntime/index.d.ts +6 -1
- package/dist/testRuntime/index.js +5 -1
- package/dist/testRuntime/testRuntime.core.d.ts +21 -2
- package/dist/testRuntime/testRuntime.core.js +23 -6
- package/package.json +29 -22
- package/dist/.tsbuildinfo +0 -1
- package/dist/dependencyGraph/dependencyGraph.core.d.ts.map +0 -1
- package/dist/dependencyGraph/dependencyGraph.core.js.map +0 -1
- package/dist/dependencyGraph/dependencyGraph.type.d.ts.map +0 -1
- package/dist/dependencyGraph/dependencyGraph.type.js.map +0 -1
- package/dist/dependencyGraph/index.d.ts.map +0 -1
- package/dist/dependencyGraph/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/index.js.map +0 -1
- package/dist/lifecycle/lifecycle.core.d.ts.map +0 -1
- package/dist/lifecycle/lifecycle.core.js.map +0 -1
- package/dist/lifecycle/lifecycle.type.d.ts.map +0 -1
- package/dist/lifecycle/lifecycle.type.js.map +0 -1
- package/dist/readiness/index.d.ts.map +0 -1
- package/dist/readiness/index.js.map +0 -1
- package/dist/readiness/readiness.core.d.ts.map +0 -1
- package/dist/readiness/readiness.core.js.map +0 -1
- package/dist/readiness/readiness.type.d.ts.map +0 -1
- package/dist/readiness/readiness.type.js.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/registry.core.d.ts.map +0 -1
- package/dist/registry/registry.core.js.map +0 -1
- package/dist/runtime/runtime.core.d.ts.map +0 -1
- package/dist/runtime/runtime.core.js.map +0 -1
- package/dist/runtimeContext/index.d.ts.map +0 -1
- package/dist/runtimeContext/index.js.map +0 -1
- package/dist/runtimeContext/runtimeContext.core.d.ts.map +0 -1
- package/dist/runtimeContext/runtimeContext.core.js.map +0 -1
- package/dist/runtimeContext/runtimeContext.factory.d.ts.map +0 -1
- package/dist/runtimeContext/runtimeContext.factory.js.map +0 -1
- package/dist/runtimeContext/runtimeContext.type.d.ts.map +0 -1
- package/dist/runtimeContext/runtimeContext.type.js.map +0 -1
- package/dist/runtimeError/index.d.ts.map +0 -1
- package/dist/runtimeError/index.js.map +0 -1
- package/dist/runtimeError/runtimeError.base.d.ts.map +0 -1
- package/dist/runtimeError/runtimeError.base.js.map +0 -1
- package/dist/runtimeEvents/index.d.ts.map +0 -1
- package/dist/runtimeEvents/index.js.map +0 -1
- package/dist/runtimeEvents/runtimeEvents.core.d.ts.map +0 -1
- package/dist/runtimeEvents/runtimeEvents.core.js.map +0 -1
- package/dist/runtimeEvents/runtimeEvents.type.d.ts.map +0 -1
- package/dist/runtimeEvents/runtimeEvents.type.js.map +0 -1
- package/dist/runtimeOptions/index.d.ts.map +0 -1
- package/dist/runtimeOptions/index.js.map +0 -1
- package/dist/runtimeOptions/runtimeOptions.core.d.ts.map +0 -1
- package/dist/runtimeOptions/runtimeOptions.core.js.map +0 -1
- package/dist/runtimeOptions/runtimeOptions.type.d.ts.map +0 -1
- package/dist/runtimeOptions/runtimeOptions.type.js.map +0 -1
- package/dist/runtimeState/index.d.ts.map +0 -1
- package/dist/runtimeState/index.js.map +0 -1
- package/dist/runtimeState/runtimeState.core.d.ts.map +0 -1
- package/dist/runtimeState/runtimeState.core.js.map +0 -1
- package/dist/runtimeState/runtimeState.type.d.ts.map +0 -1
- package/dist/runtimeState/runtimeState.type.js.map +0 -1
- package/dist/shutdown/index.d.ts.map +0 -1
- package/dist/shutdown/index.js.map +0 -1
- package/dist/shutdown/shutdown.core.d.ts.map +0 -1
- package/dist/shutdown/shutdown.core.js.map +0 -1
- package/dist/signalHandler/index.d.ts.map +0 -1
- package/dist/signalHandler/index.js.map +0 -1
- package/dist/signalHandler/signalHandler.core.d.ts.map +0 -1
- package/dist/signalHandler/signalHandler.core.js.map +0 -1
- package/dist/startup/index.d.ts.map +0 -1
- package/dist/startup/index.js.map +0 -1
- package/dist/startup/startup.core.d.ts.map +0 -1
- package/dist/startup/startup.core.js.map +0 -1
- package/dist/testRuntime/index.d.ts.map +0 -1
- package/dist/testRuntime/index.js.map +0 -1
- package/dist/testRuntime/testRuntime.core.d.ts.map +0 -1
- package/dist/testRuntime/testRuntime.core.js.map +0 -1
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertTransition, canStart, canStop, hasFailed, isRunning, } from "../runtimeState/index.js";
|
|
2
2
|
import { createRuntimeOptions } from "../runtimeOptions/index.js";
|
|
3
|
-
import { createRuntimeContext } from "../runtimeContext/index.js";
|
|
4
|
-
import { createRuntimeId } from "../runtimeContext/index.js";
|
|
3
|
+
import { createRuntimeContext, withRuntimeContextState, } from "../runtimeContext/index.js";
|
|
5
4
|
import { LifecycleManager } from "../lifecycle/index.js";
|
|
6
5
|
import { executeStartup, rollbackStartup } from "../startup/index.js";
|
|
7
6
|
import { executeShutdown } from "../shutdown/index.js";
|
|
8
7
|
import { SignalHandler } from "../signalHandler/index.js";
|
|
9
8
|
import { ReadinessTracker } from "../readiness/index.js";
|
|
10
|
-
import {
|
|
9
|
+
import { computeRuntimeHealth } from "../health/index.js";
|
|
10
|
+
import { createRuntimeEventPayload, createFailureEventPayload, createHealthEventPayload, createReadinessEventPayload, publishRuntimeEvent, } from "../runtimeEvents/index.js";
|
|
11
11
|
import { createEvent } from "@zudojs/events";
|
|
12
|
-
import {
|
|
12
|
+
import { RuntimeStateError, toRuntimeError } from "../runtimeError/index.js";
|
|
13
13
|
/**
|
|
14
14
|
* Default runtime implementation.
|
|
15
15
|
*/
|
|
16
16
|
export class DefaultRuntime {
|
|
17
17
|
_state = "created";
|
|
18
|
-
|
|
19
|
-
_context;
|
|
20
|
-
_ready = false;
|
|
18
|
+
_contextBase;
|
|
21
19
|
_startedAt;
|
|
22
20
|
_stoppedAt;
|
|
23
21
|
_failedAt;
|
|
24
22
|
_error;
|
|
23
|
+
_shutdownFailures = [];
|
|
25
24
|
options;
|
|
26
25
|
modules;
|
|
27
26
|
logger;
|
|
@@ -34,7 +33,7 @@ export class DefaultRuntime {
|
|
|
34
33
|
this.options = createRuntimeOptions(options);
|
|
35
34
|
this.modules = dependencies.modules;
|
|
36
35
|
this.logger = dependencies.logger;
|
|
37
|
-
this.
|
|
36
|
+
this._contextBase = createRuntimeContext({
|
|
38
37
|
runtimeId: this.options.runtimeId,
|
|
39
38
|
environment: this.options.environment,
|
|
40
39
|
applicationName: this.options.applicationName,
|
|
@@ -42,11 +41,26 @@ export class DefaultRuntime {
|
|
|
42
41
|
logger: dependencies.logger,
|
|
43
42
|
container: dependencies.container,
|
|
44
43
|
eventBus: dependencies.eventBus,
|
|
44
|
+
metadata: this.options.metadata,
|
|
45
45
|
});
|
|
46
|
-
this.
|
|
47
|
-
this.lifecycle = new LifecycleManager(this.modules, this.logger, dependencies.container, this.options.runtimeId, this.options.environment, {
|
|
46
|
+
this.lifecycle = new LifecycleManager(this.modules, this.logger, {
|
|
48
47
|
shutdownTimeout: this.options.shutdownTimeout,
|
|
48
|
+
// Always false during startup: rollback depends on stopping at
|
|
49
|
+
// the first failure rather than pressing on into modules whose
|
|
50
|
+
// dependencies never came up.
|
|
49
51
|
continueOnFailure: false,
|
|
52
|
+
parallelInitialization: this.options.parallelInitialization,
|
|
53
|
+
runtimeId: this.options.runtimeId,
|
|
54
|
+
onModuleEvent: (type, payload) => {
|
|
55
|
+
this.emitEvent(type, payload);
|
|
56
|
+
},
|
|
57
|
+
}, {
|
|
58
|
+
...(dependencies.configuration !== undefined && {
|
|
59
|
+
configuration: dependencies.configuration,
|
|
60
|
+
}),
|
|
61
|
+
...(dependencies.application !== undefined && {
|
|
62
|
+
application: dependencies.application,
|
|
63
|
+
}),
|
|
50
64
|
});
|
|
51
65
|
this.signalHandler = new SignalHandler(this.logger, {
|
|
52
66
|
handleSignals: this.options.handleSignals,
|
|
@@ -54,6 +68,7 @@ export class DefaultRuntime {
|
|
|
54
68
|
});
|
|
55
69
|
this.readinessTracker = new ReadinessTracker({
|
|
56
70
|
autoMarkReady: this.options.trackReadiness,
|
|
71
|
+
checkTimeout: this.options.readinessCheckTimeout,
|
|
57
72
|
});
|
|
58
73
|
}
|
|
59
74
|
/**
|
|
@@ -66,19 +81,88 @@ export class DefaultRuntime {
|
|
|
66
81
|
* Current runtime status.
|
|
67
82
|
*/
|
|
68
83
|
get status() {
|
|
69
|
-
return
|
|
84
|
+
return Object.freeze({
|
|
85
|
+
state: this._state,
|
|
86
|
+
ready: this.ready,
|
|
87
|
+
running: isRunning(this._state),
|
|
88
|
+
startedAt: this._startedAt,
|
|
89
|
+
stoppedAt: this._stoppedAt,
|
|
90
|
+
failedAt: this._failedAt,
|
|
91
|
+
error: this._error,
|
|
92
|
+
shutdownFailures: this._shutdownFailures,
|
|
93
|
+
});
|
|
70
94
|
}
|
|
71
95
|
/**
|
|
72
96
|
* Runtime context.
|
|
73
97
|
*/
|
|
74
98
|
get context() {
|
|
75
|
-
return this.
|
|
99
|
+
return withRuntimeContextState(this._contextBase, {
|
|
100
|
+
status: this.status,
|
|
101
|
+
health: this.health,
|
|
102
|
+
ready: this.ready,
|
|
103
|
+
...(this._startedAt !== undefined && { startedAt: this._startedAt }),
|
|
104
|
+
...(this._stoppedAt !== undefined && { stoppedAt: this._stoppedAt }),
|
|
105
|
+
...(this._failedAt !== undefined && { failedAt: this._failedAt }),
|
|
106
|
+
...(this._error !== undefined && { error: this._error }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Current runtime health, derived from the lifecycle state and the
|
|
111
|
+
* registered readiness checks.
|
|
112
|
+
*/
|
|
113
|
+
get health() {
|
|
114
|
+
if (!this.options.trackHealth) {
|
|
115
|
+
return Object.freeze({
|
|
116
|
+
state: "unknown",
|
|
117
|
+
checks: Object.freeze([]),
|
|
118
|
+
timestamp: new Date(),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return computeRuntimeHealth(this._state, this.readinessTracker.getState());
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Current readiness state, including per-check results.
|
|
125
|
+
*/
|
|
126
|
+
get readiness() {
|
|
127
|
+
return this.readinessTracker.getState();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Registers a readiness check.
|
|
131
|
+
*/
|
|
132
|
+
registerReadinessCheck(name, check) {
|
|
133
|
+
const previousHealth = this.health.state;
|
|
134
|
+
this.readinessTracker.registerCheck(name, check);
|
|
135
|
+
this.emitHealthChange(previousHealth);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Removes a readiness check.
|
|
139
|
+
*/
|
|
140
|
+
removeReadinessCheck(name) {
|
|
141
|
+
const previousHealth = this.health.state;
|
|
142
|
+
const removed = this.readinessTracker.removeCheck(name);
|
|
143
|
+
if (removed) {
|
|
144
|
+
this.emitHealthChange(previousHealth);
|
|
145
|
+
}
|
|
146
|
+
return removed;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Re-evaluates every registered readiness check.
|
|
150
|
+
*/
|
|
151
|
+
async runReadinessChecks() {
|
|
152
|
+
const previousHealth = this.health.state;
|
|
153
|
+
const previouslyReady = this.ready;
|
|
154
|
+
await this.readinessTracker.runChecks();
|
|
155
|
+
this.emitHealthChange(previousHealth);
|
|
156
|
+
if (this.options.emitEvents && this.ready !== previouslyReady) {
|
|
157
|
+
const state = this.readinessTracker.getState();
|
|
158
|
+
this.emitEvent("runtime.readiness.changed", createReadinessEventPayload(this.options.runtimeId, this._state, state.ready, state.reason));
|
|
159
|
+
}
|
|
76
160
|
}
|
|
77
161
|
/**
|
|
78
162
|
* Whether the runtime is ready.
|
|
79
163
|
*/
|
|
80
164
|
get ready() {
|
|
81
|
-
return this.
|
|
165
|
+
return this.readinessTracker.isReady();
|
|
82
166
|
}
|
|
83
167
|
/**
|
|
84
168
|
* Starts the runtime.
|
|
@@ -90,8 +174,11 @@ export class DefaultRuntime {
|
|
|
90
174
|
if (this.startPromise) {
|
|
91
175
|
return this.startPromise;
|
|
92
176
|
}
|
|
93
|
-
if (!
|
|
94
|
-
throw new RuntimeStateError(`Cannot start runtime
|
|
177
|
+
if (!canStart(this._state)) {
|
|
178
|
+
throw new RuntimeStateError(`Cannot start a runtime in state "${this._state}"; start is only valid from "created". ` +
|
|
179
|
+
(hasFailed(this._state)
|
|
180
|
+
? "This runtime failed to start; call stop() to release it and create a new one."
|
|
181
|
+
: "Create a new runtime instead of restarting this one."));
|
|
95
182
|
}
|
|
96
183
|
this.startPromise = this.performStart();
|
|
97
184
|
try {
|
|
@@ -104,6 +191,14 @@ export class DefaultRuntime {
|
|
|
104
191
|
/**
|
|
105
192
|
* Stops the runtime.
|
|
106
193
|
*/
|
|
194
|
+
/**
|
|
195
|
+
* Stops the runtime.
|
|
196
|
+
*
|
|
197
|
+
* A failed runtime is stoppable: startup rollback only reaches modules
|
|
198
|
+
* that were started, so this is the operator's route to releasing
|
|
199
|
+
* everything else. It is also idempotent — stopping an already-stopped
|
|
200
|
+
* runtime is a no-op rather than an error.
|
|
201
|
+
*/
|
|
107
202
|
async stop() {
|
|
108
203
|
if (this._state === "stopped") {
|
|
109
204
|
return;
|
|
@@ -114,10 +209,11 @@ export class DefaultRuntime {
|
|
|
114
209
|
if (this._state === "created") {
|
|
115
210
|
this._state = "stopped";
|
|
116
211
|
this._stoppedAt = new Date();
|
|
212
|
+
this.signalHandler.unregister();
|
|
117
213
|
return;
|
|
118
214
|
}
|
|
119
|
-
if (!
|
|
120
|
-
throw new RuntimeStateError(`Cannot stop runtime
|
|
215
|
+
if (!canStop(this._state)) {
|
|
216
|
+
throw new RuntimeStateError(`Cannot stop a runtime in state "${this._state}"; stop is valid from "created", "running" and "failed".`);
|
|
121
217
|
}
|
|
122
218
|
this.stopPromise = this.performStop();
|
|
123
219
|
try {
|
|
@@ -132,26 +228,34 @@ export class DefaultRuntime {
|
|
|
132
228
|
*/
|
|
133
229
|
async performStart() {
|
|
134
230
|
this.transitionTo("initializing");
|
|
231
|
+
this.readinessTracker.setState("initializing", "Runtime is starting its modules.");
|
|
135
232
|
if (this.options.emitEvents) {
|
|
136
233
|
this.emitEvent("runtime.initializing");
|
|
137
234
|
}
|
|
235
|
+
// Registered before startup rather than after: a SIGTERM arriving
|
|
236
|
+
// while modules are still coming up must be handled, not ignored.
|
|
237
|
+
this.signalHandler.register(() => this.handleShutdownSignal());
|
|
138
238
|
try {
|
|
139
|
-
await executeStartup(this.lifecycle, this.options.runtimeId, this.
|
|
239
|
+
await executeStartup(this.lifecycle, this.options.runtimeId, this._contextBase.eventBus, this.logger, this.options.emitEvents, this.options.startupTimeout);
|
|
140
240
|
this.transitionTo("running");
|
|
141
|
-
this._ready = true;
|
|
142
241
|
this._startedAt = new Date();
|
|
143
|
-
|
|
242
|
+
// Evaluate any checks registered before startup instead of
|
|
243
|
+
// declaring readiness over the top of them. Force-marking ready
|
|
244
|
+
// here reported a runtime as ready while a dependency check was
|
|
245
|
+
// failing.
|
|
246
|
+
await this.readinessTracker.runChecks();
|
|
247
|
+
if (this.readinessTracker.hasChecks()) {
|
|
248
|
+
if (!this.ready) {
|
|
249
|
+
this.logger.warn("Runtime started, but one or more readiness checks are failing.", { reason: this.readinessTracker.getState().reason });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
this.readinessTracker.markReady("Runtime started successfully.");
|
|
254
|
+
}
|
|
144
255
|
if (this.options.emitEvents) {
|
|
145
256
|
this.emitEvent("runtime.running");
|
|
146
|
-
this.emitEvent("runtime.readiness.changed", createReadinessEventPayload(this.options.runtimeId, "running",
|
|
257
|
+
this.emitEvent("runtime.readiness.changed", createReadinessEventPayload(this.options.runtimeId, "running", this.ready, this.readinessTracker.getState().reason));
|
|
147
258
|
}
|
|
148
|
-
this.signalHandler.register(() => {
|
|
149
|
-
this.stop().catch((error) => {
|
|
150
|
-
this.logger.error("Shutdown failed.", {
|
|
151
|
-
errorMessage: error.message,
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
259
|
this.logger.info("Runtime is ready.", {
|
|
156
260
|
runtimeId: this.options.runtimeId,
|
|
157
261
|
environment: this.options.environment,
|
|
@@ -168,7 +272,12 @@ export class DefaultRuntime {
|
|
|
168
272
|
this.emitEvent("runtime.failed", createFailureEventPayload(this.options.runtimeId, "failed", runtimeError, "startup"));
|
|
169
273
|
}
|
|
170
274
|
try {
|
|
171
|
-
await rollbackStartup(this.lifecycle, this.logger);
|
|
275
|
+
const rollbackFailures = await rollbackStartup(this.lifecycle, this.logger);
|
|
276
|
+
if (rollbackFailures.length > 0) {
|
|
277
|
+
this.logger.error("Rollback completed with failures.", {
|
|
278
|
+
failedModules: rollbackFailures.map((failure) => failure.moduleId),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
172
281
|
}
|
|
173
282
|
catch (rollbackError) {
|
|
174
283
|
this.logger.error("Rollback failed.", {
|
|
@@ -186,22 +295,31 @@ export class DefaultRuntime {
|
|
|
186
295
|
*/
|
|
187
296
|
async performStop() {
|
|
188
297
|
this.transitionTo("stopping");
|
|
189
|
-
this.
|
|
190
|
-
this.readinessTracker.markNotReady("Runtime is shutting down.");
|
|
298
|
+
this.readinessTracker.setState("shutting_down", "Runtime is shutting down.");
|
|
191
299
|
if (this.options.emitEvents) {
|
|
192
300
|
this.emitEvent("runtime.stopping");
|
|
193
301
|
}
|
|
194
302
|
try {
|
|
195
|
-
await executeShutdown(this.lifecycle, this.options.runtimeId, this.
|
|
303
|
+
const result = await executeShutdown(this.lifecycle, this.options.runtimeId, this._contextBase.eventBus, this.logger, this.options.shutdownTimeout, this.options.emitEvents);
|
|
304
|
+
this._shutdownFailures = result.failures;
|
|
196
305
|
this.transitionTo("stopped");
|
|
197
306
|
this._stoppedAt = new Date();
|
|
198
|
-
this.signalHandler.unregister();
|
|
199
307
|
if (this.options.emitEvents) {
|
|
200
308
|
this.emitEvent("runtime.stopped");
|
|
201
309
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
310
|
+
if (result.failures.length > 0) {
|
|
311
|
+
// A teardown that dropped modules on the floor must not read as
|
|
312
|
+
// a clean stop; `status.shutdownFailures` records what failed.
|
|
313
|
+
this.logger.warn("Runtime stopped with module failures.", {
|
|
314
|
+
runtimeId: this.options.runtimeId,
|
|
315
|
+
failedModules: result.failures.map((failure) => failure.moduleId),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
this.logger.info("Runtime stopped.", {
|
|
320
|
+
runtimeId: this.options.runtimeId,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
205
323
|
}
|
|
206
324
|
catch (error) {
|
|
207
325
|
const runtimeError = toRuntimeError(error, "shutdown");
|
|
@@ -216,31 +334,69 @@ export class DefaultRuntime {
|
|
|
216
334
|
this.transitionTo("failed");
|
|
217
335
|
throw runtimeError;
|
|
218
336
|
}
|
|
337
|
+
finally {
|
|
338
|
+
// Released on both paths: leaving handlers attached after a failed
|
|
339
|
+
// stop keeps the process listening for a signal it can no longer
|
|
340
|
+
// act on.
|
|
341
|
+
this.signalHandler.unregister();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Runs shutdown in response to a termination signal.
|
|
346
|
+
*/
|
|
347
|
+
async handleShutdownSignal() {
|
|
348
|
+
try {
|
|
349
|
+
await this.stop();
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
this.logger.error("Shutdown failed.", {
|
|
353
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
219
356
|
}
|
|
220
357
|
/**
|
|
221
358
|
* Transitions to a new state.
|
|
222
359
|
*/
|
|
223
360
|
transitionTo(newState) {
|
|
224
361
|
const oldState = this._state;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
362
|
+
// Delegates to the exported assertion so the public state machine and
|
|
363
|
+
// the runtime cannot disagree about what a legal transition is.
|
|
364
|
+
assertTransition(oldState, newState);
|
|
365
|
+
const previousHealth = this.health.state;
|
|
228
366
|
this._state = newState;
|
|
229
|
-
this._status = createStatus(newState);
|
|
230
367
|
this.logger.debug(`Runtime state: ${oldState} -> ${newState}`);
|
|
368
|
+
this.emitHealthChange(previousHealth);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Emits `runtime.health.changed` when the derived health state has moved
|
|
372
|
+
* away from `previousHealth`. Health is derived rather than stored, so
|
|
373
|
+
* callers compare against a value captured before the change.
|
|
374
|
+
*/
|
|
375
|
+
emitHealthChange(previousHealth) {
|
|
376
|
+
if (!this.options.emitEvents || !this.options.trackHealth) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const health = this.health;
|
|
380
|
+
if (health.state === previousHealth) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this.emitEvent("runtime.health.changed", createHealthEventPayload(this.options.runtimeId, this._state, previousHealth, health.state, health.checks.map((check) => ({
|
|
384
|
+
name: check.name,
|
|
385
|
+
healthy: check.healthy,
|
|
386
|
+
}))));
|
|
231
387
|
}
|
|
232
388
|
/**
|
|
233
389
|
* Emits a runtime event.
|
|
234
390
|
*/
|
|
235
391
|
emitEvent(eventType, payload) {
|
|
236
|
-
if (this.options.emitEvents && this.
|
|
392
|
+
if (this.options.emitEvents && this._contextBase.eventBus) {
|
|
237
393
|
const eventPayload = payload ??
|
|
238
394
|
createRuntimeEventPayload(this.options.runtimeId, this._state);
|
|
239
395
|
const event = createEvent({
|
|
240
396
|
type: eventType,
|
|
241
397
|
payload: eventPayload,
|
|
242
398
|
});
|
|
243
|
-
this.
|
|
399
|
+
publishRuntimeEvent(this._contextBase.eventBus, this.logger, event);
|
|
244
400
|
}
|
|
245
401
|
}
|
|
246
402
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime context providing access to running application state.
|
|
3
3
|
*/
|
|
4
|
-
export { createRuntimeContext,
|
|
4
|
+
export { createRuntimeContext, withRuntimeContextState, } from "./runtimeContext.core.js";
|
|
5
5
|
export { createRuntimeId, createCorrelationId, createRequestId, } from "./runtimeContext.factory.js";
|
|
6
|
-
export type { RuntimeContext,
|
|
6
|
+
export type { RuntimeContext, RuntimeContextDependencies, RuntimeContextState, } from "./runtimeContext.type.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime context providing access to running application state.
|
|
3
3
|
*/
|
|
4
|
-
export { createRuntimeContext,
|
|
4
|
+
export { createRuntimeContext, withRuntimeContextState, } from "./runtimeContext.core.js";
|
|
5
5
|
export { createRuntimeId, createCorrelationId, createRequestId, } from "./runtimeContext.factory.js";
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { RuntimeContext, RuntimeContextDependencies, RuntimeIdentity } from "./runtimeContext.type.js";
|
|
1
|
+
import type { RuntimeContext, RuntimeContextDependencies, RuntimeContextState } from "./runtimeContext.type.js";
|
|
3
2
|
/**
|
|
4
3
|
* Creates a runtime context.
|
|
5
4
|
*/
|
|
6
5
|
export declare function createRuntimeContext(dependencies: RuntimeContextDependencies): RuntimeContext;
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
7
|
+
* Returns a context reflecting the current runtime state.
|
|
8
|
+
*
|
|
9
|
+
* `createRuntimeContext` captures the dependencies that never change for
|
|
10
|
+
* the life of a runtime; the lifecycle-dependent fields are layered on top
|
|
11
|
+
* of that base each time the context is read, so callers never observe a
|
|
12
|
+
* stale snapshot.
|
|
13
|
+
*
|
|
14
|
+
* Every field of {@link RuntimeContextState} is applied. Previously only
|
|
15
|
+
* `startedAt` was, so `stoppedAt`, `failedAt` and `error` were computed by
|
|
16
|
+
* the runtime, passed in here, and silently dropped — the context claimed
|
|
17
|
+
* a clean runtime on the failure path.
|
|
9
18
|
*/
|
|
10
|
-
export declare function
|
|
19
|
+
export declare function withRuntimeContextState(base: RuntimeContext, state: RuntimeContextState): RuntimeContext;
|
|
11
20
|
//# sourceMappingURL=runtimeContext.core.d.ts.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createRuntimeId as generateRuntimeId } from "./runtimeContext.factory.js";
|
|
2
1
|
import { createStatus } from "../runtimeState/runtimeState.core.js";
|
|
3
2
|
/**
|
|
4
3
|
* Creates a runtime context.
|
|
@@ -21,21 +20,33 @@ export function createRuntimeContext(dependencies) {
|
|
|
21
20
|
timestamp: new Date(),
|
|
22
21
|
},
|
|
23
22
|
ready: false,
|
|
23
|
+
metadata: dependencies.metadata ?? Object.freeze({}),
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Returns a context reflecting the current runtime state.
|
|
28
|
+
*
|
|
29
|
+
* `createRuntimeContext` captures the dependencies that never change for
|
|
30
|
+
* the life of a runtime; the lifecycle-dependent fields are layered on top
|
|
31
|
+
* of that base each time the context is read, so callers never observe a
|
|
32
|
+
* stale snapshot.
|
|
33
|
+
*
|
|
34
|
+
* Every field of {@link RuntimeContextState} is applied. Previously only
|
|
35
|
+
* `startedAt` was, so `stoppedAt`, `failedAt` and `error` were computed by
|
|
36
|
+
* the runtime, passed in here, and silently dropped — the context claimed
|
|
37
|
+
* a clean runtime on the failure path.
|
|
28
38
|
*/
|
|
29
|
-
export function
|
|
39
|
+
export function withRuntimeContextState(base, state) {
|
|
30
40
|
return Object.freeze({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
...base,
|
|
42
|
+
state: state.status.state,
|
|
43
|
+
status: state.status,
|
|
44
|
+
health: state.health,
|
|
45
|
+
ready: state.ready,
|
|
46
|
+
...(state.startedAt !== undefined && { startedAt: state.startedAt }),
|
|
47
|
+
...(state.stoppedAt !== undefined && { stoppedAt: state.stoppedAt }),
|
|
48
|
+
...(state.failedAt !== undefined && { failedAt: state.failedAt }),
|
|
49
|
+
...(state.error !== undefined && { error: state.error }),
|
|
39
50
|
});
|
|
40
51
|
}
|
|
41
52
|
//# sourceMappingURL=runtimeContext.core.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Environment } from "@zudojs/constants";
|
|
2
|
+
import type { RuntimeError } from "@zudojs/errors";
|
|
2
3
|
import type { Logger } from "@zudojs/logger";
|
|
3
4
|
import type { EventBus } from "@zudojs/events";
|
|
4
5
|
import type { Container } from "@zudojs/container";
|
|
@@ -7,6 +8,8 @@ import type { RuntimeId, RuntimeStatus, RuntimeState, RuntimeHealth } from "../r
|
|
|
7
8
|
* Runtime context providing access to the running application state.
|
|
8
9
|
*/
|
|
9
10
|
export interface RuntimeContext {
|
|
11
|
+
/** Additional runtime metadata supplied through runtime options. */
|
|
12
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
10
13
|
readonly runtimeId: RuntimeId;
|
|
11
14
|
readonly environment: Environment;
|
|
12
15
|
readonly applicationName: string;
|
|
@@ -14,28 +17,24 @@ export interface RuntimeContext {
|
|
|
14
17
|
readonly state: RuntimeState;
|
|
15
18
|
readonly status: RuntimeStatus;
|
|
16
19
|
readonly startedAt?: Date;
|
|
20
|
+
/** When the runtime last reached `stopped`. */
|
|
21
|
+
readonly stoppedAt?: Date;
|
|
22
|
+
/** When the runtime last reached `failed`. */
|
|
23
|
+
readonly failedAt?: Date;
|
|
24
|
+
/** The error that failed the runtime, if it has failed. */
|
|
25
|
+
readonly error?: RuntimeError;
|
|
17
26
|
readonly logger: Logger;
|
|
18
27
|
readonly container: Container;
|
|
19
28
|
readonly eventBus: EventBus;
|
|
20
29
|
readonly health: RuntimeHealth;
|
|
21
30
|
readonly ready: boolean;
|
|
22
31
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Runtime identity information.
|
|
25
|
-
*/
|
|
26
|
-
export interface RuntimeIdentity {
|
|
27
|
-
readonly runtimeId: RuntimeId;
|
|
28
|
-
readonly applicationName: string;
|
|
29
|
-
readonly applicationVersion: string;
|
|
30
|
-
readonly environment: Environment;
|
|
31
|
-
readonly hostname: string;
|
|
32
|
-
readonly processId: number;
|
|
33
|
-
readonly startedAt?: Date;
|
|
34
|
-
}
|
|
35
32
|
/**
|
|
36
33
|
* Dependencies required to create the runtime context.
|
|
37
34
|
*/
|
|
38
35
|
export interface RuntimeContextDependencies {
|
|
36
|
+
/** Additional runtime metadata, surfaced on the context. */
|
|
37
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
39
38
|
readonly runtimeId: RuntimeId;
|
|
40
39
|
readonly environment: Environment;
|
|
41
40
|
readonly applicationName: string;
|
|
@@ -54,6 +53,6 @@ export interface RuntimeContextState {
|
|
|
54
53
|
startedAt?: Date;
|
|
55
54
|
stoppedAt?: Date;
|
|
56
55
|
failedAt?: Date;
|
|
57
|
-
error?:
|
|
56
|
+
error?: RuntimeError;
|
|
58
57
|
}
|
|
59
58
|
//# sourceMappingURL=runtimeContext.type.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime lifecycle events.
|
|
3
3
|
*/
|
|
4
|
-
export { createRuntimeEventPayload, createModuleEventPayload, createFailureEventPayload, createHealthEventPayload, createReadinessEventPayload, } from "./runtimeEvents.core.js";
|
|
5
|
-
export type { RuntimeEventType, RuntimeEventPayload, RuntimeModuleEventPayload, RuntimeFailureEventPayload, RuntimeHealthEventPayload, RuntimeReadinessEventPayload, RuntimeEventMap, } from "./runtimeEvents.type.js";
|
|
4
|
+
export { publishRuntimeEvent, createRuntimeEventPayload, createModuleEventPayload, createFailureEventPayload, createHealthEventPayload, createReadinessEventPayload, } from "./runtimeEvents.core.js";
|
|
5
|
+
export type { RuntimeEventType, RuntimeModuleEventType, RuntimeEventPayload, RuntimeModuleEventPayload, RuntimeFailureEventPayload, RuntimeHealthEventPayload, RuntimeReadinessEventPayload, RuntimeEventMap, } from "./runtimeEvents.type.js";
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime lifecycle events.
|
|
3
3
|
*/
|
|
4
|
-
export { createRuntimeEventPayload, createModuleEventPayload, createFailureEventPayload, createHealthEventPayload, createReadinessEventPayload, } from "./runtimeEvents.core.js";
|
|
4
|
+
export { publishRuntimeEvent, createRuntimeEventPayload, createModuleEventPayload, createFailureEventPayload, createHealthEventPayload, createReadinessEventPayload, } from "./runtimeEvents.core.js";
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Event } from "@zudojs/events";
|
|
2
|
+
import type { RuntimeFailureState, RuntimeState } from "../runtimeState/runtimeState.type.js";
|
|
1
3
|
import type { RuntimeEventPayload, RuntimeModuleEventPayload, RuntimeFailureEventPayload, RuntimeHealthEventPayload, RuntimeReadinessEventPayload } from "./runtimeEvents.type.js";
|
|
2
4
|
/**
|
|
3
5
|
* Creates a base runtime event payload.
|
|
@@ -13,7 +15,7 @@ export declare function createModuleEventPayload(runtimeId: string, state: strin
|
|
|
13
15
|
/**
|
|
14
16
|
* Creates a runtime failure event payload.
|
|
15
17
|
*/
|
|
16
|
-
export declare function createFailureEventPayload(runtimeId: string, state:
|
|
18
|
+
export declare function createFailureEventPayload(runtimeId: string, state: RuntimeState | RuntimeFailureState, error: Error, phase: string, failedModuleId?: string): RuntimeFailureEventPayload;
|
|
17
19
|
/**
|
|
18
20
|
* Creates a runtime health change event payload.
|
|
19
21
|
*/
|
|
@@ -25,4 +27,16 @@ export declare function createHealthEventPayload(runtimeId: string, state: strin
|
|
|
25
27
|
* Creates a runtime readiness change event payload.
|
|
26
28
|
*/
|
|
27
29
|
export declare function createReadinessEventPayload(runtimeId: string, state: string, ready: boolean, reason?: string): RuntimeReadinessEventPayload;
|
|
30
|
+
/**
|
|
31
|
+
* Publishes a runtime lifecycle event without letting a failing handler,
|
|
32
|
+
* middleware, or a disposed bus turn into an unhandled promise rejection.
|
|
33
|
+
*
|
|
34
|
+
* Runtime events are informational; a failure to deliver one must never
|
|
35
|
+
* abort startup or shutdown, so the rejection is logged and swallowed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function publishRuntimeEvent(eventBus: {
|
|
38
|
+
publish(event: Event): Promise<unknown>;
|
|
39
|
+
}, logger: {
|
|
40
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
41
|
+
}, event: Event): void;
|
|
28
42
|
//# sourceMappingURL=runtimeEvents.core.d.ts.map
|
|
@@ -60,4 +60,30 @@ export function createReadinessEventPayload(runtimeId, state, ready, reason) {
|
|
|
60
60
|
...(reason !== undefined && { reason }),
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Publishes a runtime lifecycle event without letting a failing handler,
|
|
65
|
+
* middleware, or a disposed bus turn into an unhandled promise rejection.
|
|
66
|
+
*
|
|
67
|
+
* Runtime events are informational; a failure to deliver one must never
|
|
68
|
+
* abort startup or shutdown, so the rejection is logged and swallowed.
|
|
69
|
+
*/
|
|
70
|
+
export function publishRuntimeEvent(eventBus, logger, event) {
|
|
71
|
+
let result;
|
|
72
|
+
try {
|
|
73
|
+
result = eventBus.publish(event);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
logger.warn("Failed to publish runtime event.", {
|
|
77
|
+
eventType: event.type,
|
|
78
|
+
error: error instanceof Error ? error.message : String(error),
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
void Promise.resolve(result).catch((error) => {
|
|
83
|
+
logger.warn("Failed to publish runtime event.", {
|
|
84
|
+
eventType: event.type,
|
|
85
|
+
error: error instanceof Error ? error.message : String(error),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
63
89
|
//# sourceMappingURL=runtimeEvents.core.js.map
|