@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,73 +1,108 @@
1
+ import { createConfigurationManager } from "@zudojs/core";
2
+ import { createModuleEventPayload } from "../runtimeEvents/runtimeEvents.core.js";
1
3
  import { resolveDependencies } from "../dependencyGraph/index.js";
2
- import { RuntimeStartError, RuntimeStopError, RuntimeDependencyError, } from "../runtimeError/index.js";
4
+ import { RuntimeDependencyError, RuntimeStateError, } from "../runtimeError/index.js";
3
5
  /**
4
6
  * Manages the lifecycle of runtime modules.
5
7
  */
6
8
  export class LifecycleManager {
7
9
  modules;
8
10
  logger;
9
- container;
10
- runtimeId;
11
- environment;
12
11
  options;
12
+ onModuleEvent;
13
+ runtimeId;
13
14
  initializedModules = [];
14
15
  startedModules = [];
15
- constructor(modules, logger, container, runtimeId, environment, options = {}) {
16
+ configuration;
17
+ application;
18
+ contexts = new Map();
19
+ constructor(modules, logger, options = {}, services = {}) {
16
20
  this.modules = modules;
17
21
  this.logger = logger;
18
- this.container = container;
19
- this.runtimeId = runtimeId;
20
- this.environment = environment;
22
+ this.configuration = services.configuration ?? createConfigurationManager();
23
+ this.application = services.application;
24
+ this.onModuleEvent = options.onModuleEvent;
25
+ this.runtimeId = options.runtimeId ?? "";
21
26
  this.options = {
22
27
  shutdownTimeout: options.shutdownTimeout ?? 30_000,
23
28
  continueOnFailure: options.continueOnFailure ?? false,
24
- parallelInitialization: options.parallelInitialization ?? true,
29
+ // Defaults to sequential: initializing a whole depth group at once
30
+ // is a real behaviour change for modules that assume ordering
31
+ // beyond what they declared, so it is opt-in.
32
+ parallelInitialization: options.parallelInitialization ?? false,
25
33
  };
26
34
  }
35
+ /**
36
+ * Publishes a per-module lifecycle event.
37
+ *
38
+ * A throwing listener must not fail the phase that produced the event,
39
+ * so delivery failures are contained here.
40
+ */
41
+ emitModuleEvent(type, moduleId, state, options = {}) {
42
+ if (!this.onModuleEvent) {
43
+ return;
44
+ }
45
+ const module = this.modules.get(moduleId);
46
+ try {
47
+ this.onModuleEvent(type, createModuleEventPayload(this.runtimeId, state, moduleId, module?.name ?? moduleId, options));
48
+ }
49
+ catch (error) {
50
+ this.logger.warn("A runtime module event listener threw.", {
51
+ eventType: type,
52
+ moduleId,
53
+ error: error instanceof Error ? error.message : String(error),
54
+ });
55
+ }
56
+ }
57
+ /**
58
+ * Loads the configuration manager once, before modules are initialized,
59
+ * so that `context.getConfiguration()` is usable from the first hook.
60
+ */
61
+ async ensureConfigurationReady() {
62
+ if (this.configuration.isReady()) {
63
+ return;
64
+ }
65
+ try {
66
+ await this.configuration.initialize();
67
+ }
68
+ catch (error) {
69
+ this.logger.warn("Configuration failed to load.", {
70
+ error: error instanceof Error ? error.message : String(error),
71
+ });
72
+ }
73
+ }
27
74
  /**
28
75
  * Initializes all modules in dependency order.
29
76
  */
30
77
  async initialize() {
31
78
  const startTime = Date.now();
79
+ // Reset the per-run bookkeeping. Appending across runs would make a
80
+ // stop-then-start cycle initialize and stop every module twice.
81
+ this.initializedModules = [];
82
+ this.startedModules = [];
83
+ await this.ensureConfigurationReady();
32
84
  const succeeded = [];
33
85
  const failed = [];
34
86
  const depGraph = this.buildModuleDependencyGraph();
35
- for (const moduleId of depGraph.order) {
36
- const module = this.modules.get(moduleId);
37
- if (!module) {
38
- failed.push({
39
- moduleId,
40
- phase: "initialize",
41
- error: new RuntimeDependencyError(moduleId, "unknown"),
42
- durationMs: 0,
43
- });
44
- continue;
45
- }
46
- const moduleStartTime = Date.now();
47
- try {
48
- if (module.onInitialize) {
49
- const context = this.createModuleContext(module);
50
- await module.onInitialize(context);
87
+ // Modules within a depth group have no dependency on one another, so
88
+ // they may be initialized together when the caller opts in.
89
+ const groups = this.options.parallelInitialization
90
+ ? depGraph.parallelGroups
91
+ : depGraph.order.map((moduleId) => [moduleId]);
92
+ for (const group of groups) {
93
+ const results = await Promise.all(group.map((moduleId) => this.initializeModule(moduleId)));
94
+ for (const result of results) {
95
+ if (result.failure) {
96
+ failed.push(result.failure);
51
97
  }
52
- succeeded.push(moduleId);
53
- this.initializedModules.push(moduleId);
54
- this.logger.debug(`Module "${moduleId}" initialized.`, {
55
- durationMs: Date.now() - moduleStartTime,
56
- });
57
- }
58
- catch (error) {
59
- const failure = {
60
- moduleId,
61
- phase: "initialize",
62
- error: error instanceof Error ? error : new Error(String(error)),
63
- durationMs: Date.now() - moduleStartTime,
64
- };
65
- failed.push(failure);
66
- this.logger.error(`Module "${moduleId}" failed during initialization.`, { error: failure.error });
67
- if (!this.options.continueOnFailure) {
68
- break;
98
+ else {
99
+ succeeded.push(result.moduleId);
100
+ this.initializedModules.push(result.moduleId);
69
101
  }
70
102
  }
103
+ if (failed.length > 0 && !this.options.continueOnFailure) {
104
+ break;
105
+ }
71
106
  }
72
107
  return Object.freeze({
73
108
  phase: "initialize",
@@ -76,11 +111,58 @@ export class LifecycleManager {
76
111
  durationMs: Date.now() - startTime,
77
112
  });
78
113
  }
114
+ /**
115
+ * Initializes a single module, converting a throw into a failure.
116
+ */
117
+ async initializeModule(moduleId) {
118
+ const module = this.modules.get(moduleId);
119
+ if (!module) {
120
+ return {
121
+ moduleId,
122
+ failure: {
123
+ moduleId,
124
+ phase: "initialize",
125
+ error: new RuntimeDependencyError(moduleId, "unknown"),
126
+ durationMs: 0,
127
+ },
128
+ };
129
+ }
130
+ const startedAt = Date.now();
131
+ this.emitModuleEvent("runtime.module.initializing", moduleId, "initializing");
132
+ try {
133
+ if (module.onInitialize) {
134
+ await module.onInitialize(this.createModuleContext(module));
135
+ }
136
+ const durationMs = Date.now() - startedAt;
137
+ this.logger.debug(`Module "${moduleId}" initialized.`, { durationMs });
138
+ this.emitModuleEvent("runtime.module.initialized", moduleId, "initialized", {
139
+ durationMs,
140
+ });
141
+ return { moduleId };
142
+ }
143
+ catch (error) {
144
+ const failure = {
145
+ moduleId,
146
+ phase: "initialize",
147
+ error: error instanceof Error ? error : new Error(String(error)),
148
+ durationMs: Date.now() - startedAt,
149
+ };
150
+ this.logger.error(`Module "${moduleId}" failed during initialization.`, {
151
+ error: failure.error,
152
+ });
153
+ this.emitModuleEvent("runtime.module.failed", moduleId, "failed", {
154
+ durationMs: failure.durationMs,
155
+ error: failure.error,
156
+ });
157
+ return { moduleId, failure };
158
+ }
159
+ }
79
160
  /**
80
161
  * Starts all modules in dependency order.
81
162
  */
82
163
  async start() {
83
164
  const startTime = Date.now();
165
+ this.startedModules = [];
84
166
  const succeeded = [];
85
167
  const failed = [];
86
168
  for (const moduleId of this.initializedModules) {
@@ -88,6 +170,7 @@ export class LifecycleManager {
88
170
  if (!module)
89
171
  continue;
90
172
  const moduleStartTime = Date.now();
173
+ this.emitModuleEvent("runtime.module.starting", moduleId, "starting");
91
174
  try {
92
175
  if (module.onReady) {
93
176
  const context = this.createModuleContext(module);
@@ -95,8 +178,10 @@ export class LifecycleManager {
95
178
  }
96
179
  succeeded.push(moduleId);
97
180
  this.startedModules.push(moduleId);
98
- this.logger.debug(`Module "${moduleId}" started.`, {
99
- durationMs: Date.now() - moduleStartTime,
181
+ const durationMs = Date.now() - moduleStartTime;
182
+ this.logger.debug(`Module "${moduleId}" started.`, { durationMs });
183
+ this.emitModuleEvent("runtime.module.started", moduleId, "started", {
184
+ durationMs,
100
185
  });
101
186
  }
102
187
  catch (error) {
@@ -110,6 +195,10 @@ export class LifecycleManager {
110
195
  this.logger.error(`Module "${moduleId}" failed during startup.`, {
111
196
  error: failure.error,
112
197
  });
198
+ this.emitModuleEvent("runtime.module.failed", moduleId, "failed", {
199
+ durationMs: failure.durationMs,
200
+ error: failure.error,
201
+ });
113
202
  if (!this.options.continueOnFailure) {
114
203
  break;
115
204
  }
@@ -135,14 +224,17 @@ export class LifecycleManager {
135
224
  if (!module)
136
225
  continue;
137
226
  const moduleStartTime = Date.now();
227
+ this.emitModuleEvent("runtime.module.stopping", moduleId, "stopping");
138
228
  try {
139
229
  if (module.onShutdown) {
140
230
  const context = this.createModuleContext(module);
141
231
  await module.onShutdown(context);
142
232
  }
143
233
  succeeded.push(moduleId);
144
- this.logger.debug(`Module "${moduleId}" stopped.`, {
145
- durationMs: Date.now() - moduleStartTime,
234
+ const durationMs = Date.now() - moduleStartTime;
235
+ this.logger.debug(`Module "${moduleId}" stopped.`, { durationMs });
236
+ this.emitModuleEvent("runtime.module.stopped", moduleId, "stopped", {
237
+ durationMs,
146
238
  });
147
239
  }
148
240
  catch (error) {
@@ -156,6 +248,10 @@ export class LifecycleManager {
156
248
  this.logger.error(`Module "${moduleId}" failed during shutdown.`, {
157
249
  error: failure.error,
158
250
  });
251
+ this.emitModuleEvent("runtime.module.failed", moduleId, "failed", {
252
+ durationMs: failure.durationMs,
253
+ error: failure.error,
254
+ });
159
255
  }
160
256
  }
161
257
  return Object.freeze({
@@ -203,6 +299,11 @@ export class LifecycleManager {
203
299
  });
204
300
  }
205
301
  }
302
+ // The runtime no longer owns these modules; a subsequent start
303
+ // rebuilds the lists from scratch.
304
+ this.startedModules = [];
305
+ this.initializedModules = [];
306
+ this.contexts.clear();
206
307
  return Object.freeze({
207
308
  phase: "destroy",
208
309
  succeeded: Object.freeze(succeeded),
@@ -211,51 +312,127 @@ export class LifecycleManager {
211
312
  });
212
313
  }
213
314
  /**
214
- * Rolls back initialization for modules that were started.
315
+ * Rolls back a failed startup.
316
+ *
317
+ * Started modules are shut down, and every module that reached
318
+ * `onInitialize` is then destroyed — including those that never
319
+ * started because an earlier module failed. Rolling back only the
320
+ * started ones leaves everything they had already acquired behind with
321
+ * no route to release it.
215
322
  */
216
323
  async rollback() {
217
- const reversedModules = [...this.startedModules].reverse();
218
- for (const moduleId of reversedModules) {
324
+ const failures = [];
325
+ for (const moduleId of [...this.startedModules].reverse()) {
219
326
  const module = this.modules.get(moduleId);
220
327
  if (!module?.onShutdown)
221
328
  continue;
329
+ const startedAt = Date.now();
222
330
  try {
223
- const context = this.createModuleContext(module);
224
- await module.onShutdown(context);
331
+ await module.onShutdown(this.createModuleContext(module));
225
332
  this.logger.debug(`Module "${moduleId}" rolled back.`);
226
333
  }
227
334
  catch (error) {
335
+ failures.push({
336
+ moduleId,
337
+ phase: "stop",
338
+ error: error instanceof Error ? error : new Error(String(error)),
339
+ durationMs: Date.now() - startedAt,
340
+ });
228
341
  this.logger.error(`Module "${moduleId}" rollback failed.`, {
229
342
  errorMessage: error instanceof Error ? error.message : String(error),
230
343
  });
231
344
  }
232
345
  }
346
+ for (const moduleId of [...this.initializedModules].reverse()) {
347
+ const module = this.modules.get(moduleId);
348
+ if (!module?.onDestroy)
349
+ continue;
350
+ const startedAt = Date.now();
351
+ try {
352
+ await module.onDestroy(this.createModuleContext(module));
353
+ this.logger.debug(`Module "${moduleId}" destroyed during rollback.`);
354
+ }
355
+ catch (error) {
356
+ failures.push({
357
+ moduleId,
358
+ phase: "destroy",
359
+ error: error instanceof Error ? error : new Error(String(error)),
360
+ durationMs: Date.now() - startedAt,
361
+ });
362
+ this.logger.error(`Module "${moduleId}" destruction failed.`, {
363
+ errorMessage: error instanceof Error ? error.message : String(error),
364
+ });
365
+ }
366
+ }
367
+ // A rolled-back runtime owns nothing; clear the bookkeeping so a
368
+ // later shutdown does not stop modules a second time.
369
+ this.startedModules = [];
370
+ this.initializedModules = [];
371
+ this.contexts.clear();
372
+ return failures;
233
373
  }
234
374
  /**
235
375
  * Creates a module context for lifecycle hooks.
236
376
  */
237
377
  createModuleContext(module) {
238
- return {
378
+ const existing = this.contexts.get(module.id);
379
+ if (existing !== undefined) {
380
+ return existing;
381
+ }
382
+ const configuration = this.configuration;
383
+ const application = this.application;
384
+ const logger = this.logger;
385
+ const contexts = this.contexts;
386
+ const modules = this.modules;
387
+ const context = {
239
388
  id: module.id,
240
389
  name: module.name,
241
390
  version: module.version,
242
391
  options: module.options ?? {},
243
392
  scope: module.scope,
244
- application: {},
245
- configuration: {},
246
- logger: this.logger,
247
- getConfiguration: () => ({}),
248
- getConfig: () => undefined,
249
- requireConfig: (path) => {
250
- throw new Error(`Config "${path}" not found.`);
393
+ // `application` is a getter so that a runtime started without an
394
+ // application context fails loudly at the point of use, rather than
395
+ // handing modules an empty object that lies about its type.
396
+ get application() {
397
+ if (application === undefined) {
398
+ throw new RuntimeStateError(`Module "${module.id}" accessed "context.application", but no ` +
399
+ `ApplicationContext was supplied to the runtime.`);
400
+ }
401
+ return application;
251
402
  },
252
- getModuleContext: () => undefined,
253
- hasModule: () => false,
403
+ configuration,
404
+ logger,
405
+ getConfiguration: () => configuration.getConfiguration(),
406
+ getConfig: (path) => configuration.get(path),
407
+ requireConfig: (path) => configuration.require(path),
408
+ // Only a module's declared dependencies are reachable. Handing
409
+ // every module every other module's configuration and application
410
+ // context makes the declared dependency graph advisory.
411
+ getModuleContext: (moduleId) => {
412
+ const allowed = moduleId === module.id ||
413
+ (module.dependencies ?? []).includes(moduleId);
414
+ return allowed ? contexts.get(moduleId) : undefined;
415
+ },
416
+ hasModule: (moduleId) => modules.has(moduleId),
254
417
  };
418
+ this.contexts.set(module.id, context);
419
+ return context;
255
420
  }
256
421
  /**
257
422
  * Builds a dependency graph from registered modules.
258
423
  */
424
+ /**
425
+ * Modules that reached `onInitialize`, in initialization order.
426
+ */
427
+ getInitializedModules() {
428
+ return Object.freeze([...this.initializedModules]);
429
+ }
430
+ /**
431
+ * Modules that reached `onReady`, in start order.
432
+ */
433
+ getStartedModules() {
434
+ return Object.freeze([...this.startedModules]);
435
+ }
259
436
  buildModuleDependencyGraph() {
260
437
  const moduleDeps = new Map();
261
438
  for (const [id, module] of this.modules) {
@@ -1,4 +1,5 @@
1
- import type { Module } from "@zudojs/core";
1
+ import type { ConfigurationManager, Module, ModuleContext } from "@zudojs/core";
2
+ import type { RuntimeModuleEventPayload, RuntimeModuleEventType } from "../runtimeEvents/runtimeEvents.type.js";
2
3
  /**
3
4
  * Lifecycle hook phases for modules.
4
5
  */
@@ -40,12 +41,58 @@ export interface ManagedModule {
40
41
  readonly depth: number;
41
42
  readonly dependencies: readonly string[];
42
43
  }
44
+ /**
45
+ * Framework services made available to modules through their
46
+ * {@link ModuleContext}.
47
+ *
48
+ * Both are optional so a runtime can be started without a configuration
49
+ * layer, but a module that reaches for a service the host did not supply
50
+ * gets a clear error rather than a silently empty object.
51
+ */
52
+ export interface ModuleContextServices {
53
+ /**
54
+ * Configuration manager backing `getConfiguration`, `getConfig` and
55
+ * `requireConfig`. Defaults to an empty, uninitialized manager.
56
+ */
57
+ readonly configuration?: ConfigurationManager;
58
+ /**
59
+ * Application context exposed as `context.application`.
60
+ *
61
+ * `ApplicationContext` is not part of `@zudojs/core`'s published entry
62
+ * points, so the runtime cannot construct one itself; a host that has an
63
+ * application context passes it in here.
64
+ */
65
+ readonly application?: ModuleContext["application"];
66
+ }
67
+ /**
68
+ * Receives a per-module lifecycle event.
69
+ */
70
+ export type ModuleEventListener = (type: RuntimeModuleEventType, payload: RuntimeModuleEventPayload) => void;
43
71
  /**
44
72
  * Options for lifecycle management.
45
73
  */
46
74
  export interface LifecycleManagerOptions {
47
75
  readonly shutdownTimeout?: number;
48
76
  readonly continueOnFailure?: boolean;
77
+ /**
78
+ * Receives one event per module per lifecycle phase.
79
+ *
80
+ * The lifecycle is the only layer that knows which module is running,
81
+ * so it is the only layer that can populate a
82
+ * {@link RuntimeModuleEventPayload}. Without this the runtime published
83
+ * two phase-wide events with no module on them, and every
84
+ * `runtime.module.*` entry in `RuntimeEventMap` named a payload nothing
85
+ * produced.
86
+ */
87
+ readonly onModuleEvent?: ModuleEventListener;
88
+ /** Runtime identifier stamped onto emitted module events. */
89
+ readonly runtimeId?: string;
90
+ /**
91
+ * Whether modules at the same dependency depth are initialized
92
+ * concurrently. Defaults to `false`. Modules within a depth group do
93
+ * not depend on one another, but enabling this surfaces any ordering
94
+ * a module assumed without declaring.
95
+ */
49
96
  readonly parallelInitialization?: boolean;
50
97
  }
51
98
  //# sourceMappingURL=lifecycle.type.d.ts.map
@@ -2,5 +2,5 @@
2
2
  * Runtime readiness tracking.
3
3
  */
4
4
  export { ReadinessTracker } from "./readiness.core.js";
5
- export type { ReadinessState, ReadinessCheck, ReadinessTrackerState, ReadinessOptions, } from "./readiness.type.js";
5
+ export type { ReadinessState, ReadinessCheck, ReadinessCheckFn, ReadinessTrackerState, ReadinessOptions, } from "./readiness.type.js";
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,22 +1,54 @@
1
- import type { ReadinessState, ReadinessTrackerState, ReadinessOptions } from "./readiness.type.js";
1
+ import type { ReadinessState, ReadinessCheckFn, ReadinessTrackerState, ReadinessOptions } from "./readiness.type.js";
2
2
  /**
3
3
  * Tracks runtime readiness state.
4
4
  */
5
5
  export declare class ReadinessTracker {
6
6
  private state;
7
7
  private readonly checks;
8
+ private readonly checkFns;
8
9
  private readonly autoMarkReady;
10
+ private readonly checkTimeout;
9
11
  private ready;
10
12
  private reason?;
13
+ private running;
11
14
  constructor(options?: ReadinessOptions);
12
15
  /**
13
16
  * Registers a readiness check.
17
+ *
18
+ * The check function is retained so it can be re-evaluated later by
19
+ * `updateCheck(name)` or `runChecks()`. Registration does not run the
20
+ * check: it starts out not-ready until it is first evaluated.
14
21
  */
15
- registerCheck(name: string, check: () => boolean | Promise<boolean>): void;
22
+ registerCheck(name: string, check: ReadinessCheckFn): void;
16
23
  /**
17
- * Updates a readiness check result.
24
+ * Removes a readiness check.
25
+ *
26
+ * Returns whether a check with that name was registered.
18
27
  */
19
- updateCheck(name: string, check: () => boolean | Promise<boolean>): Promise<void>;
28
+ removeCheck(name: string): boolean;
29
+ /**
30
+ * Evaluates a readiness check and records its result.
31
+ *
32
+ * Re-runs the function registered under `name` unless a replacement is
33
+ * supplied, in which case the replacement is stored and used from then on.
34
+ */
35
+ updateCheck(name: string, check?: ReadinessCheckFn): Promise<void>;
36
+ /**
37
+ * Re-evaluates every registered check.
38
+ */
39
+ runChecks(): Promise<void>;
40
+ /**
41
+ * Whether any readiness check is registered.
42
+ */
43
+ hasChecks(): boolean;
44
+ /**
45
+ * Runs a single check and records its result and duration.
46
+ */
47
+ private evaluateCheck;
48
+ /**
49
+ * Runs a check under a timeout, clearing the timer either way.
50
+ */
51
+ private withCheckTimeout;
20
52
  /**
21
53
  * Marks the runtime as ready.
22
54
  */