lifecycleion 0.0.13 → 0.0.15
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/README.md +2 -2
- package/dist/lib/lifecycle-manager/index.cjs +666 -329
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
- package/dist/lib/lifecycle-manager/index.d.cts +116 -27
- package/dist/lib/lifecycle-manager/index.d.ts +116 -27
- package/dist/lib/lifecycle-manager/index.js +666 -329
- package/dist/lib/lifecycle-manager/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -158,7 +158,7 @@ interface RestartComponentOptions {
|
|
|
158
158
|
/**
|
|
159
159
|
* Stable, machine-readable failure codes for individual component operations
|
|
160
160
|
*/
|
|
161
|
-
type ComponentOperationFailureCode = 'component_not_found' | 'component_already_running' | 'component_already_starting' | 'component_already_stopping' | 'component_not_running' | 'component_stalled' | 'missing_dependency' | 'dependency_not_running' | 'has_running_dependents' | 'startup_in_progress' | 'shutdown_in_progress' | 'component_startup_timeout' | 'component_shutdown_timeout' | 'restart_stop_failed' | 'restart_start_failed' | 'unknown_error';
|
|
161
|
+
type ComponentOperationFailureCode = 'component_not_found' | 'component_already_running' | 'component_already_starting' | 'component_already_stopping' | 'component_not_running' | 'component_stalled' | 'missing_dependency' | 'dependency_not_running' | 'has_running_dependents' | 'startup_in_progress' | 'shutdown_in_progress' | 'component_unexpected_stop' | 'component_startup_timeout' | 'component_shutdown_timeout' | 'restart_stop_failed' | 'restart_start_failed' | 'unknown_error';
|
|
162
162
|
/**
|
|
163
163
|
* Failure codes for unregister operations
|
|
164
164
|
*/
|
|
@@ -202,7 +202,7 @@ interface StartupResult {
|
|
|
202
202
|
/** Reason for failure (when success is false) */
|
|
203
203
|
reason?: string;
|
|
204
204
|
/** Error code (when success is false) */
|
|
205
|
-
code?: 'already_in_progress' | 'shutdown_in_progress' | 'dependency_cycle' | 'no_components_registered' | 'stalled_components_exist' | 'partial_state' | 'required_component_failed' | 'startup_timeout' | 'unknown_error';
|
|
205
|
+
code?: 'already_in_progress' | 'component_unexpected_stop' | 'shutdown_in_progress' | 'dependency_cycle' | 'no_components_registered' | 'stalled_components_exist' | 'partial_state' | 'required_component_failed' | 'startup_timeout' | 'unknown_error';
|
|
206
206
|
/** Error object (when success is false due to dependency cycle or unknown error) */
|
|
207
207
|
error?: Error;
|
|
208
208
|
/** Total startup duration in milliseconds */
|
|
@@ -438,13 +438,20 @@ interface ValueResult<T = unknown> {
|
|
|
438
438
|
code: 'found' | 'not_found' | 'stopped' | 'stalled' | 'no_handler' | 'error';
|
|
439
439
|
}
|
|
440
440
|
type EventEmitterSurface = Pick<EventEmitterProtected, 'on' | 'once' | 'hasListener' | 'hasListeners' | 'listenerCount'>;
|
|
441
|
+
/**
|
|
442
|
+
* Public value lookup surface shared by LifecycleManager and component-scoped
|
|
443
|
+
* lifecycle references.
|
|
444
|
+
*/
|
|
445
|
+
interface LifecycleValueProvider {
|
|
446
|
+
getValue<T = unknown>(componentName: string, key: string, options?: GetValueOptions): ValueResult<T>;
|
|
447
|
+
}
|
|
441
448
|
/**
|
|
442
449
|
* Common lifecycle interface shared by LifecycleManager and ComponentLifecycle
|
|
443
450
|
*
|
|
444
451
|
* Keep in sync with public LifecycleManager API and ComponentLifecycle proxy.
|
|
445
452
|
* Purpose: define the shared surface both expose to avoid drift across the two.
|
|
446
453
|
*/
|
|
447
|
-
interface LifecycleCommon extends EventEmitterSurface {
|
|
454
|
+
interface LifecycleCommon extends EventEmitterSurface, LifecycleValueProvider {
|
|
448
455
|
hasComponent(name: string): boolean;
|
|
449
456
|
isComponentRunning(name: string): boolean;
|
|
450
457
|
getComponentNames(): string[];
|
|
@@ -479,7 +486,6 @@ interface LifecycleCommon extends EventEmitterSurface {
|
|
|
479
486
|
broadcastMessage(payload: unknown, options?: BroadcastOptions): Promise<BroadcastResult[]>;
|
|
480
487
|
checkComponentHealth(name: string): Promise<HealthCheckResult>;
|
|
481
488
|
checkAllHealth(): Promise<HealthReport>;
|
|
482
|
-
getValue<T = unknown>(componentName: string, key: string, options?: GetValueOptions): ValueResult<T>;
|
|
483
489
|
}
|
|
484
490
|
/**
|
|
485
491
|
* Component-scoped lifecycle interface injected into BaseComponent
|
|
@@ -546,7 +552,7 @@ interface UnregisterOptions {
|
|
|
546
552
|
* Options for starting all components
|
|
547
553
|
*/
|
|
548
554
|
interface StartupOptions {
|
|
549
|
-
/** Allow
|
|
555
|
+
/** Allow bulk startup to proceed by skipping stalled components (default: false) */
|
|
550
556
|
ignoreStalledComponents?: boolean;
|
|
551
557
|
/** Global timeout for entire startup process in milliseconds (default: constructor's startupTimeoutMS) */
|
|
552
558
|
timeoutMS?: number;
|
|
@@ -956,6 +962,12 @@ declare abstract class BaseComponent {
|
|
|
956
962
|
protected name: string;
|
|
957
963
|
/** Reference to component-scoped lifecycle (set by manager when registered) */
|
|
958
964
|
protected lifecycle: ComponentLifecycleRef;
|
|
965
|
+
/** @internal Set by LifecycleManager while the component is running. */
|
|
966
|
+
private _unexpectedStopHandler?;
|
|
967
|
+
/** @internal Incremented whenever the unexpected-stop handler is re-armed or cleared. */
|
|
968
|
+
private _unexpectedStopGeneration;
|
|
969
|
+
/** @internal Flag indicating whether this component is currently registered with a LifecycleManager */
|
|
970
|
+
private _isRegistered;
|
|
959
971
|
/**
|
|
960
972
|
* Create a new component
|
|
961
973
|
*
|
|
@@ -964,6 +976,16 @@ declare abstract class BaseComponent {
|
|
|
964
976
|
* @throws {InvalidComponentNameError} If name doesn't match kebab-case pattern
|
|
965
977
|
*/
|
|
966
978
|
constructor(rootLogger: Logger, options: ComponentOptions);
|
|
979
|
+
/** @internal Called by LifecycleManager after a successful start. */
|
|
980
|
+
_setUnexpectedStopHandler(handler: (error?: Error) => boolean): void;
|
|
981
|
+
/** @internal Called by LifecycleManager when stop begins or component is unregistered. */
|
|
982
|
+
_clearUnexpectedStopHandler(): void;
|
|
983
|
+
/** @internal Called by LifecycleManager when registering the component. */
|
|
984
|
+
_markRegistered(): void;
|
|
985
|
+
/** @internal Called by LifecycleManager when unregistering the component. */
|
|
986
|
+
_markUnregistered(): void;
|
|
987
|
+
/** @internal Check if the component is registered with any LifecycleManager. */
|
|
988
|
+
_isRegisteredWithManager(): boolean;
|
|
967
989
|
/**
|
|
968
990
|
* Start the component
|
|
969
991
|
*
|
|
@@ -1146,6 +1168,28 @@ declare abstract class BaseComponent {
|
|
|
1146
1168
|
* Check if component is optional
|
|
1147
1169
|
*/
|
|
1148
1170
|
isOptional(): boolean;
|
|
1171
|
+
/**
|
|
1172
|
+
* Run-scoped unexpected-stop callback. Rebound by LifecycleManager on each
|
|
1173
|
+
* successful start so captured references from older runs go stale.
|
|
1174
|
+
*/
|
|
1175
|
+
protected reportUnexpectedStop: (error?: Error) => boolean;
|
|
1176
|
+
/**
|
|
1177
|
+
* Get this component's own status from the manager's perspective.
|
|
1178
|
+
*
|
|
1179
|
+
* Equivalent to `this.lifecycle.getComponentStatus(this.getName())` but without
|
|
1180
|
+
* needing to pass the name. Returns `undefined` if the component is not registered.
|
|
1181
|
+
*
|
|
1182
|
+
* Check `status?.state === 'running'` to test whether the component is currently running.
|
|
1183
|
+
*/
|
|
1184
|
+
protected getSelfStatus(): ComponentStatus | undefined;
|
|
1185
|
+
/**
|
|
1186
|
+
* Capture a run-scoped unexpected-stop reporter for async listeners created during start().
|
|
1187
|
+
*
|
|
1188
|
+
* Unlike calling `this.reportUnexpectedStop()` later, the returned callback becomes a no-op
|
|
1189
|
+
* once the component is stopped, unregistered, or restarted. This prevents stale listeners
|
|
1190
|
+
* from a previous run from stopping a newer run of the same component instance.
|
|
1191
|
+
*/
|
|
1192
|
+
protected getUnexpectedStopReporter(): (error?: Error) => boolean;
|
|
1149
1193
|
}
|
|
1150
1194
|
|
|
1151
1195
|
/**
|
|
@@ -1179,7 +1223,11 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1179
1223
|
private componentTimestamps;
|
|
1180
1224
|
private componentErrors;
|
|
1181
1225
|
private componentStartAttemptTokens;
|
|
1226
|
+
private componentStopAttemptTokens;
|
|
1227
|
+
private pendingForceStopWaiters;
|
|
1228
|
+
private unexpectedStopsDuringStartup;
|
|
1182
1229
|
private isStarting;
|
|
1230
|
+
private autoAttachedSignalsDuringStartup;
|
|
1183
1231
|
private isStarted;
|
|
1184
1232
|
private isShuttingDown;
|
|
1185
1233
|
private shutdownToken;
|
|
@@ -1592,6 +1640,45 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1592
1640
|
private autoAttachSignals;
|
|
1593
1641
|
private autoDetachSignalsIfIdle;
|
|
1594
1642
|
private monitorLateStartupCompletion;
|
|
1643
|
+
private consumeUnexpectedStopsDuringStartup;
|
|
1644
|
+
/**
|
|
1645
|
+
* Issues and returns a unique stop attempt token for a component.
|
|
1646
|
+
*
|
|
1647
|
+
* Each stop attempt (graceful or force-retry) gets a unique token.
|
|
1648
|
+
* The late-resolution handler captures this token in its closure so it can
|
|
1649
|
+
* skip any stall entries that were created by a *later* stop attempt — e.g. a
|
|
1650
|
+
* force-retry that also timed out after the original graceful promise floated
|
|
1651
|
+
* in the background.
|
|
1652
|
+
*/
|
|
1653
|
+
private issueStopAttemptToken;
|
|
1654
|
+
private createPendingForceStopWaiter;
|
|
1655
|
+
private resolvePendingForceStopWaiters;
|
|
1656
|
+
/**
|
|
1657
|
+
* Called when a stop promise eventually resolves after its timeout path already fired.
|
|
1658
|
+
*
|
|
1659
|
+
* Usually this means a previously stalled component's original stop() or
|
|
1660
|
+
* onShutdownForce() promise finally resolved, so the manager can clear the
|
|
1661
|
+
* stall and transition the component to stopped without a manual retry.
|
|
1662
|
+
*
|
|
1663
|
+
* There is one extra overlap case for graceful stop(): stop() can resolve
|
|
1664
|
+
* after the graceful timeout but before onShutdownForce() itself times out.
|
|
1665
|
+
* In that window no stall entry exists yet, but the component still finished
|
|
1666
|
+
* stopping cleanly, so we finalize it here and let the later force-timeout
|
|
1667
|
+
* path observe the already-stopped state and no-op. This overlap fix is
|
|
1668
|
+
* scoped to the same stop token and will not cross a later retry attempt.
|
|
1669
|
+
*
|
|
1670
|
+
* Two guards prevent stale floating promises from incorrectly clearing state:
|
|
1671
|
+
*
|
|
1672
|
+
* 1. token guard — if a newer stop attempt (e.g. a retryStalled
|
|
1673
|
+
* force-retry) has started since this promise was launched, its token
|
|
1674
|
+
* won't match and we bail out immediately.
|
|
1675
|
+
*
|
|
1676
|
+
* 2. state/stall guard — if the component was unregistered, restarted, or
|
|
1677
|
+
* already cleared by another path, there will be neither a matching stall
|
|
1678
|
+
* entry nor the force-phase overlap state, so we bail out.
|
|
1679
|
+
*/
|
|
1680
|
+
private handleLateStopResolution;
|
|
1681
|
+
private handleComponentUnexpectedStop;
|
|
1595
1682
|
/**
|
|
1596
1683
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
1597
1684
|
*/
|
|
@@ -1713,36 +1800,27 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1713
1800
|
*/
|
|
1714
1801
|
private armRepeatedShutdownAfterFailure;
|
|
1715
1802
|
/**
|
|
1716
|
-
*
|
|
1803
|
+
* Shared dispatch path for reload/info/debug requests. Logs the dispatch,
|
|
1804
|
+
* emits the signal event, then either invokes the user-supplied callback
|
|
1805
|
+
* (passing the broadcast function so the user controls when/whether to
|
|
1806
|
+
* broadcast) or broadcasts directly when no callback is configured.
|
|
1717
1807
|
*
|
|
1718
1808
|
* When called from signal handlers (source='signal'), the Promise is started
|
|
1719
|
-
* but not awaited
|
|
1720
|
-
* still notified and the work completes
|
|
1721
|
-
*
|
|
1809
|
+
* but not awaited — Node.js signal handlers cannot return values, so results
|
|
1810
|
+
* are not accessible. Components are still notified and the work completes.
|
|
1722
1811
|
* When called from manual triggers (source='trigger'), the Promise is awaited
|
|
1723
1812
|
* and results are returned for programmatic use.
|
|
1724
|
-
*
|
|
1725
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
1726
1813
|
*/
|
|
1814
|
+
private handleSignalRequest;
|
|
1727
1815
|
private handleReloadRequest;
|
|
1728
|
-
/**
|
|
1729
|
-
* Handle info request - calls custom callback or broadcasts to components.
|
|
1730
|
-
*
|
|
1731
|
-
* When called from signal handlers, the Promise executes but return values
|
|
1732
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
1733
|
-
*
|
|
1734
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
1735
|
-
*/
|
|
1736
1816
|
private handleInfoRequest;
|
|
1817
|
+
private handleDebugRequest;
|
|
1737
1818
|
/**
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
1742
|
-
*
|
|
1743
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
1819
|
+
* Shared signal broadcast pipeline used by reload/info/debug.
|
|
1820
|
+
* Iterates running components, runs the picked handler with timeout, and
|
|
1821
|
+
* aggregates per-component results into a SignalBroadcastResult.
|
|
1744
1822
|
*/
|
|
1745
|
-
private
|
|
1823
|
+
private runSignalBroadcast;
|
|
1746
1824
|
/**
|
|
1747
1825
|
* Broadcast reload signal to all running components.
|
|
1748
1826
|
* Calls onReload() on components that implement it.
|
|
@@ -1962,6 +2040,15 @@ interface LifecycleManagerEventMap {
|
|
|
1962
2040
|
reason?: string;
|
|
1963
2041
|
code?: string;
|
|
1964
2042
|
};
|
|
2043
|
+
'component:stalled-resolved': {
|
|
2044
|
+
name: string;
|
|
2045
|
+
stallInfo: ComponentStallInfo;
|
|
2046
|
+
stalledDurationMS: number;
|
|
2047
|
+
};
|
|
2048
|
+
'component:unexpected-stop': {
|
|
2049
|
+
name: string;
|
|
2050
|
+
error?: Error;
|
|
2051
|
+
};
|
|
1965
2052
|
'component:shutdown-force-completed': {
|
|
1966
2053
|
name: string;
|
|
1967
2054
|
};
|
|
@@ -2140,6 +2227,8 @@ declare class LifecycleManagerEvents {
|
|
|
2140
2227
|
reason?: string;
|
|
2141
2228
|
code?: string;
|
|
2142
2229
|
}): void;
|
|
2230
|
+
componentStalledResolved(name: string, stallInfo: ComponentStallInfo, stalledDurationMS: number): void;
|
|
2231
|
+
componentUnexpectedStop(name: string, error?: Error): void;
|
|
2143
2232
|
componentShutdownForceCompleted(name: string): void;
|
|
2144
2233
|
componentShutdownForceTimeout(name: string, timeoutMS: number): void;
|
|
2145
2234
|
componentStartupRollback(name: string): void;
|
|
@@ -2329,4 +2418,4 @@ declare const lifecycleManagerErrCodes: {
|
|
|
2329
2418
|
readonly StopTimeout: "StopTimeout";
|
|
2330
2419
|
};
|
|
2331
2420
|
|
|
2332
|
-
export { BaseComponent, type BaseOperationResult, type BroadcastOptions, type BroadcastResult, type ComponentHealthResult, ComponentNotFoundError, type ComponentOperationFailureCode, type ComponentOperationResult, type ComponentOptions, ComponentRegistrationError, type ComponentSignalResult, type ComponentStallInfo, ComponentStartTimeoutError, ComponentStartupError, type ComponentState, type ComponentStatus, ComponentStopTimeoutError, type ComponentValueResult, DependencyCycleError, type DependencyValidationResult, type ForceShutdownContext, type GetValueOptions, type HealthCheckResult, type HealthReport, type InsertComponentAtResult, type InsertPosition, InvalidComponentNameError, LifecycleManager, type LifecycleManagerEmit, type LifecycleManagerEventMap, type LifecycleManagerEventName, LifecycleManagerEvents, type LifecycleManagerOptions, type LifecycleManagerStatus, type MessageResult, MissingDependencyError, type RegisterComponentResult, type RegisterOptions, type RegistrationFailureCode, type RepeatedShutdownRequestPolicy, type RestartComponentOptions, type RestartResult, type SendMessageOptions, type ShutdownEscalationStatus, type ShutdownMethod, type ShutdownResult, type SignalBroadcastResult, type StartComponentOptions, type StartupOptions, type StartupOrderFailureCode, type StartupOrderResult, type StartupResult, StartupTimeoutError, type StopAllOptions, type StopComponentOptions, type SystemState, type UnregisterComponentResult, type UnregisterFailureCode, type UnregisterOptions, type ValueResult, lifecycleManagerErrCodes, lifecycleManagerErrPrefix, lifecycleManagerErrTypes };
|
|
2421
|
+
export { BaseComponent, type BaseOperationResult, type BroadcastOptions, type BroadcastResult, type ComponentHealthResult, ComponentNotFoundError, type ComponentOperationFailureCode, type ComponentOperationResult, type ComponentOptions, ComponentRegistrationError, type ComponentSignalResult, type ComponentStallInfo, ComponentStartTimeoutError, ComponentStartupError, type ComponentState, type ComponentStatus, ComponentStopTimeoutError, type ComponentValueResult, DependencyCycleError, type DependencyValidationResult, type ForceShutdownContext, type GetValueOptions, type HealthCheckResult, type HealthReport, type InsertComponentAtResult, type InsertPosition, InvalidComponentNameError, LifecycleManager, type LifecycleManagerEmit, type LifecycleManagerEventMap, type LifecycleManagerEventName, LifecycleManagerEvents, type LifecycleManagerOptions, type LifecycleManagerStatus, type LifecycleValueProvider, type MessageResult, MissingDependencyError, type RegisterComponentResult, type RegisterOptions, type RegistrationFailureCode, type RepeatedShutdownRequestPolicy, type RestartComponentOptions, type RestartResult, type SendMessageOptions, type ShutdownEscalationStatus, type ShutdownMethod, type ShutdownResult, type SignalBroadcastResult, type StartComponentOptions, type StartupOptions, type StartupOrderFailureCode, type StartupOrderResult, type StartupResult, StartupTimeoutError, type StopAllOptions, type StopComponentOptions, type SystemState, type UnregisterComponentResult, type UnregisterFailureCode, type UnregisterOptions, type ValueResult, lifecycleManagerErrCodes, lifecycleManagerErrPrefix, lifecycleManagerErrTypes };
|
|
@@ -158,7 +158,7 @@ interface RestartComponentOptions {
|
|
|
158
158
|
/**
|
|
159
159
|
* Stable, machine-readable failure codes for individual component operations
|
|
160
160
|
*/
|
|
161
|
-
type ComponentOperationFailureCode = 'component_not_found' | 'component_already_running' | 'component_already_starting' | 'component_already_stopping' | 'component_not_running' | 'component_stalled' | 'missing_dependency' | 'dependency_not_running' | 'has_running_dependents' | 'startup_in_progress' | 'shutdown_in_progress' | 'component_startup_timeout' | 'component_shutdown_timeout' | 'restart_stop_failed' | 'restart_start_failed' | 'unknown_error';
|
|
161
|
+
type ComponentOperationFailureCode = 'component_not_found' | 'component_already_running' | 'component_already_starting' | 'component_already_stopping' | 'component_not_running' | 'component_stalled' | 'missing_dependency' | 'dependency_not_running' | 'has_running_dependents' | 'startup_in_progress' | 'shutdown_in_progress' | 'component_unexpected_stop' | 'component_startup_timeout' | 'component_shutdown_timeout' | 'restart_stop_failed' | 'restart_start_failed' | 'unknown_error';
|
|
162
162
|
/**
|
|
163
163
|
* Failure codes for unregister operations
|
|
164
164
|
*/
|
|
@@ -202,7 +202,7 @@ interface StartupResult {
|
|
|
202
202
|
/** Reason for failure (when success is false) */
|
|
203
203
|
reason?: string;
|
|
204
204
|
/** Error code (when success is false) */
|
|
205
|
-
code?: 'already_in_progress' | 'shutdown_in_progress' | 'dependency_cycle' | 'no_components_registered' | 'stalled_components_exist' | 'partial_state' | 'required_component_failed' | 'startup_timeout' | 'unknown_error';
|
|
205
|
+
code?: 'already_in_progress' | 'component_unexpected_stop' | 'shutdown_in_progress' | 'dependency_cycle' | 'no_components_registered' | 'stalled_components_exist' | 'partial_state' | 'required_component_failed' | 'startup_timeout' | 'unknown_error';
|
|
206
206
|
/** Error object (when success is false due to dependency cycle or unknown error) */
|
|
207
207
|
error?: Error;
|
|
208
208
|
/** Total startup duration in milliseconds */
|
|
@@ -438,13 +438,20 @@ interface ValueResult<T = unknown> {
|
|
|
438
438
|
code: 'found' | 'not_found' | 'stopped' | 'stalled' | 'no_handler' | 'error';
|
|
439
439
|
}
|
|
440
440
|
type EventEmitterSurface = Pick<EventEmitterProtected, 'on' | 'once' | 'hasListener' | 'hasListeners' | 'listenerCount'>;
|
|
441
|
+
/**
|
|
442
|
+
* Public value lookup surface shared by LifecycleManager and component-scoped
|
|
443
|
+
* lifecycle references.
|
|
444
|
+
*/
|
|
445
|
+
interface LifecycleValueProvider {
|
|
446
|
+
getValue<T = unknown>(componentName: string, key: string, options?: GetValueOptions): ValueResult<T>;
|
|
447
|
+
}
|
|
441
448
|
/**
|
|
442
449
|
* Common lifecycle interface shared by LifecycleManager and ComponentLifecycle
|
|
443
450
|
*
|
|
444
451
|
* Keep in sync with public LifecycleManager API and ComponentLifecycle proxy.
|
|
445
452
|
* Purpose: define the shared surface both expose to avoid drift across the two.
|
|
446
453
|
*/
|
|
447
|
-
interface LifecycleCommon extends EventEmitterSurface {
|
|
454
|
+
interface LifecycleCommon extends EventEmitterSurface, LifecycleValueProvider {
|
|
448
455
|
hasComponent(name: string): boolean;
|
|
449
456
|
isComponentRunning(name: string): boolean;
|
|
450
457
|
getComponentNames(): string[];
|
|
@@ -479,7 +486,6 @@ interface LifecycleCommon extends EventEmitterSurface {
|
|
|
479
486
|
broadcastMessage(payload: unknown, options?: BroadcastOptions): Promise<BroadcastResult[]>;
|
|
480
487
|
checkComponentHealth(name: string): Promise<HealthCheckResult>;
|
|
481
488
|
checkAllHealth(): Promise<HealthReport>;
|
|
482
|
-
getValue<T = unknown>(componentName: string, key: string, options?: GetValueOptions): ValueResult<T>;
|
|
483
489
|
}
|
|
484
490
|
/**
|
|
485
491
|
* Component-scoped lifecycle interface injected into BaseComponent
|
|
@@ -546,7 +552,7 @@ interface UnregisterOptions {
|
|
|
546
552
|
* Options for starting all components
|
|
547
553
|
*/
|
|
548
554
|
interface StartupOptions {
|
|
549
|
-
/** Allow
|
|
555
|
+
/** Allow bulk startup to proceed by skipping stalled components (default: false) */
|
|
550
556
|
ignoreStalledComponents?: boolean;
|
|
551
557
|
/** Global timeout for entire startup process in milliseconds (default: constructor's startupTimeoutMS) */
|
|
552
558
|
timeoutMS?: number;
|
|
@@ -956,6 +962,12 @@ declare abstract class BaseComponent {
|
|
|
956
962
|
protected name: string;
|
|
957
963
|
/** Reference to component-scoped lifecycle (set by manager when registered) */
|
|
958
964
|
protected lifecycle: ComponentLifecycleRef;
|
|
965
|
+
/** @internal Set by LifecycleManager while the component is running. */
|
|
966
|
+
private _unexpectedStopHandler?;
|
|
967
|
+
/** @internal Incremented whenever the unexpected-stop handler is re-armed or cleared. */
|
|
968
|
+
private _unexpectedStopGeneration;
|
|
969
|
+
/** @internal Flag indicating whether this component is currently registered with a LifecycleManager */
|
|
970
|
+
private _isRegistered;
|
|
959
971
|
/**
|
|
960
972
|
* Create a new component
|
|
961
973
|
*
|
|
@@ -964,6 +976,16 @@ declare abstract class BaseComponent {
|
|
|
964
976
|
* @throws {InvalidComponentNameError} If name doesn't match kebab-case pattern
|
|
965
977
|
*/
|
|
966
978
|
constructor(rootLogger: Logger, options: ComponentOptions);
|
|
979
|
+
/** @internal Called by LifecycleManager after a successful start. */
|
|
980
|
+
_setUnexpectedStopHandler(handler: (error?: Error) => boolean): void;
|
|
981
|
+
/** @internal Called by LifecycleManager when stop begins or component is unregistered. */
|
|
982
|
+
_clearUnexpectedStopHandler(): void;
|
|
983
|
+
/** @internal Called by LifecycleManager when registering the component. */
|
|
984
|
+
_markRegistered(): void;
|
|
985
|
+
/** @internal Called by LifecycleManager when unregistering the component. */
|
|
986
|
+
_markUnregistered(): void;
|
|
987
|
+
/** @internal Check if the component is registered with any LifecycleManager. */
|
|
988
|
+
_isRegisteredWithManager(): boolean;
|
|
967
989
|
/**
|
|
968
990
|
* Start the component
|
|
969
991
|
*
|
|
@@ -1146,6 +1168,28 @@ declare abstract class BaseComponent {
|
|
|
1146
1168
|
* Check if component is optional
|
|
1147
1169
|
*/
|
|
1148
1170
|
isOptional(): boolean;
|
|
1171
|
+
/**
|
|
1172
|
+
* Run-scoped unexpected-stop callback. Rebound by LifecycleManager on each
|
|
1173
|
+
* successful start so captured references from older runs go stale.
|
|
1174
|
+
*/
|
|
1175
|
+
protected reportUnexpectedStop: (error?: Error) => boolean;
|
|
1176
|
+
/**
|
|
1177
|
+
* Get this component's own status from the manager's perspective.
|
|
1178
|
+
*
|
|
1179
|
+
* Equivalent to `this.lifecycle.getComponentStatus(this.getName())` but without
|
|
1180
|
+
* needing to pass the name. Returns `undefined` if the component is not registered.
|
|
1181
|
+
*
|
|
1182
|
+
* Check `status?.state === 'running'` to test whether the component is currently running.
|
|
1183
|
+
*/
|
|
1184
|
+
protected getSelfStatus(): ComponentStatus | undefined;
|
|
1185
|
+
/**
|
|
1186
|
+
* Capture a run-scoped unexpected-stop reporter for async listeners created during start().
|
|
1187
|
+
*
|
|
1188
|
+
* Unlike calling `this.reportUnexpectedStop()` later, the returned callback becomes a no-op
|
|
1189
|
+
* once the component is stopped, unregistered, or restarted. This prevents stale listeners
|
|
1190
|
+
* from a previous run from stopping a newer run of the same component instance.
|
|
1191
|
+
*/
|
|
1192
|
+
protected getUnexpectedStopReporter(): (error?: Error) => boolean;
|
|
1149
1193
|
}
|
|
1150
1194
|
|
|
1151
1195
|
/**
|
|
@@ -1179,7 +1223,11 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1179
1223
|
private componentTimestamps;
|
|
1180
1224
|
private componentErrors;
|
|
1181
1225
|
private componentStartAttemptTokens;
|
|
1226
|
+
private componentStopAttemptTokens;
|
|
1227
|
+
private pendingForceStopWaiters;
|
|
1228
|
+
private unexpectedStopsDuringStartup;
|
|
1182
1229
|
private isStarting;
|
|
1230
|
+
private autoAttachedSignalsDuringStartup;
|
|
1183
1231
|
private isStarted;
|
|
1184
1232
|
private isShuttingDown;
|
|
1185
1233
|
private shutdownToken;
|
|
@@ -1592,6 +1640,45 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1592
1640
|
private autoAttachSignals;
|
|
1593
1641
|
private autoDetachSignalsIfIdle;
|
|
1594
1642
|
private monitorLateStartupCompletion;
|
|
1643
|
+
private consumeUnexpectedStopsDuringStartup;
|
|
1644
|
+
/**
|
|
1645
|
+
* Issues and returns a unique stop attempt token for a component.
|
|
1646
|
+
*
|
|
1647
|
+
* Each stop attempt (graceful or force-retry) gets a unique token.
|
|
1648
|
+
* The late-resolution handler captures this token in its closure so it can
|
|
1649
|
+
* skip any stall entries that were created by a *later* stop attempt — e.g. a
|
|
1650
|
+
* force-retry that also timed out after the original graceful promise floated
|
|
1651
|
+
* in the background.
|
|
1652
|
+
*/
|
|
1653
|
+
private issueStopAttemptToken;
|
|
1654
|
+
private createPendingForceStopWaiter;
|
|
1655
|
+
private resolvePendingForceStopWaiters;
|
|
1656
|
+
/**
|
|
1657
|
+
* Called when a stop promise eventually resolves after its timeout path already fired.
|
|
1658
|
+
*
|
|
1659
|
+
* Usually this means a previously stalled component's original stop() or
|
|
1660
|
+
* onShutdownForce() promise finally resolved, so the manager can clear the
|
|
1661
|
+
* stall and transition the component to stopped without a manual retry.
|
|
1662
|
+
*
|
|
1663
|
+
* There is one extra overlap case for graceful stop(): stop() can resolve
|
|
1664
|
+
* after the graceful timeout but before onShutdownForce() itself times out.
|
|
1665
|
+
* In that window no stall entry exists yet, but the component still finished
|
|
1666
|
+
* stopping cleanly, so we finalize it here and let the later force-timeout
|
|
1667
|
+
* path observe the already-stopped state and no-op. This overlap fix is
|
|
1668
|
+
* scoped to the same stop token and will not cross a later retry attempt.
|
|
1669
|
+
*
|
|
1670
|
+
* Two guards prevent stale floating promises from incorrectly clearing state:
|
|
1671
|
+
*
|
|
1672
|
+
* 1. token guard — if a newer stop attempt (e.g. a retryStalled
|
|
1673
|
+
* force-retry) has started since this promise was launched, its token
|
|
1674
|
+
* won't match and we bail out immediately.
|
|
1675
|
+
*
|
|
1676
|
+
* 2. state/stall guard — if the component was unregistered, restarted, or
|
|
1677
|
+
* already cleared by another path, there will be neither a matching stall
|
|
1678
|
+
* entry nor the force-phase overlap state, so we bail out.
|
|
1679
|
+
*/
|
|
1680
|
+
private handleLateStopResolution;
|
|
1681
|
+
private handleComponentUnexpectedStop;
|
|
1595
1682
|
/**
|
|
1596
1683
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
1597
1684
|
*/
|
|
@@ -1713,36 +1800,27 @@ declare class LifecycleManager extends EventEmitterProtected implements Lifecycl
|
|
|
1713
1800
|
*/
|
|
1714
1801
|
private armRepeatedShutdownAfterFailure;
|
|
1715
1802
|
/**
|
|
1716
|
-
*
|
|
1803
|
+
* Shared dispatch path for reload/info/debug requests. Logs the dispatch,
|
|
1804
|
+
* emits the signal event, then either invokes the user-supplied callback
|
|
1805
|
+
* (passing the broadcast function so the user controls when/whether to
|
|
1806
|
+
* broadcast) or broadcasts directly when no callback is configured.
|
|
1717
1807
|
*
|
|
1718
1808
|
* When called from signal handlers (source='signal'), the Promise is started
|
|
1719
|
-
* but not awaited
|
|
1720
|
-
* still notified and the work completes
|
|
1721
|
-
*
|
|
1809
|
+
* but not awaited — Node.js signal handlers cannot return values, so results
|
|
1810
|
+
* are not accessible. Components are still notified and the work completes.
|
|
1722
1811
|
* When called from manual triggers (source='trigger'), the Promise is awaited
|
|
1723
1812
|
* and results are returned for programmatic use.
|
|
1724
|
-
*
|
|
1725
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
1726
1813
|
*/
|
|
1814
|
+
private handleSignalRequest;
|
|
1727
1815
|
private handleReloadRequest;
|
|
1728
|
-
/**
|
|
1729
|
-
* Handle info request - calls custom callback or broadcasts to components.
|
|
1730
|
-
*
|
|
1731
|
-
* When called from signal handlers, the Promise executes but return values
|
|
1732
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
1733
|
-
*
|
|
1734
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
1735
|
-
*/
|
|
1736
1816
|
private handleInfoRequest;
|
|
1817
|
+
private handleDebugRequest;
|
|
1737
1818
|
/**
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
* are not accessible due to Node.js signal handler constraints.
|
|
1742
|
-
*
|
|
1743
|
-
* @param source - Whether triggered from signal manager or manual trigger
|
|
1819
|
+
* Shared signal broadcast pipeline used by reload/info/debug.
|
|
1820
|
+
* Iterates running components, runs the picked handler with timeout, and
|
|
1821
|
+
* aggregates per-component results into a SignalBroadcastResult.
|
|
1744
1822
|
*/
|
|
1745
|
-
private
|
|
1823
|
+
private runSignalBroadcast;
|
|
1746
1824
|
/**
|
|
1747
1825
|
* Broadcast reload signal to all running components.
|
|
1748
1826
|
* Calls onReload() on components that implement it.
|
|
@@ -1962,6 +2040,15 @@ interface LifecycleManagerEventMap {
|
|
|
1962
2040
|
reason?: string;
|
|
1963
2041
|
code?: string;
|
|
1964
2042
|
};
|
|
2043
|
+
'component:stalled-resolved': {
|
|
2044
|
+
name: string;
|
|
2045
|
+
stallInfo: ComponentStallInfo;
|
|
2046
|
+
stalledDurationMS: number;
|
|
2047
|
+
};
|
|
2048
|
+
'component:unexpected-stop': {
|
|
2049
|
+
name: string;
|
|
2050
|
+
error?: Error;
|
|
2051
|
+
};
|
|
1965
2052
|
'component:shutdown-force-completed': {
|
|
1966
2053
|
name: string;
|
|
1967
2054
|
};
|
|
@@ -2140,6 +2227,8 @@ declare class LifecycleManagerEvents {
|
|
|
2140
2227
|
reason?: string;
|
|
2141
2228
|
code?: string;
|
|
2142
2229
|
}): void;
|
|
2230
|
+
componentStalledResolved(name: string, stallInfo: ComponentStallInfo, stalledDurationMS: number): void;
|
|
2231
|
+
componentUnexpectedStop(name: string, error?: Error): void;
|
|
2143
2232
|
componentShutdownForceCompleted(name: string): void;
|
|
2144
2233
|
componentShutdownForceTimeout(name: string, timeoutMS: number): void;
|
|
2145
2234
|
componentStartupRollback(name: string): void;
|
|
@@ -2329,4 +2418,4 @@ declare const lifecycleManagerErrCodes: {
|
|
|
2329
2418
|
readonly StopTimeout: "StopTimeout";
|
|
2330
2419
|
};
|
|
2331
2420
|
|
|
2332
|
-
export { BaseComponent, type BaseOperationResult, type BroadcastOptions, type BroadcastResult, type ComponentHealthResult, ComponentNotFoundError, type ComponentOperationFailureCode, type ComponentOperationResult, type ComponentOptions, ComponentRegistrationError, type ComponentSignalResult, type ComponentStallInfo, ComponentStartTimeoutError, ComponentStartupError, type ComponentState, type ComponentStatus, ComponentStopTimeoutError, type ComponentValueResult, DependencyCycleError, type DependencyValidationResult, type ForceShutdownContext, type GetValueOptions, type HealthCheckResult, type HealthReport, type InsertComponentAtResult, type InsertPosition, InvalidComponentNameError, LifecycleManager, type LifecycleManagerEmit, type LifecycleManagerEventMap, type LifecycleManagerEventName, LifecycleManagerEvents, type LifecycleManagerOptions, type LifecycleManagerStatus, type MessageResult, MissingDependencyError, type RegisterComponentResult, type RegisterOptions, type RegistrationFailureCode, type RepeatedShutdownRequestPolicy, type RestartComponentOptions, type RestartResult, type SendMessageOptions, type ShutdownEscalationStatus, type ShutdownMethod, type ShutdownResult, type SignalBroadcastResult, type StartComponentOptions, type StartupOptions, type StartupOrderFailureCode, type StartupOrderResult, type StartupResult, StartupTimeoutError, type StopAllOptions, type StopComponentOptions, type SystemState, type UnregisterComponentResult, type UnregisterFailureCode, type UnregisterOptions, type ValueResult, lifecycleManagerErrCodes, lifecycleManagerErrPrefix, lifecycleManagerErrTypes };
|
|
2421
|
+
export { BaseComponent, type BaseOperationResult, type BroadcastOptions, type BroadcastResult, type ComponentHealthResult, ComponentNotFoundError, type ComponentOperationFailureCode, type ComponentOperationResult, type ComponentOptions, ComponentRegistrationError, type ComponentSignalResult, type ComponentStallInfo, ComponentStartTimeoutError, ComponentStartupError, type ComponentState, type ComponentStatus, ComponentStopTimeoutError, type ComponentValueResult, DependencyCycleError, type DependencyValidationResult, type ForceShutdownContext, type GetValueOptions, type HealthCheckResult, type HealthReport, type InsertComponentAtResult, type InsertPosition, InvalidComponentNameError, LifecycleManager, type LifecycleManagerEmit, type LifecycleManagerEventMap, type LifecycleManagerEventName, LifecycleManagerEvents, type LifecycleManagerOptions, type LifecycleManagerStatus, type LifecycleValueProvider, type MessageResult, MissingDependencyError, type RegisterComponentResult, type RegisterOptions, type RegistrationFailureCode, type RepeatedShutdownRequestPolicy, type RestartComponentOptions, type RestartResult, type SendMessageOptions, type ShutdownEscalationStatus, type ShutdownMethod, type ShutdownResult, type SignalBroadcastResult, type StartComponentOptions, type StartupOptions, type StartupOrderFailureCode, type StartupOrderResult, type StartupResult, StartupTimeoutError, type StopAllOptions, type StopComponentOptions, type SystemState, type UnregisterComponentResult, type UnregisterFailureCode, type UnregisterOptions, type ValueResult, lifecycleManagerErrCodes, lifecycleManagerErrPrefix, lifecycleManagerErrTypes };
|