@zudojs/runtime 0.0.1 → 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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -6
  3. package/dist/dependencyGraph/dependencyGraph.core.d.ts +11 -5
  4. package/dist/dependencyGraph/dependencyGraph.core.js +13 -12
  5. package/dist/dependencyGraph/dependencyGraph.type.d.ts +0 -7
  6. package/dist/dependencyGraph/index.d.ts +2 -2
  7. package/dist/dependencyGraph/index.js +1 -1
  8. package/dist/health/health.core.d.ts +12 -0
  9. package/dist/health/health.core.js +55 -0
  10. package/dist/health/index.d.ts +5 -0
  11. package/dist/health/index.js +5 -0
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +4 -1
  14. package/dist/lifecycle/index.d.ts +1 -1
  15. package/dist/lifecycle/lifecycle.core.d.ts +41 -10
  16. package/dist/lifecycle/lifecycle.core.js +238 -61
  17. package/dist/lifecycle/lifecycle.type.d.ts +48 -1
  18. package/dist/readiness/index.d.ts +1 -1
  19. package/dist/readiness/readiness.core.d.ts +36 -4
  20. package/dist/readiness/readiness.core.js +109 -6
  21. package/dist/readiness/readiness.type.d.ts +15 -1
  22. package/dist/registry/registry.core.d.ts +14 -1
  23. package/dist/registry/registry.core.js +41 -2
  24. package/dist/runtime/runtime.core.d.ts +75 -5
  25. package/dist/runtime/runtime.core.js +199 -43
  26. package/dist/runtimeContext/index.d.ts +2 -2
  27. package/dist/runtimeContext/index.js +1 -1
  28. package/dist/runtimeContext/runtimeContext.core.d.ts +13 -4
  29. package/dist/runtimeContext/runtimeContext.core.js +22 -11
  30. package/dist/runtimeContext/runtimeContext.type.d.ts +12 -13
  31. package/dist/runtimeError/runtimeError.base.js +1 -1
  32. package/dist/runtimeEvents/index.d.ts +2 -2
  33. package/dist/runtimeEvents/index.js +1 -1
  34. package/dist/runtimeEvents/runtimeEvents.core.d.ts +15 -1
  35. package/dist/runtimeEvents/runtimeEvents.core.js +26 -0
  36. package/dist/runtimeEvents/runtimeEvents.type.d.ts +19 -4
  37. package/dist/runtimeOptions/runtimeOptions.core.js +8 -2
  38. package/dist/runtimeOptions/runtimeOptions.type.d.ts +29 -1
  39. package/dist/runtimeOptions/runtimeOptions.type.js +6 -0
  40. package/dist/runtimeState/index.d.ts +1 -1
  41. package/dist/runtimeState/runtimeState.core.d.ts +4 -0
  42. package/dist/runtimeState/runtimeState.core.js +17 -4
  43. package/dist/runtimeState/runtimeState.type.d.ts +14 -17
  44. package/dist/shutdown/shutdown.core.d.ts +20 -1
  45. package/dist/shutdown/shutdown.core.js +55 -29
  46. package/dist/signalHandler/signalHandler.core.d.ts +53 -13
  47. package/dist/signalHandler/signalHandler.core.js +97 -27
  48. package/dist/startup/startup.core.d.ts +3 -2
  49. package/dist/startup/startup.core.js +52 -46
  50. package/dist/testRuntime/index.d.ts +6 -1
  51. package/dist/testRuntime/index.js +5 -1
  52. package/dist/testRuntime/testRuntime.core.d.ts +21 -2
  53. package/dist/testRuntime/testRuntime.core.js +23 -6
  54. package/package.json +29 -22
  55. package/dist/.tsbuildinfo +0 -1
  56. package/dist/dependencyGraph/dependencyGraph.core.d.ts.map +0 -1
  57. package/dist/dependencyGraph/dependencyGraph.core.js.map +0 -1
  58. package/dist/dependencyGraph/dependencyGraph.type.d.ts.map +0 -1
  59. package/dist/dependencyGraph/dependencyGraph.type.js.map +0 -1
  60. package/dist/dependencyGraph/index.d.ts.map +0 -1
  61. package/dist/dependencyGraph/index.js.map +0 -1
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/lifecycle/index.d.ts.map +0 -1
  65. package/dist/lifecycle/index.js.map +0 -1
  66. package/dist/lifecycle/lifecycle.core.d.ts.map +0 -1
  67. package/dist/lifecycle/lifecycle.core.js.map +0 -1
  68. package/dist/lifecycle/lifecycle.type.d.ts.map +0 -1
  69. package/dist/lifecycle/lifecycle.type.js.map +0 -1
  70. package/dist/readiness/index.d.ts.map +0 -1
  71. package/dist/readiness/index.js.map +0 -1
  72. package/dist/readiness/readiness.core.d.ts.map +0 -1
  73. package/dist/readiness/readiness.core.js.map +0 -1
  74. package/dist/readiness/readiness.type.d.ts.map +0 -1
  75. package/dist/readiness/readiness.type.js.map +0 -1
  76. package/dist/registry/index.d.ts.map +0 -1
  77. package/dist/registry/index.js.map +0 -1
  78. package/dist/registry/registry.core.d.ts.map +0 -1
  79. package/dist/registry/registry.core.js.map +0 -1
  80. package/dist/runtime/runtime.core.d.ts.map +0 -1
  81. package/dist/runtime/runtime.core.js.map +0 -1
  82. package/dist/runtimeContext/index.d.ts.map +0 -1
  83. package/dist/runtimeContext/index.js.map +0 -1
  84. package/dist/runtimeContext/runtimeContext.core.d.ts.map +0 -1
  85. package/dist/runtimeContext/runtimeContext.core.js.map +0 -1
  86. package/dist/runtimeContext/runtimeContext.factory.d.ts.map +0 -1
  87. package/dist/runtimeContext/runtimeContext.factory.js.map +0 -1
  88. package/dist/runtimeContext/runtimeContext.type.d.ts.map +0 -1
  89. package/dist/runtimeContext/runtimeContext.type.js.map +0 -1
  90. package/dist/runtimeError/index.d.ts.map +0 -1
  91. package/dist/runtimeError/index.js.map +0 -1
  92. package/dist/runtimeError/runtimeError.base.d.ts.map +0 -1
  93. package/dist/runtimeError/runtimeError.base.js.map +0 -1
  94. package/dist/runtimeEvents/index.d.ts.map +0 -1
  95. package/dist/runtimeEvents/index.js.map +0 -1
  96. package/dist/runtimeEvents/runtimeEvents.core.d.ts.map +0 -1
  97. package/dist/runtimeEvents/runtimeEvents.core.js.map +0 -1
  98. package/dist/runtimeEvents/runtimeEvents.type.d.ts.map +0 -1
  99. package/dist/runtimeEvents/runtimeEvents.type.js.map +0 -1
  100. package/dist/runtimeOptions/index.d.ts.map +0 -1
  101. package/dist/runtimeOptions/index.js.map +0 -1
  102. package/dist/runtimeOptions/runtimeOptions.core.d.ts.map +0 -1
  103. package/dist/runtimeOptions/runtimeOptions.core.js.map +0 -1
  104. package/dist/runtimeOptions/runtimeOptions.type.d.ts.map +0 -1
  105. package/dist/runtimeOptions/runtimeOptions.type.js.map +0 -1
  106. package/dist/runtimeState/index.d.ts.map +0 -1
  107. package/dist/runtimeState/index.js.map +0 -1
  108. package/dist/runtimeState/runtimeState.core.d.ts.map +0 -1
  109. package/dist/runtimeState/runtimeState.core.js.map +0 -1
  110. package/dist/runtimeState/runtimeState.type.d.ts.map +0 -1
  111. package/dist/runtimeState/runtimeState.type.js.map +0 -1
  112. package/dist/shutdown/index.d.ts.map +0 -1
  113. package/dist/shutdown/index.js.map +0 -1
  114. package/dist/shutdown/shutdown.core.d.ts.map +0 -1
  115. package/dist/shutdown/shutdown.core.js.map +0 -1
  116. package/dist/signalHandler/index.d.ts.map +0 -1
  117. package/dist/signalHandler/index.js.map +0 -1
  118. package/dist/signalHandler/signalHandler.core.d.ts.map +0 -1
  119. package/dist/signalHandler/signalHandler.core.js.map +0 -1
  120. package/dist/startup/index.d.ts.map +0 -1
  121. package/dist/startup/index.js.map +0 -1
  122. package/dist/startup/startup.core.d.ts.map +0 -1
  123. package/dist/startup/startup.core.js.map +0 -1
  124. package/dist/testRuntime/index.d.ts.map +0 -1
  125. package/dist/testRuntime/index.js.map +0 -1
  126. package/dist/testRuntime/testRuntime.core.d.ts.map +0 -1
  127. package/dist/testRuntime/testRuntime.core.js.map +0 -1
@@ -1,90 +1,160 @@
1
+ /** Default grace period for a fatal-error shutdown. */
2
+ const DEFAULT_FATAL_EXIT_TIMEOUT = 10_000;
1
3
  /**
2
4
  * Signal handler for process lifecycle events.
3
5
  */
4
6
  export class SignalHandler {
5
7
  logger;
6
- handleSignals;
7
- handleFatalErrors;
8
+ options;
8
9
  shutdownHandler = null;
9
10
  isShuttingDown = false;
11
+ registered = false;
12
+ forcedExitTimer = null;
10
13
  constructor(logger, options) {
11
14
  this.logger = logger;
12
- this.handleSignals = options.handleSignals;
13
- this.handleFatalErrors = options.handleFatalErrors;
15
+ this.options = options;
14
16
  }
15
17
  /**
16
18
  * Registers signal handlers.
19
+ *
20
+ * Registering twice is a no-op rather than a second set of listeners,
21
+ * so a stop/start cycle cannot accumulate handlers.
17
22
  */
18
23
  register(shutdownHandler) {
19
24
  this.shutdownHandler = shutdownHandler;
20
- if (this.handleSignals) {
25
+ if (this.registered) {
26
+ return;
27
+ }
28
+ this.registered = true;
29
+ this.isShuttingDown = false;
30
+ if (this.options.handleSignals) {
21
31
  process.on("SIGTERM", this.handleTermination);
22
32
  process.on("SIGINT", this.handleInterruption);
23
33
  }
24
- if (this.handleFatalErrors) {
34
+ if (this.options.handleFatalErrors) {
25
35
  process.on("uncaughtException", this.handleUncaughtException);
26
36
  process.on("unhandledRejection", this.handleUnhandledRejection);
27
37
  }
28
38
  }
29
39
  /**
30
40
  * Removes all signal handlers.
41
+ *
42
+ * Safe to call when nothing is registered, so it can run on both the
43
+ * success and failure paths of a shutdown.
31
44
  */
32
45
  unregister() {
33
- if (this.handleSignals) {
46
+ if (this.forcedExitTimer) {
47
+ clearTimeout(this.forcedExitTimer);
48
+ this.forcedExitTimer = null;
49
+ }
50
+ if (!this.registered) {
51
+ this.shutdownHandler = null;
52
+ return;
53
+ }
54
+ if (this.options.handleSignals) {
34
55
  process.off("SIGTERM", this.handleTermination);
35
56
  process.off("SIGINT", this.handleInterruption);
36
57
  }
37
- if (this.handleFatalErrors) {
58
+ if (this.options.handleFatalErrors) {
38
59
  process.off("uncaughtException", this.handleUncaughtException);
39
60
  process.off("unhandledRejection", this.handleUnhandledRejection);
40
61
  }
62
+ this.registered = false;
63
+ this.isShuttingDown = false;
41
64
  this.shutdownHandler = null;
42
65
  }
43
66
  /**
44
- * Handles SIGTERM signal.
67
+ * Whether a shutdown has been initiated by a signal.
45
68
  */
69
+ get shuttingDown() {
70
+ return this.isShuttingDown;
71
+ }
46
72
  handleTermination = () => {
47
73
  this.logger.info("Received SIGTERM signal.");
48
- this.initiateShutdown();
74
+ this.initiateShutdown("SIGTERM");
49
75
  };
50
- /**
51
- * Handles SIGINT signal.
52
- */
53
76
  handleInterruption = () => {
54
77
  this.logger.info("Received SIGINT signal.");
55
- this.initiateShutdown();
78
+ this.initiateShutdown("SIGINT");
56
79
  };
57
80
  /**
58
81
  * Handles uncaught exceptions.
82
+ *
83
+ * The process state is no longer trustworthy after one, so this shuts
84
+ * down and then exits non-zero rather than continuing to serve.
59
85
  */
60
86
  handleUncaughtException = (error) => {
61
- this.logger.error("Uncaught exception.", { errorMessage: error.message });
62
- if (this.handleFatalErrors) {
63
- this.initiateShutdown();
64
- }
87
+ this.logger.error("Uncaught exception.", {
88
+ errorMessage: error.message,
89
+ stack: error.stack,
90
+ });
91
+ this.initiateFatalShutdown();
65
92
  };
66
- /**
67
- * Handles unhandled promise rejections.
68
- */
69
93
  handleUnhandledRejection = (reason) => {
70
94
  const message = reason instanceof Error ? reason.message : String(reason);
71
- this.logger.error("Unhandled rejection.", { reason: message });
72
- if (this.handleFatalErrors) {
73
- this.initiateShutdown();
74
- }
95
+ this.logger.error("Unhandled rejection.", {
96
+ reason: message,
97
+ ...(reason instanceof Error && reason.stack
98
+ ? { stack: reason.stack }
99
+ : {}),
100
+ });
101
+ this.initiateFatalShutdown();
75
102
  };
103
+ /**
104
+ * Shuts down after a fatal error and exits non-zero.
105
+ */
106
+ initiateFatalShutdown() {
107
+ if (this.options.exitOnFatalError === false) {
108
+ this.initiateShutdown("fatal");
109
+ return;
110
+ }
111
+ const timeout = this.options.fatalExitTimeout ?? DEFAULT_FATAL_EXIT_TIMEOUT;
112
+ if (!this.isShuttingDown) {
113
+ // Exit even if the shutdown itself hangs — a process that is
114
+ // already in an undefined state must not linger indefinitely.
115
+ this.forcedExitTimer = setTimeout(() => {
116
+ this.logger.error("Fatal shutdown timed out; exiting.");
117
+ this.exit(1);
118
+ }, timeout);
119
+ this.forcedExitTimer.unref?.();
120
+ }
121
+ void Promise.resolve(this.initiateShutdown("fatal")).finally(() => {
122
+ if (this.forcedExitTimer) {
123
+ clearTimeout(this.forcedExitTimer);
124
+ this.forcedExitTimer = null;
125
+ }
126
+ this.exit(1);
127
+ });
128
+ }
76
129
  /**
77
130
  * Initiates graceful shutdown.
131
+ *
132
+ * A second termination signal exits immediately: an operator pressing
133
+ * Ctrl-C again on a stuck shutdown is asking for exactly that.
78
134
  */
79
- initiateShutdown() {
135
+ initiateShutdown(source) {
80
136
  if (this.isShuttingDown) {
137
+ if ((this.options.forceExitOnSecondSignal ?? true) &&
138
+ source !== "fatal") {
139
+ this.logger.warn(`Received a second ${source} while shutting down; exiting immediately.`);
140
+ this.exit(1);
141
+ return;
142
+ }
81
143
  this.logger.warn("Shutdown already in progress, ignoring signal.");
82
144
  return;
83
145
  }
84
146
  this.isShuttingDown = true;
85
147
  if (this.shutdownHandler) {
86
- this.shutdownHandler();
148
+ return Promise.resolve(this.shutdownHandler()).catch((error) => {
149
+ this.logger.error("Shutdown handler failed.", {
150
+ errorMessage: error instanceof Error ? error.message : String(error),
151
+ });
152
+ });
87
153
  }
88
154
  }
155
+ exit(code) {
156
+ const exit = this.options.exit ?? ((value) => process.exit(value));
157
+ exit(code);
158
+ }
89
159
  }
90
160
  //# sourceMappingURL=signalHandler.core.js.map
@@ -1,12 +1,13 @@
1
1
  import type { Logger } from "@zudojs/logger";
2
2
  import type { EventBus } from "@zudojs/events";
3
3
  import { LifecycleManager } from "../lifecycle/index.js";
4
+ import type { LifecycleFailure } from "../lifecycle/lifecycle.type.js";
4
5
  /**
5
6
  * Executes the startup sequence.
6
7
  */
7
- export declare function executeStartup(lifecycle: LifecycleManager, runtimeId: string, eventBus: EventBus | undefined, logger: Logger, emitEvents: boolean): Promise<void>;
8
+ export declare function executeStartup(lifecycle: LifecycleManager, runtimeId: string, eventBus: EventBus | undefined, logger: Logger, emitEvents: boolean, startupTimeout?: number): Promise<void>;
8
9
  /**
9
10
  * Rolls back a failed startup.
10
11
  */
11
- export declare function rollbackStartup(lifecycle: LifecycleManager, logger: Logger): Promise<void>;
12
+ export declare function rollbackStartup(lifecycle: LifecycleManager, logger: Logger): Promise<readonly LifecycleFailure[]>;
12
13
  //# sourceMappingURL=startup.core.d.ts.map
@@ -1,35 +1,56 @@
1
1
  import { createEvent } from "@zudojs/events";
2
+ import { createFailureEventPayload, publishRuntimeEvent, } from "../runtimeEvents/index.js";
2
3
  import { LifecycleManager } from "../lifecycle/index.js";
3
- import { RuntimeStartError } from "../runtimeError/index.js";
4
+ import { RuntimeStartError, RuntimeTimeoutError, } from "../runtimeError/index.js";
5
+ /** Largest delay a timer can represent. */
6
+ const MAX_TIMER_DELAY = 2_147_483_647;
4
7
  /**
5
- * Executes the startup sequence.
8
+ * Runs the startup sequence under a bound.
9
+ *
10
+ * `startupTimeout` was validated as positive and then never enforced, so
11
+ * a module whose `onInitialize` never settles hung the boot forever with
12
+ * no diagnostic. The timer is cleared whichever side wins.
6
13
  */
7
- export async function executeStartup(lifecycle, runtimeId, eventBus, logger, emitEvents) {
8
- // Initialize modules
9
- if (emitEvents && eventBus) {
10
- eventBus.publish(createEvent({
11
- type: "runtime.module.initializing",
12
- payload: {
13
- runtimeId,
14
- timestamp: new Date(),
15
- state: "initializing",
16
- },
17
- }));
14
+ async function withStartupTimeout(operation, timeoutMs) {
15
+ if (timeoutMs <= 0) {
16
+ return operation;
17
+ }
18
+ // The startup promise keeps running if the timeout wins; attach a
19
+ // handler now so its eventual rejection is never unhandled.
20
+ operation.catch(() => { });
21
+ let timer;
22
+ const timeout = new Promise((_, reject) => {
23
+ timer = setTimeout(() => reject(new RuntimeTimeoutError("startup", timeoutMs)), Math.min(timeoutMs, MAX_TIMER_DELAY));
24
+ timer.unref?.();
25
+ });
26
+ try {
27
+ return await Promise.race([operation, timeout]);
18
28
  }
29
+ finally {
30
+ if (timer !== undefined) {
31
+ clearTimeout(timer);
32
+ }
33
+ }
34
+ }
35
+ /**
36
+ * Executes the startup sequence.
37
+ */
38
+ export async function executeStartup(lifecycle, runtimeId, eventBus, logger, emitEvents, startupTimeout = 0) {
39
+ return withStartupTimeout(runStartup(lifecycle, runtimeId, eventBus, logger, emitEvents), startupTimeout);
40
+ }
41
+ /**
42
+ * Runs the startup sequence.
43
+ */
44
+ async function runStartup(lifecycle, runtimeId, eventBus, logger, emitEvents) {
45
+ // Per-module `runtime.module.*` events are emitted by the lifecycle
46
+ // manager, which is the only layer that knows which module is running.
19
47
  const initResult = await lifecycle.initialize();
20
48
  if (initResult.failed.length > 0) {
21
49
  const failure = initResult.failed[0];
22
50
  if (emitEvents && eventBus) {
23
- eventBus.publish(createEvent({
24
- type: "runtime.module.failed",
25
- payload: {
26
- runtimeId,
27
- state: "initialization_failed",
28
- timestamp: new Date(),
29
- error: failure.error,
30
- phase: "initialize",
31
- failedModuleId: failure.moduleId,
32
- },
51
+ publishRuntimeEvent(eventBus, logger, createEvent({
52
+ type: "runtime.failed",
53
+ payload: createFailureEventPayload(runtimeId, "initialization_failed", failure.error, "initialize", failure.moduleId),
33
54
  }));
34
55
  }
35
56
  throw new RuntimeStartError(`Module "${failure.moduleId}" failed during initialization.`, {
@@ -42,31 +63,13 @@ export async function executeStartup(lifecycle, runtimeId, eventBus, logger, emi
42
63
  modules: initResult.succeeded,
43
64
  durationMs: initResult.durationMs,
44
65
  });
45
- // Start modules
46
- if (emitEvents && eventBus) {
47
- eventBus.publish(createEvent({
48
- type: "runtime.module.starting",
49
- payload: {
50
- runtimeId,
51
- timestamp: new Date(),
52
- state: "starting",
53
- },
54
- }));
55
- }
56
66
  const startResult = await lifecycle.start();
57
67
  if (startResult.failed.length > 0) {
58
68
  const failure = startResult.failed[0];
59
69
  if (emitEvents && eventBus) {
60
- eventBus.publish(createEvent({
61
- type: "runtime.module.failed",
62
- payload: {
63
- runtimeId,
64
- state: "startup_failed",
65
- timestamp: new Date(),
66
- error: failure.error,
67
- phase: "start",
68
- failedModuleId: failure.moduleId,
69
- },
70
+ publishRuntimeEvent(eventBus, logger, createEvent({
71
+ type: "runtime.failed",
72
+ payload: createFailureEventPayload(runtimeId, "startup_failed", failure.error, "start", failure.moduleId),
70
73
  }));
71
74
  }
72
75
  throw new RuntimeStartError(`Module "${failure.moduleId}" failed during startup.`, {
@@ -85,7 +88,10 @@ export async function executeStartup(lifecycle, runtimeId, eventBus, logger, emi
85
88
  */
86
89
  export async function rollbackStartup(lifecycle, logger) {
87
90
  logger.info("Rolling back module startup.");
88
- await lifecycle.rollback();
89
- logger.info("Module rollback complete.");
91
+ const failures = await lifecycle.rollback();
92
+ logger.info("Module rollback complete.", {
93
+ failedModules: failures.map((failure) => failure.moduleId),
94
+ });
95
+ return failures;
90
96
  }
91
97
  //# sourceMappingURL=startup.core.js.map
@@ -1,5 +1,10 @@
1
1
  /**
2
- * @zudojs/runtime — Test Runtime Barrel
2
+ * @zudojs/runtime/testing
3
+ *
4
+ * Testing helpers. Exposed through the "@zudojs/runtime/testing" subpath
5
+ * rather than the package root, so a test-only module never sits on the
6
+ * main entry point.
3
7
  */
8
+ export type { MockModule } from "./testRuntime.core.js";
4
9
  export { createTestRuntime, createMockModule, withTestRuntime, } from "./testRuntime.core.js";
5
10
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,9 @@
1
1
  /**
2
- * @zudojs/runtime — Test Runtime Barrel
2
+ * @zudojs/runtime/testing
3
+ *
4
+ * Testing helpers. Exposed through the "@zudojs/runtime/testing" subpath
5
+ * rather than the package root, so a test-only module never sits on the
6
+ * main entry point.
3
7
  */
4
8
  export { createTestRuntime, createMockModule, withTestRuntime, } from "./testRuntime.core.js";
5
9
  //# sourceMappingURL=index.js.map
@@ -15,14 +15,33 @@ import type { RuntimeOptions } from "../runtimeOptions/runtimeOptions.type.js";
15
15
  * @returns A runtime instance ready for testing.
16
16
  */
17
17
  export declare function createTestRuntime(modules?: Module[], options?: Partial<RuntimeOptions>): DefaultRuntime;
18
+ /**
19
+ * A module whose lifecycle hooks record their calls.
20
+ */
21
+ export interface MockModule extends Module {
22
+ /** How many times each hook ran, in call order. */
23
+ readonly calls: {
24
+ readonly onInitialize: number;
25
+ readonly onReady: number;
26
+ readonly onShutdown: number;
27
+ readonly onDestroy: number;
28
+ };
29
+ /** Hook names in the order they were invoked. */
30
+ readonly callOrder: readonly string[];
31
+ }
18
32
  /**
19
33
  * Creates a mock module for testing.
20
34
  *
35
+ * The hooks are plain counting functions rather than `vi.fn()`: this
36
+ * module ships in the package, so it must not import a test runner —
37
+ * `vitest` is a dev dependency consumers do not install, and `require`
38
+ * is not available in an ESM package at all.
39
+ *
21
40
  * @param id - Module identifier.
22
41
  * @param dependencies - Module dependencies.
23
- * @returns A mock module with vi.fn() hooks.
42
+ * @returns A mock module that records its lifecycle calls.
24
43
  */
25
- export declare function createMockModule(id: string, dependencies?: string[]): Module;
44
+ export declare function createMockModule(id: string, dependencies?: string[]): MockModule;
26
45
  /**
27
46
  * Starts a test runtime, runs a callback, and stops it.
28
47
  *
@@ -45,20 +45,37 @@ export function createTestRuntime(modules = [], options = {}) {
45
45
  /**
46
46
  * Creates a mock module for testing.
47
47
  *
48
+ * The hooks are plain counting functions rather than `vi.fn()`: this
49
+ * module ships in the package, so it must not import a test runner —
50
+ * `vitest` is a dev dependency consumers do not install, and `require`
51
+ * is not available in an ESM package at all.
52
+ *
48
53
  * @param id - Module identifier.
49
54
  * @param dependencies - Module dependencies.
50
- * @returns A mock module with vi.fn() hooks.
55
+ * @returns A mock module that records its lifecycle calls.
51
56
  */
52
57
  export function createMockModule(id, dependencies = []) {
53
- const { vi } = require("vitest");
58
+ const calls = {
59
+ onInitialize: 0,
60
+ onReady: 0,
61
+ onShutdown: 0,
62
+ onDestroy: 0,
63
+ };
64
+ const callOrder = [];
65
+ const record = (hook) => async () => {
66
+ calls[hook] += 1;
67
+ callOrder.push(hook);
68
+ };
54
69
  return {
55
70
  id,
56
71
  name: `Module ${id}`,
57
72
  dependencies,
58
- onInitialize: vi.fn().mockResolvedValue(undefined),
59
- onReady: vi.fn().mockResolvedValue(undefined),
60
- onShutdown: vi.fn().mockResolvedValue(undefined),
61
- onDestroy: vi.fn().mockResolvedValue(undefined),
73
+ calls,
74
+ callOrder,
75
+ onInitialize: record("onInitialize"),
76
+ onReady: record("onReady"),
77
+ onShutdown: record("onShutdown"),
78
+ onDestroy: record("onDestroy"),
62
79
  };
63
80
  }
64
81
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/runtime",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "description": "Application lifecycle orchestrator with dependency ordering, rollback, signals, and readiness checks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -10,34 +10,30 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.js"
12
12
  },
13
- "./types": {
14
- "types": "./dist/types/index.d.ts",
15
- "import": "./dist/types/index.js"
13
+ "./testing": {
14
+ "types": "./dist/testRuntime/index.d.ts",
15
+ "import": "./dist/testRuntime/index.js"
16
16
  }
17
17
  },
18
18
  "files": [
19
- "dist"
19
+ "dist",
20
+ "!dist/**/*.map",
21
+ "!dist/**/*.tsbuildinfo",
22
+ "!dist/.tsbuildinfo"
20
23
  ],
21
- "scripts": {
22
- "build": "tsc -p tsconfig.json",
23
- "typecheck": "tsc -p tsconfig.json --noEmit",
24
- "test": "vitest run",
25
- "test:watch": "vitest",
26
- "clean": "tsc -p tsconfig.json --clean"
27
- },
28
24
  "devDependencies": {
29
25
  "@types/node": "^26.4.1",
30
- "typescript": "^7.0.2",
26
+ "typescript": "7.0.2",
31
27
  "vitest": "^4.1.11"
32
28
  },
33
29
  "dependencies": {
34
- "@zudojs/errors": "workspace:*",
35
- "@zudojs/constants": "workspace:*",
36
- "@zudojs/container": "workspace:*",
37
- "@zudojs/config": "workspace:*",
38
- "@zudojs/logger": "workspace:*",
39
- "@zudojs/events": "workspace:*",
40
- "@zudojs/core": "workspace:*"
30
+ "@zudojs/errors": "1.0.0",
31
+ "@zudojs/constants": "1.0.0",
32
+ "@zudojs/container": "1.0.0",
33
+ "@zudojs/config": "1.0.0",
34
+ "@zudojs/logger": "1.0.0",
35
+ "@zudojs/events": "1.0.0",
36
+ "@zudojs/core": "1.0.0"
41
37
  },
42
38
  "license": "MIT",
43
39
  "publishConfig": {
@@ -54,8 +50,19 @@
54
50
  "services"
55
51
  ],
56
52
  "homepage": "https://github.com/oyinlola-tech/zudo#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/oyinlola-tech/zudo/issues"
55
+ },
57
56
  "repository": {
58
57
  "type": "git",
59
- "url": "https://github.com/oyinlola-tech/zudo"
58
+ "url": "https://github.com/oyinlola-tech/zudo",
59
+ "directory": "packages/runtime"
60
+ },
61
+ "scripts": {
62
+ "build": "tsc -p tsconfig.json",
63
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest",
66
+ "clean": "rm -rf dist"
60
67
  }
61
- }
68
+ }