@zudojs/runtime 1.2.0 → 1.2.1
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.
|
@@ -50,6 +50,15 @@ export declare class LifecycleManager {
|
|
|
50
50
|
* Initializes all modules in dependency order.
|
|
51
51
|
*/
|
|
52
52
|
initialize(): Promise<LifecycleResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Returns the first declared dependency of `moduleId` that is known to
|
|
55
|
+
* have failed or been skipped, or `undefined` when none has.
|
|
56
|
+
*
|
|
57
|
+
* Only direct dependencies are inspected: `blocked` already contains
|
|
58
|
+
* every module skipped by an earlier group, and groups are visited in
|
|
59
|
+
* dependency order, so the cascade is transitive.
|
|
60
|
+
*/
|
|
61
|
+
private findBlockingDependency;
|
|
53
62
|
/**
|
|
54
63
|
* Initializes a single module, converting a throw into a failure.
|
|
55
64
|
*/
|
|
@@ -116,14 +116,44 @@ export class LifecycleManager {
|
|
|
116
116
|
const groups = this.options.parallelInitialization
|
|
117
117
|
? depGraph.parallelGroups
|
|
118
118
|
: depGraph.order.map((moduleId) => [moduleId]);
|
|
119
|
+
// Ids that must not have dependents initialized on top of them:
|
|
120
|
+
// modules whose own hook threw, plus modules already skipped for the
|
|
121
|
+
// same reason, so the skip cascades transitively. `continueOnFailure`
|
|
122
|
+
// used to consult only the failure COUNT, so a dependent of a broken
|
|
123
|
+
// module was initialized, readied, and reported as started — an API
|
|
124
|
+
// serving traffic against a database that never came up.
|
|
125
|
+
const blocked = new Set();
|
|
119
126
|
for (const group of groups) {
|
|
120
127
|
if (this.cancellation.isCancelled) {
|
|
121
128
|
break;
|
|
122
129
|
}
|
|
123
|
-
const
|
|
130
|
+
const runnable = [];
|
|
131
|
+
for (const moduleId of group) {
|
|
132
|
+
const blocker = this.findBlockingDependency(moduleId, blocked);
|
|
133
|
+
if (blocker === undefined) {
|
|
134
|
+
runnable.push(moduleId);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const failure = {
|
|
138
|
+
moduleId,
|
|
139
|
+
phase: "initialize",
|
|
140
|
+
error: new RuntimeStartError(`Module "${moduleId}" was not initialized because its ` +
|
|
141
|
+
`dependency "${blocker}" failed.`, { phase: "initialize", failedModuleId: blocker }),
|
|
142
|
+
durationMs: 0,
|
|
143
|
+
};
|
|
144
|
+
failed.push(failure);
|
|
145
|
+
blocked.add(moduleId);
|
|
146
|
+
this.logger.error(`Module "${moduleId}" was skipped because its dependency "${blocker}" failed.`);
|
|
147
|
+
this.emitModuleEvent("runtime.module.failed", moduleId, "failed", {
|
|
148
|
+
durationMs: 0,
|
|
149
|
+
error: failure.error,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const results = await Promise.all(runnable.map((moduleId) => this.initializeModule(moduleId)));
|
|
124
153
|
for (const result of results) {
|
|
125
154
|
if (result.failure) {
|
|
126
155
|
failed.push(result.failure);
|
|
156
|
+
blocked.add(result.moduleId);
|
|
127
157
|
}
|
|
128
158
|
else if (!result.abandoned) {
|
|
129
159
|
succeeded.push(result.moduleId);
|
|
@@ -140,6 +170,18 @@ export class LifecycleManager {
|
|
|
140
170
|
durationMs: Date.now() - startTime,
|
|
141
171
|
});
|
|
142
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Returns the first declared dependency of `moduleId` that is known to
|
|
175
|
+
* have failed or been skipped, or `undefined` when none has.
|
|
176
|
+
*
|
|
177
|
+
* Only direct dependencies are inspected: `blocked` already contains
|
|
178
|
+
* every module skipped by an earlier group, and groups are visited in
|
|
179
|
+
* dependency order, so the cascade is transitive.
|
|
180
|
+
*/
|
|
181
|
+
findBlockingDependency(moduleId, blocked) {
|
|
182
|
+
const dependencies = this.modules.get(moduleId)?.dependencies ?? [];
|
|
183
|
+
return dependencies.find((dependency) => blocked.has(dependency));
|
|
184
|
+
}
|
|
143
185
|
/**
|
|
144
186
|
* Initializes a single module, converting a throw into a failure.
|
|
145
187
|
*/
|
|
@@ -79,6 +79,14 @@ export interface LifecycleManagerOptions {
|
|
|
79
79
|
* closing the same resources.
|
|
80
80
|
*/
|
|
81
81
|
readonly shutdownTimeout?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Keeps initializing and starting after a module fails.
|
|
84
|
+
*
|
|
85
|
+
* Only modules INDEPENDENT of the failure continue: a module that
|
|
86
|
+
* declares a failed (or already skipped) module in `dependencies` is
|
|
87
|
+
* never initialized or readied, and is reported in `failed` with the
|
|
88
|
+
* blocking dependency named. Defaults to false.
|
|
89
|
+
*/
|
|
82
90
|
readonly continueOnFailure?: boolean;
|
|
83
91
|
/**
|
|
84
92
|
* Receives one event per module per lifecycle phase.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Manages multiple runtime instances for scenarios where Zudojs
|
|
5
5
|
* runs multiple applications or workers in a single process.
|
|
6
6
|
*/
|
|
7
|
+
import { RuntimeStateError } from "../runtimeError/index.js";
|
|
7
8
|
/**
|
|
8
9
|
* Runtime registry for managing multiple runtime instances.
|
|
9
10
|
*
|
|
@@ -19,7 +20,7 @@ export class RuntimeRegistry {
|
|
|
19
20
|
*/
|
|
20
21
|
register(id, runtime) {
|
|
21
22
|
if (this.runtimes.has(id)) {
|
|
22
|
-
throw new
|
|
23
|
+
throw new RuntimeStateError(`Runtime "${id}" is already registered.`);
|
|
23
24
|
}
|
|
24
25
|
this.runtimes.set(id, runtime);
|
|
25
26
|
}
|
|
@@ -56,7 +57,7 @@ export class RuntimeRegistry {
|
|
|
56
57
|
require(id) {
|
|
57
58
|
const runtime = this.runtimes.get(id);
|
|
58
59
|
if (!runtime) {
|
|
59
|
-
throw new
|
|
60
|
+
throw new RuntimeStateError(`Runtime "${id}" not found.`);
|
|
60
61
|
}
|
|
61
62
|
return runtime;
|
|
62
63
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_RUNTIME_OPTIONS } from "./runtimeOptions.type.js";
|
|
2
2
|
import { createRuntimeId } from "../runtimeContext/runtimeContext.factory.js";
|
|
3
|
+
import { RuntimeError } from "@zudojs/errors";
|
|
3
4
|
/**
|
|
4
5
|
* Resolves runtime options with defaults applied.
|
|
5
6
|
*
|
|
@@ -24,22 +25,40 @@ export function resolveRuntimeOptions(options) {
|
|
|
24
25
|
*/
|
|
25
26
|
export function validateRuntimeOptions(options) {
|
|
26
27
|
if (!options.environment) {
|
|
27
|
-
throw new
|
|
28
|
+
throw new RuntimeError("Runtime environment is required.", {
|
|
29
|
+
metadata: { option: "environment" },
|
|
30
|
+
});
|
|
28
31
|
}
|
|
29
32
|
if (!options.applicationName) {
|
|
30
|
-
throw new
|
|
33
|
+
throw new RuntimeError("Application name is required.", {
|
|
34
|
+
metadata: { option: "applicationName" },
|
|
35
|
+
});
|
|
31
36
|
}
|
|
32
37
|
if (options.shutdownTimeout <= 0) {
|
|
33
|
-
throw new
|
|
38
|
+
throw new RuntimeError("Shutdown timeout must be positive.", {
|
|
39
|
+
metadata: { option: "shutdownTimeout", value: options.shutdownTimeout },
|
|
40
|
+
});
|
|
34
41
|
}
|
|
35
42
|
if (options.startupTimeout <= 0) {
|
|
36
|
-
throw new
|
|
43
|
+
throw new RuntimeError("Startup timeout must be positive.", {
|
|
44
|
+
metadata: { option: "startupTimeout", value: options.startupTimeout },
|
|
45
|
+
});
|
|
37
46
|
}
|
|
38
47
|
if (!Number.isFinite(options.fatalExitTimeout) || options.fatalExitTimeout < 0) {
|
|
39
|
-
throw new
|
|
48
|
+
throw new RuntimeError(`Fatal exit timeout must be a finite, non-negative number, got ${options.fatalExitTimeout}.`, {
|
|
49
|
+
metadata: {
|
|
50
|
+
option: "fatalExitTimeout",
|
|
51
|
+
value: options.fatalExitTimeout,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
40
54
|
}
|
|
41
55
|
if (options.readinessCheckTimeout < 0) {
|
|
42
|
-
throw new
|
|
56
|
+
throw new RuntimeError(`Readiness check timeout must be zero or positive, got ${options.readinessCheckTimeout}. Use 0 to run checks without a bound.`, {
|
|
57
|
+
metadata: {
|
|
58
|
+
option: "readinessCheckTimeout",
|
|
59
|
+
value: options.readinessCheckTimeout,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
43
62
|
}
|
|
44
63
|
}
|
|
45
64
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/runtime",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Application lifecycle orchestrator with dependency ordering, rollback, signals, and readiness checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"vitest": "^4.1.11"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@zudojs/constants": "1.1.
|
|
31
|
-
"@zudojs/container": "1.1.
|
|
32
|
-
"@zudojs/core": "1.2.
|
|
33
|
-
"@zudojs/errors": "1.
|
|
34
|
-
"@zudojs/events": "1.
|
|
35
|
-
"@zudojs/logger": "1.
|
|
30
|
+
"@zudojs/constants": "1.1.1",
|
|
31
|
+
"@zudojs/container": "1.1.2",
|
|
32
|
+
"@zudojs/core": "1.2.1",
|
|
33
|
+
"@zudojs/errors": "1.2.0",
|
|
34
|
+
"@zudojs/events": "1.2.0",
|
|
35
|
+
"@zudojs/logger": "1.3.0"
|
|
36
36
|
},
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"author": {
|