lifecycleion 0.0.2 → 0.0.3
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 +1 -1
- package/dist/lib/lifecycle-manager/index.cjs +118 -5
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
- package/dist/lib/lifecycle-manager/index.d.cts +9 -1
- package/dist/lib/lifecycle-manager/index.d.ts +9 -1
- package/dist/lib/lifecycle-manager/index.js +118 -5
- package/dist/lib/lifecycle-manager/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -900,6 +900,9 @@ var EventEmitterProtected = class {
|
|
|
900
900
|
}
|
|
901
901
|
};
|
|
902
902
|
|
|
903
|
+
// src/lib/lifecycle-manager/lifecycle-manager.ts
|
|
904
|
+
var import_ulid2 = require("ulid");
|
|
905
|
+
|
|
903
906
|
// src/lib/lifecycle-manager/component-lifecycle.ts
|
|
904
907
|
var ComponentLifecycle = class {
|
|
905
908
|
manager;
|
|
@@ -1965,6 +1968,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
1965
1968
|
messageTimeoutMS;
|
|
1966
1969
|
startupTimeoutMS;
|
|
1967
1970
|
shutdownOptions;
|
|
1971
|
+
attachSignalsBeforeStartup;
|
|
1968
1972
|
attachSignalsOnStart;
|
|
1969
1973
|
detachSignalsOnStop;
|
|
1970
1974
|
// Component management
|
|
@@ -1975,10 +1979,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
1975
1979
|
// State tracking for individual components
|
|
1976
1980
|
componentTimestamps = /* @__PURE__ */ new Map();
|
|
1977
1981
|
componentErrors = /* @__PURE__ */ new Map();
|
|
1982
|
+
componentStartAttemptTokens = /* @__PURE__ */ new Map();
|
|
1978
1983
|
// State flags
|
|
1979
1984
|
isStarting = false;
|
|
1980
1985
|
isStarted = false;
|
|
1981
1986
|
isShuttingDown = false;
|
|
1987
|
+
// Unique token used to detect shutdowns that happened during async start().
|
|
1988
|
+
shutdownToken = (0, import_ulid2.ulid)();
|
|
1982
1989
|
shutdownMethod = null;
|
|
1983
1990
|
lastShutdownResult = null;
|
|
1984
1991
|
// Signal management
|
|
@@ -2004,6 +2011,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2004
2011
|
haltOnStall: true,
|
|
2005
2012
|
...options.shutdownOptions
|
|
2006
2013
|
};
|
|
2014
|
+
this.attachSignalsBeforeStartup = options.attachSignalsBeforeStartup ?? false;
|
|
2007
2015
|
this.attachSignalsOnStart = options.attachSignalsOnStart ?? false;
|
|
2008
2016
|
this.detachSignalsOnStop = options.detachSignalsOnStop ?? false;
|
|
2009
2017
|
this.onReloadRequested = options.onReloadRequested;
|
|
@@ -2167,6 +2175,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2167
2175
|
this.componentStates.delete(name);
|
|
2168
2176
|
this.componentTimestamps.delete(name);
|
|
2169
2177
|
this.componentErrors.delete(name);
|
|
2178
|
+
this.componentStartAttemptTokens.delete(name);
|
|
2170
2179
|
this.stalledComponents.delete(name);
|
|
2171
2180
|
this.runningComponents.delete(name);
|
|
2172
2181
|
this.updateStartedFlag();
|
|
@@ -2575,6 +2584,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2575
2584
|
this.isStarting = true;
|
|
2576
2585
|
this.shutdownMethod = null;
|
|
2577
2586
|
this.lastShutdownResult = null;
|
|
2587
|
+
const didAutoAttachSignalsForBulkStartup = this.attachSignalsBeforeStartup ? this.autoAttachSignals("bulk startup") : false;
|
|
2578
2588
|
this.logger.info("Starting all components");
|
|
2579
2589
|
const effectiveTimeout = options?.timeoutMS ?? this.startupTimeoutMS;
|
|
2580
2590
|
let hasTimedOut = false;
|
|
@@ -2688,6 +2698,18 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2688
2698
|
startedComponents.push(name);
|
|
2689
2699
|
} else if (result.code === "component_already_running") {
|
|
2690
2700
|
startedComponents.push(name);
|
|
2701
|
+
} else if (result.code === "shutdown_in_progress") {
|
|
2702
|
+
await this.rollbackStartup(startedComponents);
|
|
2703
|
+
return {
|
|
2704
|
+
success: false,
|
|
2705
|
+
startedComponents: [],
|
|
2706
|
+
failedOptionalComponents,
|
|
2707
|
+
skippedDueToDependency: Array.from(skippedDueToDependency),
|
|
2708
|
+
reason: result.reason || "Shutdown triggered during startup",
|
|
2709
|
+
code: "shutdown_in_progress",
|
|
2710
|
+
error: result.error,
|
|
2711
|
+
durationMS: Date.now() - startTime
|
|
2712
|
+
};
|
|
2691
2713
|
} else {
|
|
2692
2714
|
if (component.isOptional()) {
|
|
2693
2715
|
this.logger.entity(name).warn("Optional component failed to start, continuing", {
|
|
@@ -2776,6 +2798,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
2776
2798
|
if (timeoutHandle) {
|
|
2777
2799
|
clearTimeout(timeoutHandle);
|
|
2778
2800
|
}
|
|
2801
|
+
if (didAutoAttachSignalsForBulkStartup) {
|
|
2802
|
+
this.autoDetachSignalsIfIdle("failed bulk startup");
|
|
2803
|
+
}
|
|
2779
2804
|
this.isStarting = false;
|
|
2780
2805
|
}
|
|
2781
2806
|
}
|
|
@@ -3843,6 +3868,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3843
3868
|
stoppedAt: null
|
|
3844
3869
|
});
|
|
3845
3870
|
this.componentErrors.set(componentName, null);
|
|
3871
|
+
this.componentStartAttemptTokens.set(componentName, (0, import_ulid2.ulid)());
|
|
3846
3872
|
const isManualPositionRespected = this.isManualPositionRespected({
|
|
3847
3873
|
componentName,
|
|
3848
3874
|
position,
|
|
@@ -3994,6 +4020,7 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
3994
4020
|
};
|
|
3995
4021
|
}
|
|
3996
4022
|
this.isShuttingDown = true;
|
|
4023
|
+
this.shutdownToken = (0, import_ulid2.ulid)();
|
|
3997
4024
|
this.shutdownMethod = method;
|
|
3998
4025
|
const isDuringStartup = this.isStarting;
|
|
3999
4026
|
this.logger.info("Stopping all components", { params: { method } });
|
|
@@ -4264,6 +4291,10 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4264
4291
|
this.logger.entity(name).info("Starting component");
|
|
4265
4292
|
this.lifecycleEvents.componentStarting(name);
|
|
4266
4293
|
const timeoutMS = component.startupTimeoutMS;
|
|
4294
|
+
const startAttemptToken = (0, import_ulid2.ulid)();
|
|
4295
|
+
this.componentStartAttemptTokens.set(name, startAttemptToken);
|
|
4296
|
+
const shutdownTokenAtStart = this.shutdownToken;
|
|
4297
|
+
const didAutoAttachSignalsForComponentStartup = this.attachSignalsBeforeStartup ? this.autoAttachSignals("component startup") : false;
|
|
4267
4298
|
let timeoutHandle;
|
|
4268
4299
|
try {
|
|
4269
4300
|
const startPromise = component.start();
|
|
@@ -4278,6 +4309,13 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4278
4309
|
params: { error }
|
|
4279
4310
|
});
|
|
4280
4311
|
}
|
|
4312
|
+
} else {
|
|
4313
|
+
this.monitorLateStartupCompletion(
|
|
4314
|
+
name,
|
|
4315
|
+
component,
|
|
4316
|
+
startPromise,
|
|
4317
|
+
startAttemptToken
|
|
4318
|
+
);
|
|
4281
4319
|
}
|
|
4282
4320
|
Promise.resolve(startPromise).catch(() => {
|
|
4283
4321
|
});
|
|
@@ -4293,15 +4331,36 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4293
4331
|
} else {
|
|
4294
4332
|
await startPromise;
|
|
4295
4333
|
}
|
|
4334
|
+
if (this.isShuttingDown || shutdownTokenAtStart !== this.shutdownToken) {
|
|
4335
|
+
this.componentStates.set(name, "running");
|
|
4336
|
+
this.runningComponents.add(name);
|
|
4337
|
+
this.stalledComponents.delete(name);
|
|
4338
|
+
this.updateStartedFlag();
|
|
4339
|
+
const timestamps2 = this.componentTimestamps.get(name) ?? {
|
|
4340
|
+
startedAt: null,
|
|
4341
|
+
stoppedAt: null
|
|
4342
|
+
};
|
|
4343
|
+
timestamps2.startedAt = Date.now();
|
|
4344
|
+
this.componentTimestamps.set(name, timestamps2);
|
|
4345
|
+
this.logger.entity(name).warn(
|
|
4346
|
+
"Component finished starting after shutdown began, stopping immediately"
|
|
4347
|
+
);
|
|
4348
|
+
const stopResult = await this.stopComponentInternal(name);
|
|
4349
|
+
return {
|
|
4350
|
+
success: false,
|
|
4351
|
+
componentName: name,
|
|
4352
|
+
reason: "Shutdown triggered during component startup",
|
|
4353
|
+
code: "shutdown_in_progress",
|
|
4354
|
+
error: stopResult.error,
|
|
4355
|
+
status: this.getComponentStatus(name)
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4296
4358
|
this.componentStates.set(name, "running");
|
|
4297
4359
|
this.runningComponents.add(name);
|
|
4298
4360
|
this.stalledComponents.delete(name);
|
|
4299
4361
|
this.updateStartedFlag();
|
|
4300
|
-
if (this.attachSignalsOnStart && this.runningComponents.size === 1
|
|
4301
|
-
this.
|
|
4302
|
-
"Auto-attaching process signals on first component start"
|
|
4303
|
-
);
|
|
4304
|
-
this.attachSignals();
|
|
4362
|
+
if (this.attachSignalsOnStart && this.runningComponents.size === 1) {
|
|
4363
|
+
this.autoAttachSignals("first component start");
|
|
4305
4364
|
}
|
|
4306
4365
|
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
4307
4366
|
startedAt: null,
|
|
@@ -4350,6 +4409,9 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4350
4409
|
if (timeoutHandle) {
|
|
4351
4410
|
clearTimeout(timeoutHandle);
|
|
4352
4411
|
}
|
|
4412
|
+
if (didAutoAttachSignalsForComponentStartup) {
|
|
4413
|
+
this.autoDetachSignalsIfIdle("failed component startup");
|
|
4414
|
+
}
|
|
4353
4415
|
}
|
|
4354
4416
|
}
|
|
4355
4417
|
/**
|
|
@@ -4846,6 +4908,57 @@ var LifecycleManager = class extends EventEmitterProtected {
|
|
|
4846
4908
|
}
|
|
4847
4909
|
this.logger.info("Rollback completed");
|
|
4848
4910
|
}
|
|
4911
|
+
autoAttachSignals(trigger) {
|
|
4912
|
+
if (this.processSignalManager?.getStatus().isAttached) {
|
|
4913
|
+
return false;
|
|
4914
|
+
}
|
|
4915
|
+
this.logger.info(`Auto-attaching process signals on ${trigger}`);
|
|
4916
|
+
this.attachSignals();
|
|
4917
|
+
return true;
|
|
4918
|
+
}
|
|
4919
|
+
autoDetachSignalsIfIdle(trigger) {
|
|
4920
|
+
if (!this.detachSignalsOnStop || this.runningComponents.size > 0 || !this.processSignalManager?.getStatus().isAttached) {
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
this.logger.info(`Auto-detaching process signals after ${trigger}`);
|
|
4924
|
+
this.detachSignals();
|
|
4925
|
+
}
|
|
4926
|
+
monitorLateStartupCompletion(name, component, startPromise, startAttemptToken) {
|
|
4927
|
+
this.logger.entity(name).warn(
|
|
4928
|
+
"Startup timed out without onStartupAborted, stopping component if startup completes later"
|
|
4929
|
+
);
|
|
4930
|
+
Promise.resolve(startPromise).then(async () => {
|
|
4931
|
+
const timeoutState = this.componentStates.get(name);
|
|
4932
|
+
if (this.getComponent(name) !== component || this.componentStartAttemptTokens.get(name) !== startAttemptToken || this.isComponentRunning(name) || timeoutState !== "starting-timed-out" && timeoutState !== "failed") {
|
|
4933
|
+
return;
|
|
4934
|
+
}
|
|
4935
|
+
this.componentStates.set(name, "running");
|
|
4936
|
+
this.runningComponents.add(name);
|
|
4937
|
+
this.stalledComponents.delete(name);
|
|
4938
|
+
this.updateStartedFlag();
|
|
4939
|
+
const timestamps = this.componentTimestamps.get(name) ?? {
|
|
4940
|
+
startedAt: null,
|
|
4941
|
+
stoppedAt: null
|
|
4942
|
+
};
|
|
4943
|
+
timestamps.startedAt = Date.now();
|
|
4944
|
+
this.componentTimestamps.set(name, timestamps);
|
|
4945
|
+
this.logger.entity(name).warn(
|
|
4946
|
+
"Component completed startup after timeout, stopping automatically"
|
|
4947
|
+
);
|
|
4948
|
+
const stopResult = await this.stopComponentInternal(name);
|
|
4949
|
+
if (!stopResult.success) {
|
|
4950
|
+
this.logger.entity(name).warn("Automatic stop after startup timeout failed", {
|
|
4951
|
+
params: {
|
|
4952
|
+
error: stopResult.error,
|
|
4953
|
+
code: stopResult.code
|
|
4954
|
+
}
|
|
4955
|
+
});
|
|
4956
|
+
return;
|
|
4957
|
+
}
|
|
4958
|
+
this.componentStates.set(name, timeoutState);
|
|
4959
|
+
}).catch(() => {
|
|
4960
|
+
});
|
|
4961
|
+
}
|
|
4849
4962
|
/**
|
|
4850
4963
|
* Safe emit wrapper - prevents event handler errors from breaking lifecycle
|
|
4851
4964
|
*/
|