@zudojs/core 1.0.0 → 1.2.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.
Files changed (41) hide show
  1. package/README.md +15 -5
  2. package/dist/application/applicationContext.context.d.ts +12 -1
  3. package/dist/application/applicationContext.context.js +10 -2
  4. package/dist/application/createApplication.js +6 -1
  5. package/dist/configuration/configurationManager.manager.d.ts +10 -0
  6. package/dist/configuration/configurationManager.manager.js +19 -1
  7. package/dist/container/container.d.ts +4 -3
  8. package/dist/container/container.js +15 -10
  9. package/dist/container/container.lifetime.d.ts +21 -0
  10. package/dist/container/container.lifetime.js +39 -0
  11. package/dist/container/scope.d.ts +3 -1
  12. package/dist/context/provider/contextStorage.storage.d.ts +8 -0
  13. package/dist/context/provider/contextStorage.storage.js +12 -1
  14. package/dist/lifecycle/core/lifecycle.d.ts +8 -3
  15. package/dist/lifecycle/core/lifecycle.js +18 -5
  16. package/dist/lifecycle/core/lifecycle.rollback.d.ts +18 -0
  17. package/dist/lifecycle/core/lifecycle.rollback.js +34 -0
  18. package/dist/logging/core/logEntry.entry.js +12 -1
  19. package/dist/modules/moduleLifecycle/index.d.ts +1 -1
  20. package/dist/modules/moduleLifecycle/moduleLifecycle.lifecycle.d.ts +30 -5
  21. package/dist/modules/moduleLifecycle/moduleLifecycle.lifecycle.js +58 -17
  22. package/dist/modules/moduleLifecycle/moduleLifecycle.stateMachine.d.ts +1 -1
  23. package/dist/modules/moduleLifecycle/moduleLifecycle.stateMachine.js +3 -1
  24. package/dist/modules/moduleLifecycle/moduleLifecycle.type.d.ts +20 -0
  25. package/dist/modules/moduleLoader/moduleLoader.loader.js +61 -25
  26. package/dist/runtime/runtime.d.ts +2 -0
  27. package/dist/runtime/runtime.js +15 -4
  28. package/dist/runtime/runtimeBootstrap/pipeline/runtimeBootstrap.pipeline.d.ts +3 -1
  29. package/dist/runtime/runtimeBootstrap/pipeline/runtimeBootstrap.pipeline.js +29 -5
  30. package/dist/runtime/runtimeOptions/runtimeOptions.defaults.js +3 -1
  31. package/dist/runtime/runtimeOptions/runtimeOptions.mode.d.ts +14 -0
  32. package/dist/runtime/runtimeOptions/runtimeOptions.mode.js +16 -0
  33. package/dist/runtime/runtimeOptions/runtimeOptions.resolver.js +17 -1
  34. package/dist/runtime/runtimeOptions/runtimeOptions.type.d.ts +18 -2
  35. package/dist/runtime/runtimeOptions/runtimeOptions.validation.d.ts +1 -1
  36. package/dist/runtime/runtimeShutdown/pipeline/runtimeShutdown.pipeline.js +10 -2
  37. package/dist/runtime/runtimeSignals/runtimeSignals.d.ts +14 -3
  38. package/dist/runtime/runtimeSignals/runtimeSignals.fatal.d.ts +29 -0
  39. package/dist/runtime/runtimeSignals/runtimeSignals.fatal.js +51 -0
  40. package/dist/runtime/runtimeSignals/runtimeSignals.js +15 -4
  41. package/package.json +7 -3
@@ -17,16 +17,24 @@ export async function executeShutdownPipeline(options, services, state, signal,
17
17
  runtimeName: services.runtimeName,
18
18
  };
19
19
  if (options.stopModules) {
20
- await runShutdownPhase("stopping", "stopped", () => moduleLifecycle.stop(), moduleLifecycle, options.continueOnStopError, (count) => {
20
+ await runShutdownPhase("stopping", "stopped", () => moduleLifecycle.stop(phaseOptions(options.continueOnStopError)), moduleLifecycle, options.continueOnStopError, (count) => {
21
21
  state.counters.stoppedModules = count;
22
22
  }, RuntimeErrorCode.MODULE_STOP_FAILED, identity, state, publish, log);
23
23
  }
24
24
  if (options.destroyModules) {
25
- await runShutdownPhase("destroying", "destroyed", () => moduleLifecycle.destroy(), moduleLifecycle, options.continueOnDestroyError, (count) => {
25
+ await runShutdownPhase("destroying", "destroyed", () => moduleLifecycle.destroy(phaseOptions(options.continueOnDestroyError)), moduleLifecycle, options.continueOnDestroyError, (count) => {
26
26
  state.counters.destroyedModules = count;
27
27
  }, RuntimeErrorCode.MODULE_DESTROY_FAILED, identity, state, publish, log);
28
28
  }
29
29
  }
30
+ /**
31
+ * Per-phase options for the ModuleLifecycleManager: the runtime flag
32
+ * relaxes the manager when on and leaves its own setting when off
33
+ * (see the bootstrap pipeline for the rationale).
34
+ */
35
+ function phaseOptions(continueOnError) {
36
+ return continueOnError ? { continueOnError: true } : {};
37
+ }
30
38
  async function runShutdownPhase(phase, donePhase, run, moduleLifecycle, continueOnError, setCount, code, identity, state, publish, log) {
31
39
  publish(phase);
32
40
  log("debug", `Runtime modules ${phase}.`);
@@ -1,5 +1,11 @@
1
1
  import type { Logger } from "../../logging/core/logger.js";
2
2
  import type { RuntimeSignalOptions } from "../runtimeOptions/runtimeOptions.type.js";
3
+ /**
4
+ * Fatal-exit settings are optional on the manager so that a
5
+ * hand-assembled `signals` object from before they existed still
6
+ * type-checks; they default to exiting after 10 seconds.
7
+ */
8
+ type ManagerSignalOptions = Required<Omit<RuntimeSignalOptions, "exitOnFatalError" | "fatalExitTimeout">> & Pick<RuntimeSignalOptions, "exitOnFatalError" | "fatalExitTimeout">;
3
9
  /**
4
10
  * Termination signals the runtime can react to.
5
11
  */
@@ -39,7 +45,7 @@ export interface RuntimeSignalHandlers {
39
45
  * Options for the signal manager.
40
46
  */
41
47
  export interface RuntimeSignalManagerOptions {
42
- readonly signals: Required<RuntimeSignalOptions>;
48
+ readonly signals: ManagerSignalOptions;
43
49
  readonly target?: RuntimeSignalTarget;
44
50
  readonly logger?: Logger;
45
51
  }
@@ -52,8 +58,11 @@ export interface RuntimeSignalManagerOptions {
52
58
  * `unregister()`; both are idempotent.
53
59
  * - The first termination signal triggers `onSignal` (graceful stop).
54
60
  * - A second termination signal while the first is still being
55
- * handled is logged and ignored, unless `forceExitOnSecondSignal`
56
- * is on, in which case `target.exit(forceExitCode)` is called.
61
+ * handled exits with `forceExitCode` when `forceExitOnSecondSignal`
62
+ * is on (the default), and is logged and ignored otherwise.
63
+ * - An uncaught exception or unhandled rejection runs the fatal
64
+ * handler and then exits with code 1, unless `exitOnFatalError` is
65
+ * off; `fatalExitTimeout` bounds a shutdown that hangs.
57
66
  * - `process.exit` is never called otherwise.
58
67
  */
59
68
  export declare class RuntimeSignalManager {
@@ -88,6 +97,8 @@ export declare class RuntimeSignalManager {
88
97
  unregister(): void;
89
98
  private createListener;
90
99
  private handleSignal;
100
+ private runFatal;
91
101
  private run;
92
102
  }
103
+ export {};
93
104
  //# sourceMappingURL=runtimeSignals.d.ts.map
@@ -0,0 +1,29 @@
1
+ import type { Logger } from "../../logging/core/logger.js";
2
+ import type { RuntimeSignalTarget } from "./runtimeSignals.js";
3
+ /** Default grace period for a fatal-error shutdown, in milliseconds. */
4
+ export declare const DEFAULT_FATAL_EXIT_TIMEOUT = 10000;
5
+ /**
6
+ * How a fatal process event (uncaughtException, unhandledRejection)
7
+ * ends the process.
8
+ */
9
+ export interface RuntimeFatalExitPolicy {
10
+ /** Exit non-zero once the fatal-error shutdown settles. */
11
+ readonly exitOnFatalError: boolean;
12
+ /** Exit anyway if that shutdown takes longer than this. */
13
+ readonly fatalExitTimeout: number;
14
+ /** Exit code used for a fatal exit. */
15
+ readonly exitCode: number;
16
+ }
17
+ /**
18
+ * Runs the runtime's fatal-error handler and then ends the process
19
+ * with a non-zero code.
20
+ *
21
+ * Installing an `uncaughtException` listener suppresses Node's own
22
+ * crash, so without an explicit exit the process either ended with
23
+ * code 0 (supervisors saw a clean exit and never restarted or alerted)
24
+ * or lingered as a stopped zombie behind any open handle. The grace
25
+ * timer bounds a shutdown that hangs; both are skipped when
26
+ * `exitOnFatalError` is off.
27
+ */
28
+ export declare function runFatalHandler(handler: () => void | Promise<void> | undefined, target: RuntimeSignalTarget | undefined, policy: RuntimeFatalExitPolicy, logger: Logger | undefined, event: string): void;
29
+ //# sourceMappingURL=runtimeSignals.fatal.d.ts.map
@@ -0,0 +1,51 @@
1
+ /** Default grace period for a fatal-error shutdown, in milliseconds. */
2
+ export const DEFAULT_FATAL_EXIT_TIMEOUT = 10_000;
3
+ /**
4
+ * Runs the runtime's fatal-error handler and then ends the process
5
+ * with a non-zero code.
6
+ *
7
+ * Installing an `uncaughtException` listener suppresses Node's own
8
+ * crash, so without an explicit exit the process either ended with
9
+ * code 0 (supervisors saw a clean exit and never restarted or alerted)
10
+ * or lingered as a stopped zombie behind any open handle. The grace
11
+ * timer bounds a shutdown that hangs; both are skipped when
12
+ * `exitOnFatalError` is off.
13
+ */
14
+ export function runFatalHandler(handler, target, policy, logger, event) {
15
+ const exit = () => {
16
+ if (!policy.exitOnFatalError)
17
+ return;
18
+ target?.exit?.(policy.exitCode);
19
+ };
20
+ let timer;
21
+ if (policy.exitOnFatalError && policy.fatalExitTimeout > 0) {
22
+ timer = setTimeout(() => {
23
+ logger?.error(`Runtime shutdown after ${event} timed out; exiting.`, {
24
+ event,
25
+ timeoutMs: policy.fatalExitTimeout,
26
+ });
27
+ exit();
28
+ }, Math.min(policy.fatalExitTimeout, 2_147_483_647));
29
+ timer.unref?.();
30
+ }
31
+ const finish = () => {
32
+ if (timer !== undefined)
33
+ clearTimeout(timer);
34
+ exit();
35
+ };
36
+ let result;
37
+ try {
38
+ result = handler();
39
+ }
40
+ catch (error) {
41
+ logger?.error(`Runtime ${event} handler failed.`, error, { event });
42
+ finish();
43
+ return;
44
+ }
45
+ void Promise.resolve(result)
46
+ .catch((error) => {
47
+ logger?.error(`Runtime ${event} handler failed.`, error, { event });
48
+ })
49
+ .finally(finish);
50
+ }
51
+ //# sourceMappingURL=runtimeSignals.fatal.js.map
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_FATAL_EXIT_TIMEOUT, runFatalHandler, } from "./runtimeSignals.fatal.js";
1
2
  /**
2
3
  * Registers and removes process signal/exception handlers on behalf
3
4
  * of the runtime.
@@ -7,8 +8,11 @@
7
8
  * `unregister()`; both are idempotent.
8
9
  * - The first termination signal triggers `onSignal` (graceful stop).
9
10
  * - A second termination signal while the first is still being
10
- * handled is logged and ignored, unless `forceExitOnSecondSignal`
11
- * is on, in which case `target.exit(forceExitCode)` is called.
11
+ * handled exits with `forceExitCode` when `forceExitOnSecondSignal`
12
+ * is on (the default), and is logged and ignored otherwise.
13
+ * - An uncaught exception or unhandled rejection runs the fatal
14
+ * handler and then exits with code 1, unless `exitOnFatalError` is
15
+ * off; `fatalExitTimeout` bounds a shutdown that hangs.
12
16
  * - `process.exit` is never called otherwise.
13
17
  */
14
18
  export class RuntimeSignalManager {
@@ -91,11 +95,11 @@ export class RuntimeSignalManager {
91
95
  };
92
96
  case "uncaughtException":
93
97
  return ((error) => {
94
- this.run(() => this._handlers?.onUncaughtException(error), "uncaughtException");
98
+ this.runFatal(() => this._handlers?.onUncaughtException(error), "uncaughtException");
95
99
  });
96
100
  case "unhandledRejection":
97
101
  return ((reason) => {
98
- this.run(() => this._handlers?.onUnhandledRejection(reason), "unhandledRejection");
102
+ this.runFatal(() => this._handlers?.onUnhandledRejection(reason), "unhandledRejection");
99
103
  });
100
104
  default:
101
105
  return () => { };
@@ -118,6 +122,13 @@ export class RuntimeSignalManager {
118
122
  });
119
123
  this.run(() => this._handlers?.onSignal(signal), signal);
120
124
  }
125
+ runFatal(handler, event) {
126
+ runFatalHandler(handler, this._target, {
127
+ exitOnFatalError: this._signals.exitOnFatalError ?? true,
128
+ fatalExitTimeout: this._signals.fatalExitTimeout ?? DEFAULT_FATAL_EXIT_TIMEOUT,
129
+ exitCode: 1,
130
+ }, this._logger, event);
131
+ }
121
132
  run(handler, event) {
122
133
  try {
123
134
  const result = handler();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/core",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Application lifecycle management, execution context propagation, and runtime orchestration for Zudojs applications.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,10 +55,14 @@
55
55
  "vitest": "^4.1.11"
56
56
  },
57
57
  "dependencies": {
58
- "@zudojs/errors": "1.0.0",
59
- "@zudojs/constants": "1.0.0"
58
+ "@zudojs/errors": "1.1.0",
59
+ "@zudojs/constants": "1.1.0"
60
60
  },
61
61
  "license": "MIT",
62
+ "author": {
63
+ "name": "Oluwayemi Oyinlola",
64
+ "url": "https://github.com/oyinlola-tech"
65
+ },
62
66
  "publishConfig": {
63
67
  "access": "public"
64
68
  },