@seedcord/core 0.1.0-next.6 → 0.1.0-next.7
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/dist/{AutocompleteOptions-eKhIe6Qt.d.mts → AutocompleteOptions-DLp_B8as.d.mts} +61 -112
- package/dist/HmrModuleHandler-3sNgKHdf.mjs +163 -0
- package/dist/HmrModuleHandler-3sNgKHdf.mjs.map +1 -0
- package/dist/HmrModuleHandler-CTBng7wV.cjs +170 -0
- package/dist/HmrModuleHandler-CTBng7wV.cjs.map +1 -0
- package/dist/Plugin-3rlH0uxH.d.mts +620 -0
- package/dist/{Plugin-C2-VHrsX.cjs → Plugin-Cgp1a0uQ.cjs} +13 -4
- package/dist/Plugin-Cgp1a0uQ.cjs.map +1 -0
- package/dist/{Plugin-PcdPjAzc.mjs → Plugin-j9GYdroS.mjs} +13 -4
- package/dist/Plugin-j9GYdroS.mjs.map +1 -0
- package/dist/{permissions-DSW_EZfN.cjs → Silence-DiP95iwD.cjs} +38 -307
- package/dist/Silence-DiP95iwD.cjs.map +1 -0
- package/dist/{permissions-RVCBhL2n.mjs → Silence-dbC2slOB.mjs} +35 -238
- package/dist/Silence-dbC2slOB.mjs.map +1 -0
- package/dist/basic-D7bDlgSP.d.mts +10 -0
- package/dist/hmr.index.cjs +2 -164
- package/dist/hmr.index.mjs +2 -162
- package/dist/index-Dueqztyy.d.mts +142 -0
- package/dist/index.cjs +50 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +7 -5
- package/dist/index.mjs +8 -35
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.cjs +262 -74
- package/dist/internal.index.cjs.map +1 -1
- package/dist/internal.index.d.mts +68 -4
- package/dist/internal.index.mjs +216 -35
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.cjs +123 -52
- package/dist/node-internal.index.cjs.map +1 -1
- package/dist/node-internal.index.d.mts +67 -2158
- package/dist/node-internal.index.mjs +124 -54
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/plugin.index.cjs +1 -1
- package/dist/plugin.index.d.mts +1 -1
- package/dist/plugin.index.mjs +1 -1
- package/dist/publishDefault-CCf1azzW.mjs +264 -0
- package/dist/publishDefault-CCf1azzW.mjs.map +1 -0
- package/dist/publishDefault-l0NQ-SHo.cjs +353 -0
- package/dist/publishDefault-l0NQ-SHo.cjs.map +1 -0
- package/dist/subscribers-B1G6_LlK.cjs +517 -0
- package/dist/subscribers-B1G6_LlK.cjs.map +1 -0
- package/dist/subscribers-DYqFVP1X.mjs +480 -0
- package/dist/subscribers-DYqFVP1X.mjs.map +1 -0
- package/package.json +7 -6
- package/dist/Plugin-C2-VHrsX.cjs.map +0 -1
- package/dist/Plugin-CNyRKDHT.d.mts +0 -226
- package/dist/Plugin-PcdPjAzc.mjs.map +0 -1
- package/dist/hmr.index.cjs.map +0 -1
- package/dist/hmr.index.mjs.map +0 -1
- package/dist/is-never-Ddaba1Su.d.mts +0 -58
- package/dist/permissions-DSW_EZfN.cjs.map +0 -1
- package/dist/permissions-RVCBhL2n.mjs.map +0 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { m as SubscribeMetadataKey } from "./publishDefault-CCf1azzW.mjs";
|
|
1
2
|
import { n as StartupPhase, t as ShutdownPhase } from "./phases-BfpAG6w3.mjs";
|
|
2
|
-
import { n as
|
|
3
|
+
import { n as registrationFor, o as Subscriber } from "./subscribers-DYqFVP1X.mjs";
|
|
4
|
+
import { n as resolvedLifecycleSpecOf, r as finalizePluginContext } from "./Plugin-j9GYdroS.mjs";
|
|
5
|
+
import { t as HmrModuleHandler } from "./HmrModuleHandler-3sNgKHdf.mjs";
|
|
3
6
|
import { SeedcordErrorCode } from "@seedcord/errors";
|
|
4
7
|
import { SeedcordError } from "@seedcord/errors/internal";
|
|
5
|
-
import { Logger, paint } from "@seedcord/logger";
|
|
8
|
+
import { FRAMEWORK_CHANNELS, Logger, paint } from "@seedcord/logger";
|
|
6
9
|
import chalk from "chalk";
|
|
7
|
-
import {
|
|
10
|
+
import { Envapter } from "envapt";
|
|
8
11
|
import { createServer } from "node:http";
|
|
12
|
+
import { traverseDirectory } from "@seedcord/utils/node";
|
|
9
13
|
|
|
10
14
|
//#region src/node/Lifecycle/withTimeout.ts
|
|
11
15
|
async function raceTimer(work, timeoutMs, onTimeout) {
|
|
@@ -36,16 +40,15 @@ function settleWithin(work, timeoutMs) {
|
|
|
36
40
|
*
|
|
37
41
|
* @internal
|
|
38
42
|
*/
|
|
39
|
-
var CoordinatedLifecycle = class
|
|
43
|
+
var CoordinatedLifecycle = class {
|
|
40
44
|
phaseOrder;
|
|
41
45
|
phaseEnum;
|
|
42
46
|
logger;
|
|
43
47
|
tasksMap = /* @__PURE__ */ new Map();
|
|
44
48
|
constructor(loggerName, phaseOrder, phaseEnum) {
|
|
45
|
-
super();
|
|
46
49
|
this.phaseOrder = phaseOrder;
|
|
47
50
|
this.phaseEnum = phaseEnum;
|
|
48
|
-
this.logger = new Logger(loggerName);
|
|
51
|
+
this.logger = new Logger(loggerName, { channel: "lifecycle" });
|
|
49
52
|
this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
@@ -92,11 +95,9 @@ var CoordinatedLifecycle = class extends TypedEventEmitter {
|
|
|
92
95
|
return;
|
|
93
96
|
}
|
|
94
97
|
this.logger.info(`${chalk.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk.bold.magenta(this.phaseEnum[phase])} with ${chalk.bold.cyan(tasks.length)} tasks`);
|
|
95
|
-
this.emitPhase(phase, "start");
|
|
96
98
|
const failures = (await this.executeTasksInPhase(phase, tasks)).filter((r) => r.status === "rejected").length;
|
|
97
99
|
if (failures > 0) throw new SeedcordError(SeedcordErrorCode.LifecyclePhaseFailures, [this.phaseEnum[phase], failures]);
|
|
98
100
|
this.logger.info(`Phase ${chalk.bold.magenta(this.phaseEnum[phase])} ${chalk.bold.green("completed successfully")}`);
|
|
99
|
-
this.emitPhase(phase, "complete");
|
|
100
101
|
}
|
|
101
102
|
async runTaskWithTimeout(phase, task) {
|
|
102
103
|
this.logger.info(`${chalk.italic("Starting")} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);
|
|
@@ -108,12 +109,6 @@ var CoordinatedLifecycle = class extends TypedEventEmitter {
|
|
|
108
109
|
throw error;
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
|
-
emitPhase(phase, action) {
|
|
112
|
-
this.emitSafeRaw(`phase:${phase}:${action}`);
|
|
113
|
-
}
|
|
114
|
-
onListenerError(error, event) {
|
|
115
|
-
this.logger.error(`listener for ${String(event)} threw`, error);
|
|
116
|
-
}
|
|
117
112
|
};
|
|
118
113
|
|
|
119
114
|
//#endregion
|
|
@@ -176,11 +171,18 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
176
171
|
this.onSigInt = null;
|
|
177
172
|
}
|
|
178
173
|
}
|
|
179
|
-
/** @internal run() awaits this so
|
|
174
|
+
/** @internal run() awaits this so boot finishes registering its dispose tasks first */
|
|
180
175
|
gateOnStartup(settled) {
|
|
181
176
|
this.startupGate = settled;
|
|
182
177
|
}
|
|
183
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* Adds a shutdown-phase task.
|
|
180
|
+
*
|
|
181
|
+
* @param phase - The shutdown phase to run the task in.
|
|
182
|
+
* @param taskName - A descriptive name for the task.
|
|
183
|
+
* @param task - The async function to run.
|
|
184
|
+
* @param timeoutMs - Task timeout in ms. {@default `5000` }
|
|
185
|
+
*/
|
|
184
186
|
addTask(phase, taskName, task, timeoutMs = 5e3) {
|
|
185
187
|
super.addTask(phase, taskName, task, timeoutMs);
|
|
186
188
|
}
|
|
@@ -192,8 +194,8 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
192
194
|
* Runs registered tasks across shutdown phases in `ShutdownPhase` order.
|
|
193
195
|
* Tasks within each phase run in parallel.
|
|
194
196
|
*
|
|
195
|
-
* @param exitCode - Process exit code. {@default `0`}
|
|
196
|
-
* @param exitProcess - Whether to exit the process after shutdown. {@default `true`}
|
|
197
|
+
* @param exitCode - Process exit code. {@default `0` }
|
|
198
|
+
* @param exitProcess - Whether to exit the process after shutdown. {@default `true` }
|
|
197
199
|
* @example
|
|
198
200
|
* ```typescript
|
|
199
201
|
* shutdown.addTask(ShutdownPhase.Disconnect, 'database', () => db.disconnect(), 5000);
|
|
@@ -209,7 +211,6 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
209
211
|
this.isShuttingDown = true;
|
|
210
212
|
this.exitCode = exitCode;
|
|
211
213
|
this.logger.info(`${chalk.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk.bold.cyan(exitCode)}`);
|
|
212
|
-
this.emitSafe("shutdown:start");
|
|
213
214
|
try {
|
|
214
215
|
if (this.startupGate) await this.startupGate;
|
|
215
216
|
const failures = [];
|
|
@@ -218,11 +219,8 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
218
219
|
} catch (error) {
|
|
219
220
|
failures.push(error);
|
|
220
221
|
}
|
|
221
|
-
if (failures.length > 0) this.
|
|
222
|
-
else {
|
|
223
|
-
this.logger.info(`${chalk.bold.green("Coordinated shutdown completed")} successfully`);
|
|
224
|
-
this.emitSafe("shutdown:complete");
|
|
225
|
-
}
|
|
222
|
+
if (failures.length > 0) this.logger.error(`${chalk.bold.red("Coordinated shutdown failed")}`, ...failures);
|
|
223
|
+
else this.logger.info(`${chalk.bold.green("Coordinated shutdown completed")} successfully`);
|
|
226
224
|
} finally {
|
|
227
225
|
this.hasShutdown = true;
|
|
228
226
|
if (exitProcess) {
|
|
@@ -236,11 +234,6 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
236
234
|
}
|
|
237
235
|
}
|
|
238
236
|
}
|
|
239
|
-
emitFailures(failures) {
|
|
240
|
-
this.logger.error(`${chalk.bold.red("Coordinated shutdown failed")}`);
|
|
241
|
-
const error = failures.length === 1 ? failures[0] : new AggregateError(failures, "shutdown phase failures");
|
|
242
|
-
this.emitSafe("shutdown:error", error);
|
|
243
|
-
}
|
|
244
237
|
};
|
|
245
238
|
|
|
246
239
|
//#endregion
|
|
@@ -259,7 +252,14 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
259
252
|
constructor() {
|
|
260
253
|
super("Startup", PHASE_ORDER, StartupPhase);
|
|
261
254
|
}
|
|
262
|
-
/**
|
|
255
|
+
/**
|
|
256
|
+
* Adds a startup-phase task.
|
|
257
|
+
*
|
|
258
|
+
* @param phase - The startup phase to run the task in.
|
|
259
|
+
* @param taskName - A descriptive name for the task.
|
|
260
|
+
* @param task - The async function to run.
|
|
261
|
+
* @param timeoutMs - Task timeout in ms. {@default `10000` }
|
|
262
|
+
*/
|
|
263
263
|
addTask(phase, taskName, task, timeoutMs = 1e4) {
|
|
264
264
|
super.addTask(phase, taskName, task, timeoutMs);
|
|
265
265
|
}
|
|
@@ -296,7 +296,6 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
296
296
|
}
|
|
297
297
|
this.isStartingUp = true;
|
|
298
298
|
this.logger.info(`${chalk.bold.green("Starting")} coordinated startup sequence`);
|
|
299
|
-
this.emitSafe("startup:start");
|
|
300
299
|
try {
|
|
301
300
|
for (const phase of PHASE_ORDER) {
|
|
302
301
|
if (!this.isStartingUp) {
|
|
@@ -307,14 +306,12 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
307
306
|
}
|
|
308
307
|
this.hasStarted = true;
|
|
309
308
|
this.logger.info(`${chalk.bold.green("Coordinated startup completed")} successfully`);
|
|
310
|
-
this.emitSafe("startup:complete");
|
|
311
309
|
} catch (error) {
|
|
312
310
|
if (!this.isStartingUp) {
|
|
313
311
|
this.logger.warn("Startup sequence aborted during error handling");
|
|
314
312
|
return;
|
|
315
313
|
}
|
|
316
314
|
this.logger.error(`${chalk.bold.red("Coordinated startup failed")}`);
|
|
317
|
-
this.emitSafe("startup:error", error);
|
|
318
315
|
throw error;
|
|
319
316
|
} finally {
|
|
320
317
|
this.isStartingUp = false;
|
|
@@ -358,7 +355,7 @@ const DEFAULT_HEALTH_CHECK_PATH = "/health";
|
|
|
358
355
|
*/
|
|
359
356
|
var HealthCheck = class HealthCheck {
|
|
360
357
|
/** @internal */
|
|
361
|
-
logger = new Logger("HealthCheck");
|
|
358
|
+
logger = new Logger("HealthCheck", { channel: "health" });
|
|
362
359
|
port;
|
|
363
360
|
path;
|
|
364
361
|
host;
|
|
@@ -403,7 +400,7 @@ var HealthCheck = class HealthCheck {
|
|
|
403
400
|
server.removeListener("error", onListenError);
|
|
404
401
|
server.on("error", (err) => this.logger.error("Health check server error", err));
|
|
405
402
|
const address = this.host ?? "localhost";
|
|
406
|
-
this.logger.info(`${paint.mint.bold("
|
|
403
|
+
this.logger.info(`${paint.mint.bold("✔︎")} Health check server listening on ${paint.sky(`http://${address}:${this.port}${this.path}`)}`);
|
|
407
404
|
resolve();
|
|
408
405
|
});
|
|
409
406
|
if (this.host) {
|
|
@@ -437,19 +434,22 @@ var HealthCheck = class HealthCheck {
|
|
|
437
434
|
|
|
438
435
|
//#endregion
|
|
439
436
|
//#region src/node/Pluggable.ts
|
|
437
|
+
const RESERVED_KEYS = new Set(FRAMEWORK_CHANNELS);
|
|
440
438
|
/**
|
|
441
439
|
* Base class for objects that can have plugins attached.
|
|
442
440
|
*
|
|
443
441
|
* Plugins are attached during configuration and initialized during startup, sequentially in attach
|
|
444
442
|
* order within each phase. Not constructed directly, the host is a transport `Seedcord` class.
|
|
445
443
|
*/
|
|
446
|
-
var Pluggable = class Pluggable
|
|
447
|
-
|
|
448
|
-
startFailed = false;
|
|
444
|
+
var Pluggable = class Pluggable {
|
|
445
|
+
/** @see {@link CoordinatedShutdown} */
|
|
449
446
|
shutdown;
|
|
447
|
+
/** @see {@link CoordinatedStartup} */
|
|
450
448
|
startup;
|
|
449
|
+
isInitialized = false;
|
|
450
|
+
startFailed = false;
|
|
451
451
|
plugins = [];
|
|
452
|
-
pluginLogger = new Logger("Plugins");
|
|
452
|
+
pluginLogger = new Logger("Plugins", { channel: "plugins" });
|
|
453
453
|
attachments = [];
|
|
454
454
|
completedInits = /* @__PURE__ */ new Set();
|
|
455
455
|
disposePhases = /* @__PURE__ */ new Set();
|
|
@@ -461,7 +461,6 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
461
461
|
shutdown.removeSignalHandlers();
|
|
462
462
|
throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
|
|
463
463
|
}
|
|
464
|
-
super();
|
|
465
464
|
Pluggable.isInstantiated = true;
|
|
466
465
|
Pluggable.liveShutdown = shutdown;
|
|
467
466
|
this.shutdown = shutdown;
|
|
@@ -491,6 +490,10 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
491
490
|
pluginStore() {
|
|
492
491
|
return this.config.store ?? this.rateLimiter;
|
|
493
492
|
}
|
|
493
|
+
/** @internal codegen reads these to emit the `Core` augmentation */
|
|
494
|
+
get pluginKeys() {
|
|
495
|
+
return this.attachments.map((attachment) => attachment.key);
|
|
496
|
+
}
|
|
494
497
|
/** @internal releases the signal handlers and clears the singleton so the next host can construct */
|
|
495
498
|
static reset() {
|
|
496
499
|
Pluggable.liveShutdown?.removeSignalHandlers();
|
|
@@ -501,8 +504,12 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
501
504
|
* Attaches a plugin to this instance.
|
|
502
505
|
*
|
|
503
506
|
* The plugin initializes during startup, after every plugin attached before it, and becomes
|
|
504
|
-
* available as a property on `core`.
|
|
505
|
-
*
|
|
507
|
+
* available as a property on `core`. `seedcord codegen` writes the `Core` augmentation that
|
|
508
|
+
* types it.
|
|
509
|
+
*
|
|
510
|
+
* A plugin declaring a `transport` or `runtime` this host does not run is a compile error on
|
|
511
|
+
* this call. An edge host takes no plugins at all. A plugin constructor narrowing its first
|
|
512
|
+
* parameter past `CoreBase` is a compile error here too. Read the transport `Core` off `this.core`.
|
|
506
513
|
*
|
|
507
514
|
* @typeParam Key - The property name for accessing the plugin
|
|
508
515
|
* @typeParam Ctor - The plugin constructor type
|
|
@@ -510,14 +517,15 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
510
517
|
* @param Plugin - Plugin constructor class
|
|
511
518
|
* @param args - Additional arguments to pass to the plugin constructor
|
|
512
519
|
* @returns This instance with the plugin attached as a typed property
|
|
513
|
-
* @throws A **SeedcordError** When called after initialization or if key already exists
|
|
520
|
+
* @throws A **SeedcordError** When called after initialization or if key already exists or is reserved
|
|
514
521
|
* @example
|
|
515
522
|
* ```typescript
|
|
516
|
-
* seedcord.attach('db',
|
|
523
|
+
* seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
|
|
517
524
|
* ```
|
|
518
525
|
*/
|
|
519
526
|
attach(key, Plugin, ...args) {
|
|
520
527
|
if (this.isInitialized) throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);
|
|
528
|
+
if (RESERVED_KEYS.has(key)) throw new SeedcordError(SeedcordErrorCode.CorePluginReservedChannel, [key]);
|
|
521
529
|
if (key in this) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);
|
|
522
530
|
const instance = new Plugin(this, ...args);
|
|
523
531
|
this.plugins.push(instance);
|
|
@@ -531,8 +539,8 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
531
539
|
finalizeContext(key, instance) {
|
|
532
540
|
const caps = this.pluginCapabilities();
|
|
533
541
|
const readToken = () => this.pluginCapabilities().token;
|
|
542
|
+
instance.logger.setChannel(key);
|
|
534
543
|
finalizePluginContext(instance, {
|
|
535
|
-
logger: new Logger(key),
|
|
536
544
|
config: this.config,
|
|
537
545
|
store: this.pluginStore(),
|
|
538
546
|
client: caps.client,
|
|
@@ -555,7 +563,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
555
563
|
for (const [phase, group] of groups) {
|
|
556
564
|
if (phase === 3) continue;
|
|
557
565
|
const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);
|
|
558
|
-
this.startup.addTask(phase, "
|
|
566
|
+
this.startup.addTask(phase, "plugins-init", () => this.runInits(group), budget);
|
|
559
567
|
}
|
|
560
568
|
this.registerReadyTask(groups.get(3) ?? []);
|
|
561
569
|
}
|
|
@@ -564,7 +572,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
564
572
|
const { key, instance } = attachment;
|
|
565
573
|
const spec = resolvedLifecycleSpecOf(instance);
|
|
566
574
|
instance.logger.utils.initialization(key, "start");
|
|
567
|
-
await withTimeout(`Plugin
|
|
575
|
+
await withTimeout(`Plugin (${key})`, () => instance.init(), spec.init.timeout);
|
|
568
576
|
instance.logger.utils.initialization(key, "end");
|
|
569
577
|
this.completedInits.add(attachment);
|
|
570
578
|
if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);
|
|
@@ -582,7 +590,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
582
590
|
}, []);
|
|
583
591
|
if (readyInits.length === 0 && steps.length === 0) return;
|
|
584
592
|
const budget = readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) + steps.reduce((sum, step) => sum + step.timeout, 0);
|
|
585
|
-
this.startup.addTask(3, "
|
|
593
|
+
this.startup.addTask(3, "plugins-ready", async () => {
|
|
586
594
|
await this.runInits(readyInits);
|
|
587
595
|
for (const step of steps) await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);
|
|
588
596
|
}, budget);
|
|
@@ -594,7 +602,7 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
594
602
|
const spec = resolvedLifecycleSpecOf(a.instance);
|
|
595
603
|
return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;
|
|
596
604
|
}, 0);
|
|
597
|
-
this.shutdown.addTask(phase, "
|
|
605
|
+
this.shutdown.addTask(phase, "plugins-dispose", () => this.runDisposals(phase), budget);
|
|
598
606
|
}
|
|
599
607
|
async runDisposals(phase) {
|
|
600
608
|
const failures = [];
|
|
@@ -621,16 +629,78 @@ var Pluggable = class Pluggable extends TypedEventEmitter {
|
|
|
621
629
|
await this.disposeCompleted(void 0, (caught) => this.pluginLogger.warn("rollback dispose failed", caught));
|
|
622
630
|
this.completedInits.clear();
|
|
623
631
|
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/node/subscribers/SubscriberLoader.ts
|
|
636
|
+
/**
|
|
637
|
+
* Loads subscribers off the filesystem into a {@link Bus} and stores their hot-reload wiring. Node
|
|
638
|
+
* only, a bundled isolate registers from the manifest instead.
|
|
639
|
+
*
|
|
640
|
+
* @internal
|
|
641
|
+
*/
|
|
642
|
+
var SubscriberLoader = class {
|
|
643
|
+
bus;
|
|
644
|
+
directory;
|
|
645
|
+
isInitialized = false;
|
|
646
|
+
hmrHandler;
|
|
647
|
+
constructor(bus, directory) {
|
|
648
|
+
this.bus = bus;
|
|
649
|
+
this.directory = directory;
|
|
650
|
+
if (!this.directory) return;
|
|
651
|
+
if (!Envapter.isDevelopment && !Envapter.isTest) return;
|
|
652
|
+
this.hmrHandler = new HmrModuleHandler({
|
|
653
|
+
handlersDir: this.directory,
|
|
654
|
+
isHandler: this.isSubscriber.bind(this),
|
|
655
|
+
registerHandler: this.registerSubscriber.bind(this),
|
|
656
|
+
unregisterHandler: this.unregisterSubscriber.bind(this),
|
|
657
|
+
getArtifacts: this.getArtifacts.bind(this),
|
|
658
|
+
logger: this.bus.logger
|
|
659
|
+
});
|
|
660
|
+
}
|
|
624
661
|
/** @internal */
|
|
625
|
-
|
|
626
|
-
|
|
662
|
+
async init() {
|
|
663
|
+
if (this.isInitialized) return;
|
|
664
|
+
this.isInitialized = true;
|
|
665
|
+
this.bus.registerDefaults();
|
|
666
|
+
const { directory } = this;
|
|
667
|
+
if (directory) {
|
|
668
|
+
this.bus.logger.info(chalk.bold(directory));
|
|
669
|
+
await this.load(directory);
|
|
670
|
+
this.bus.logger.utils.list([`${chalk.bold.magenta(this.bus.registeredCount)} subscribers`], chalk.bold.green("Loaded"));
|
|
671
|
+
}
|
|
672
|
+
if (!Envapter.isTest) await this.bus.verifyWebhooks();
|
|
627
673
|
}
|
|
628
674
|
/** @internal */
|
|
629
|
-
|
|
630
|
-
|
|
675
|
+
async onHmr(event) {
|
|
676
|
+
if (this.hmrHandler) await this.hmrHandler.handle(event);
|
|
677
|
+
}
|
|
678
|
+
async load(dir) {
|
|
679
|
+
await traverseDirectory(dir, (fullPath, relativePath, imported) => {
|
|
680
|
+
for (const exportName of Object.keys(imported)) {
|
|
681
|
+
const value = imported[exportName];
|
|
682
|
+
if (!this.isSubscriber(value)) continue;
|
|
683
|
+
this.registerSubscriber(value);
|
|
684
|
+
this.hmrHandler?.trackHandler(fullPath, value);
|
|
685
|
+
this.bus.logger.utils.registration(value.name, relativePath);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
registerSubscriber(ctor) {
|
|
690
|
+
this.bus.register(registrationFor(ctor));
|
|
691
|
+
}
|
|
692
|
+
unregisterSubscriber(ctor, artifacts) {
|
|
693
|
+
this.bus.unregister(ctor, artifacts);
|
|
694
|
+
}
|
|
695
|
+
getArtifacts(ctor) {
|
|
696
|
+
return [Reflect.getMetadata(SubscribeMetadataKey, ctor).subscriber];
|
|
697
|
+
}
|
|
698
|
+
isSubscriber(value) {
|
|
699
|
+
if (typeof value !== "function") return false;
|
|
700
|
+
return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);
|
|
631
701
|
}
|
|
632
702
|
};
|
|
633
703
|
|
|
634
704
|
//#endregion
|
|
635
|
-
export { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, settleWithin };
|
|
705
|
+
export { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin };
|
|
636
706
|
//# sourceMappingURL=node-internal.index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-internal.index.mjs","names":["PHASE_ORDER"],"sources":["../src/node/Lifecycle/withTimeout.ts","../src/node/Lifecycle/CoordinatedLifecycle.ts","../src/node/Lifecycle/CoordinatedShutdown.ts","../src/node/Lifecycle/CoordinatedStartup.ts","../src/node/HealthCheck.ts","../src/node/Pluggable.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nasync function raceTimer(\n work: Promise<unknown>,\n timeoutMs: number,\n onTimeout: (resolve: () => void, reject: (error: unknown) => void) => void\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n work,\n new Promise<void>((resolve, reject) => {\n timer = setTimeout(() => onTimeout(resolve, reject), timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** @internal rejects with a `LifecycleTaskTimeout` when `run` outlasts the bound. */\nexport function withTimeout(name: string, run: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run(), timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n/** @internal resolves when `work` settles or the bound elapses, whichever is first. */\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(work, timeoutMs, (resolve) => resolve());\n}\n","/*\n * Inspired by Akka Coordinated Shutdown: https://doc.akka.io/libraries/akka-core/current/coordinated-shutdown.html\n * and Lewis's implementation in a private repo elsewhere (https://github.com/Yomanz)\n */\n\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { TypedEventEmitter } from '@seedcord/event-emitter';\nimport { Logger } from '@seedcord/logger';\nimport chalk from 'chalk';\n\nimport { withTimeout } from './withTimeout';\n\nimport type { LifecycleTask } from './LifecycleTypes';\nimport type { EventMap } from '@seedcord/event-emitter';\n\n/**\n * Base for the startup and shutdown coordinators. Runs phase-ordered tasks off a per-phase map.\n *\n * @internal\n */\nexport abstract class CoordinatedLifecycle<\n TPhase extends number,\n TEvents extends EventMap<TEvents>\n> extends TypedEventEmitter<TEvents> {\n protected readonly logger: Logger;\n protected readonly tasksMap = new Map<TPhase, LifecycleTask[]>();\n\n protected constructor(\n loggerName: string,\n protected readonly phaseOrder: TPhase[],\n protected readonly phaseEnum: Record<number, string>\n ) {\n super();\n this.logger = new Logger(loggerName);\n this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));\n }\n\n /**\n * Adds a task to `phase`. Tasks run in phase order, each bounded by `timeoutMs`.\n *\n * @param phase - The lifecycle phase to add the task to\n * @param taskName - Unique name for the task, used for logging and removal\n * @param task - Async function to execute during the phase\n * @param timeoutMs - Maximum time allowed for task execution in milliseconds\n */\n public addTask(phase: TPhase, taskName: string, task: () => Promise<void>, timeoutMs: number): void {\n if (!this.canAddTask()) return;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleUnknownPhase, [phase]);\n }\n\n tasks.push({ name: taskName, task, timeout: timeoutMs });\n this.logger.debug(\n `${chalk.italic('Added')} ${this.getTaskType()} task ${chalk.bold.cyan(taskName)} to phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n }\n\n /**\n * Removes a lifecycle task from a specific phase.\n *\n * @param phase - The lifecycle phase to remove the task from\n * @param taskName - Name of the task to remove\n * @returns True if the task was found and removed, false otherwise\n */\n public removeTask(phase: TPhase, taskName: string): boolean {\n if (!this.canRemoveTask()) return false;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) return false;\n\n const initialLength = tasks.length;\n const filteredTasks = tasks.filter((task) => task.name !== taskName);\n this.tasksMap.set(phase, filteredTasks);\n\n const removed = initialLength !== filteredTasks.length;\n if (removed) {\n this.logger.debug(\n `${chalk.italic('Removed')} ${this.getTaskType()} task ${chalk.bold.cyan(taskName)} from phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n }\n\n return removed;\n }\n\n protected async runPhase(phase: TPhase): Promise<void> {\n const tasks = this.tasksMap.get(phase) ?? [];\n if (tasks.length === 0) {\n this.logger.debug(`No tasks to run in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);\n return;\n }\n\n this.logger.info(\n `${chalk.bold.yellow('Running')} ${this.getTaskType()} phase ${chalk.bold.magenta(this.phaseEnum[phase])} with ${chalk.bold.cyan(tasks.length)} tasks`\n );\n this.emitPhase(phase, 'start');\n\n const results: PromiseSettledResult<void>[] = await this.executeTasksInPhase(phase, tasks);\n\n const failures = results.filter((r) => r.status === 'rejected').length;\n if (failures > 0) {\n // raw phase name here. chalk's ANSI codes would otherwise leak into the serialized\n // error message (e.g. the unknown-exception webhook payload).\n throw new SeedcordError(SeedcordErrorCode.LifecyclePhaseFailures, [this.phaseEnum[phase], failures]);\n }\n\n this.logger.info(\n `Phase ${chalk.bold.magenta(this.phaseEnum[phase])} ${chalk.bold.green('completed successfully')}`\n );\n\n this.emitPhase(phase, 'complete');\n }\n\n protected async runTaskWithTimeout(phase: TPhase, task: LifecycleTask): Promise<void> {\n this.logger.info(\n `${chalk.italic('Starting')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n\n try {\n await withTimeout(task.name, task.task, task.timeout);\n\n this.logger.info(\n `${chalk.italic('Completed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n } catch (error) {\n this.logger.error(\n `${chalk.italic('Failed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}:`,\n error\n );\n throw error;\n }\n }\n\n // TS can't correlate a template-literal key with the generic TEvents\n private emitPhase(phase: TPhase, action: 'start' | 'complete'): void {\n this.emitSafeRaw(`phase:${phase}:${action}`);\n }\n\n protected override onListenerError(error: unknown, event: string | symbol): void {\n this.logger.error(`listener for ${String(event)} threw`, error);\n }\n\n protected abstract canAddTask(): boolean;\n protected abstract canRemoveTask(): boolean;\n protected abstract getTaskType(): string;\n protected abstract executeTasksInPhase(\n phase: TPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]>;\n}\n","import chalk from 'chalk';\n\nimport { ShutdownPhase } from '@src/lifecycle/phases';\n\nimport { CoordinatedLifecycle } from './CoordinatedLifecycle';\n\nimport type { LifecycleTask, PhaseEventMap } from './LifecycleTypes';\nimport type { UnionToTuple } from 'type-fest';\n\nconst PHASE_ORDER: ShutdownPhase[] = [\n ShutdownPhase.Unbind,\n ShutdownPhase.Drain,\n ShutdownPhase.Disconnect,\n ShutdownPhase.Logout\n];\n\ntype CoordinatedShutdownEvents = PhaseEventMap<'shutdown', UnionToTuple<ShutdownPhase>>;\n\n// gives the logger's file sink time to flush before process.exit\nconst LOG_FLUSH_DELAY_MS = 3000;\n\n/**\n * Runs registered shutdown tasks across `ShutdownPhase` in order. Registers SIGINT and SIGTERM\n * handlers. Tasks within a phase run in parallel.\n */\nexport class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase, CoordinatedShutdownEvents> {\n private isShuttingDown = false;\n private hasShutdown = false;\n private exitCode = 0;\n private onSigTerm: (() => void) | null = null;\n private onSigInt: (() => void) | null = null;\n private startupGate?: Promise<void>;\n\n public constructor() {\n super('Shutdown', PHASE_ORDER, ShutdownPhase);\n\n this.registerSignalHandlers();\n }\n\n protected canAddTask(): boolean {\n return true;\n }\n\n protected canRemoveTask(): boolean {\n return true;\n }\n\n protected getTaskType(): string {\n return 'shutdown';\n }\n\n protected async executeTasksInPhase(\n phase: ShutdownPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n private registerSignalHandlers(): void {\n this.onSigTerm = () => {\n this.logger.info(`Received ${chalk.yellow.bold('SIGTERM')} signal`);\n void this.run(0);\n };\n\n this.onSigInt = () => {\n this.logger.info(`Received ${chalk.yellow.bold('SIGINT')} signal`);\n void this.run(0);\n };\n\n process.on('SIGTERM', this.onSigTerm);\n process.on('SIGINT', this.onSigInt);\n }\n\n /** @internal */\n public removeSignalHandlers(): void {\n if (this.onSigTerm) {\n process.off('SIGTERM', this.onSigTerm);\n this.onSigTerm = null;\n }\n if (this.onSigInt) {\n process.off('SIGINT', this.onSigInt);\n this.onSigInt = null;\n }\n }\n\n /** @internal run() awaits this so a shutdown during startup waits for startup to register its dispose tasks */\n public gateOnStartup(settled: Promise<void>): void {\n this.startupGate = settled;\n }\n\n /** Adds a shutdown-phase task. @param timeoutMs - Task timeout in ms. {@default 5000} */\n public override addTask(phase: ShutdownPhase, taskName: string, task: () => Promise<void>, timeoutMs = 5000): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n /** Removes a shutdown-phase task, returning whether one was removed. */\n public override removeTask(phase: ShutdownPhase, taskName: string): boolean {\n return super.removeTask(phase, taskName);\n }\n\n /**\n * Runs registered tasks across shutdown phases in `ShutdownPhase` order.\n * Tasks within each phase run in parallel.\n *\n * @param exitCode - Process exit code. {@default `0`}\n * @param exitProcess - Whether to exit the process after shutdown. {@default `true`}\n * @example\n * ```typescript\n * shutdown.addTask(ShutdownPhase.Disconnect, 'database', () => db.disconnect(), 5000);\n * await shutdown.run(0);\n * ```\n */\n public async run(exitCode = 0, exitProcess = true): Promise<void> {\n this.removeSignalHandlers();\n\n // a dev-mode run leaves the process alive, so guard against a re-run that re-executes tasks\n if (this.hasShutdown || this.isShuttingDown) {\n this.logger.warn('Shutdown sequence already ran or is in progress');\n return;\n }\n\n this.isShuttingDown = true;\n this.exitCode = exitCode;\n this.logger.info(\n `${chalk.bold.yellow('Starting')} coordinated shutdown with exit code ${chalk.bold.cyan(exitCode)}`\n );\n this.emitSafe('shutdown:start');\n\n try {\n if (this.startupGate) await this.startupGate;\n const failures: unknown[] = [];\n for (const phase of PHASE_ORDER) {\n // run every phase so a mid-shutdown failure still attempts the later teardowns\n try {\n await this.runPhase(phase);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) this.emitFailures(failures);\n else {\n this.logger.info(`${chalk.bold.green('Coordinated shutdown completed')} successfully`);\n this.emitSafe('shutdown:complete');\n }\n } finally {\n this.hasShutdown = true;\n if (exitProcess) {\n this.logger.info(`${chalk.bold.red('Exiting')} process with code ${chalk.bold.cyan(this.exitCode)}`);\n setTimeout(() => {\n process.exit(this.exitCode);\n }, LOG_FLUSH_DELAY_MS);\n } else {\n this.logger.info(`${chalk.bold.yellow('Skipping')} process exit (dev mode)`);\n this.isShuttingDown = false;\n }\n }\n }\n\n private emitFailures(failures: unknown[]): void {\n this.logger.error(`${chalk.bold.red('Coordinated shutdown failed')}`);\n const error = failures.length === 1 ? failures[0] : new AggregateError(failures, 'shutdown phase failures');\n this.emitSafe('shutdown:error', error);\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport chalk from 'chalk';\n\nimport { StartupPhase } from '@src/lifecycle/phases';\n\nimport { CoordinatedLifecycle } from './CoordinatedLifecycle';\nimport { withTimeout } from './withTimeout';\n\nimport type { LifecycleTask, PhaseEventMap } from './LifecycleTypes';\nimport type { UnionToTuple } from 'type-fest';\n\nconst PHASE_ORDER: StartupPhase[] = [StartupPhase.Configuration, StartupPhase.Login, StartupPhase.Ready];\n\ntype CoordinatedStartupEvents = PhaseEventMap<'startup', UnionToTuple<StartupPhase>>;\n\n/**\n * Runs registered startup tasks across `StartupPhase` in order. Tasks within a phase run in parallel.\n */\nexport class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase, CoordinatedStartupEvents> {\n private isStartingUp = false;\n private hasStarted = false;\n\n public constructor() {\n super('Startup', PHASE_ORDER, StartupPhase);\n }\n\n /** Adds a startup-phase task. @param timeoutMs - Task timeout in ms. {@default 10000} */\n public override addTask(\n phase: StartupPhase,\n taskName: string,\n task: () => Promise<void>,\n timeoutMs = 10_000\n ): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n protected canAddTask(): boolean {\n if (this.hasStarted) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleAddAfterCompletion);\n }\n\n if (this.isStartingUp) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleAddDuringRun);\n }\n\n return true;\n }\n\n protected canRemoveTask(): boolean {\n if (this.isStartingUp) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleRemoveDuringRun);\n }\n\n return true;\n }\n\n protected getTaskType(): string {\n return 'startup';\n }\n\n protected async executeTasksInPhase(\n phase: StartupPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n /**\n * Runs the registered tasks across startup phases in `StartupPhase` order. Each phase completes\n * before the next begins, and tasks within a phase run concurrently.\n *\n * @throws {@link SeedcordError} if a startup task throws.\n */\n public async run(): Promise<void> {\n if (this.hasStarted) {\n this.logger.warn('Startup sequence has already completed');\n return;\n }\n\n if (this.isStartingUp) {\n this.logger.warn('Startup sequence already in progress');\n return;\n }\n\n this.isStartingUp = true;\n this.logger.info(`${chalk.bold.green('Starting')} coordinated startup sequence`);\n this.emitSafe('startup:start');\n\n try {\n for (const phase of PHASE_ORDER) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- abort() can flip isStartingUp to false mid-loop from the cli\n if (!this.isStartingUp) {\n this.logger.warn('Startup sequence aborted');\n return;\n }\n await this.runPhase(phase);\n }\n\n this.hasStarted = true;\n this.logger.info(`${chalk.bold.green('Coordinated startup completed')} successfully`);\n this.emitSafe('startup:complete');\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- abort() can flip isStartingUp to false before this catch runs\n if (!this.isStartingUp) {\n this.logger.warn('Startup sequence aborted during error handling');\n return;\n }\n this.logger.error(`${chalk.bold.red('Coordinated startup failed')}`);\n this.emitSafe('startup:error', error);\n throw error;\n } finally {\n this.isStartingUp = false;\n }\n }\n\n protected override async runTaskWithTimeout(phase: StartupPhase, task: LifecycleTask): Promise<void> {\n this.logger.info(\n `${chalk.italic('Starting')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(StartupPhase[phase])}`\n );\n\n try {\n await withTimeout(task.name, task.task, task.timeout);\n\n this.logger.info(\n `${chalk.italic('Completed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(StartupPhase[phase])}`\n );\n } catch (error) {\n if (!this.isStartingUp) {\n return;\n }\n\n this.logger.error(\n `${chalk.italic('Failed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(StartupPhase[phase])}:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * Aborts the startup sequence if it is currently running.\n */\n public abort(): void {\n if (!this.isStartingUp) return;\n\n this.isStartingUp = false;\n this.logger.warn('Aborting coordinated startup sequence');\n }\n\n public get isReady(): boolean {\n return this.hasStarted;\n }\n\n public get isRunning(): boolean {\n return this.isStartingUp;\n }\n}\n","import { createServer } from 'node:http';\n\nimport { Logger, paint } from '@seedcord/logger';\n\nimport { ShutdownPhase } from '@src/lifecycle/phases';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\n\n/**\n * HTTP server answering `GET` on the configured path with a JSON `{ status, timestamp }` body.\n */\nexport class HealthCheck {\n /** @internal */\n public readonly logger = new Logger('HealthCheck');\n\n public readonly port: number;\n public readonly path: string;\n public readonly host: string | undefined;\n\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this.port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this.path = options?.path ?? DEFAULT_HEALTH_CHECK_PATH;\n this.host = options?.host;\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n /**\n * Resolves the `healthCheck` option. `undefined` and `true` build the defaults, an object uses\n * its config, `false` returns `undefined`.\n */\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n if (option === false) return undefined;\n return new HealthCheck(shutdown, typeof option === 'object' ? option : undefined);\n }\n\n /**\n * Starts the health check server.\n * @returns Promise that resolves when the server is listening\n * @internal\n */\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (req.method === 'GET' && req.url === this.path) {\n res.writeHead(HTTP_OK, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));\n } else {\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n }\n });\n this.server = server;\n\n const onListenError = (err: Error): void => reject(err);\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // Swap the listen-time reject handler for a logging one: keeping it would reject an\n // already-settled promise on a late error, and removing it without a replacement\n // would crash the process on an unhandled 'error' event.\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // the server binds all interfaces, the log shows an address a browser can open\n const address = this.host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold('✓')} Health check server listening on ${paint.sky(`http://${address}:${this.port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this.host) {\n this.logger.debug(`Binding health check server to ${this.host}`);\n server.listen(this.port, this.host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this.port);\n }\n });\n }\n\n /**\n * Stops the health check server.\n * @internal\n */\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { TypedEventEmitter } from '@seedcord/event-emitter';\nimport { Logger } from '@seedcord/logger';\n\nimport { StartupPhase } from '@src/lifecycle/phases';\nimport { finalizePluginContext } from '@src/plugin/context';\nimport { resolvedLifecycleSpecOf } from '@src/plugin/Plugin';\n\nimport { withTimeout } from './Lifecycle/withTimeout';\n\nimport type { CoreBase } from '@interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '@node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '@node/Lifecycle/CoordinatedStartup';\nimport type { EventMap, NoEvents } from '@seedcord/event-emitter';\nimport type { Config, IRateLimiter, Store } from '@seedcord/types';\nimport type { ShutdownPhase } from '@src/lifecycle/phases';\nimport type { PluginCapabilities, PluginContext, StoredPluginContext } from '@src/plugin/context';\nimport type { PluginArgs, PluginCtor, Plugin } from '@src/plugin/Plugin';\n\ninterface Attachment {\n readonly key: string;\n readonly instance: Plugin;\n}\n\n/**\n * Base class for objects that can have plugins attached.\n *\n * Plugins are attached during configuration and initialized during startup, sequentially in attach\n * order within each phase. Not constructed directly, the host is a transport `Seedcord` class.\n */\nexport abstract class Pluggable<TPluggableEvents extends EventMap<TPluggableEvents> = NoEvents>\n extends TypedEventEmitter<TPluggableEvents>\n implements CoreBase\n{\n public abstract readonly config: Config;\n public abstract readonly rateLimiter: IRateLimiter;\n\n protected isInitialized = false;\n protected startFailed = false;\n protected readonly shutdown: CoordinatedShutdown;\n protected readonly startup: CoordinatedStartup;\n protected readonly plugins: Plugin[] = [];\n\n private readonly pluginLogger = new Logger('Plugins');\n\n private readonly attachments: Attachment[] = [];\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private pluginTasksRegistered = false;\n\n private static isInstantiated = false;\n private static liveShutdown?: CoordinatedShutdown | undefined;\n\n constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n if (Pluggable.isInstantiated) {\n // the caller constructed this shutdown, release its fresh handler pair before rejecting\n shutdown.removeSignalHandlers();\n throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n }\n\n super();\n Pluggable.isInstantiated = true;\n Pluggable.liveShutdown = shutdown;\n this.shutdown = shutdown;\n this.startup = startup;\n }\n\n /** @internal */\n protected async init(): Promise<this> {\n if (this.isInitialized) return this;\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n this.registerPluginTasks();\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this.shutdown.gateOnStartup(startupSettled.promise);\n\n try {\n await this.startup.run();\n } catch (caught) {\n await this.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n this.isInitialized = true;\n return this;\n }\n\n // transports override to inject client/token/rest\n protected pluginCapabilities(): PluginCapabilities {\n return {};\n }\n\n protected pluginStore(): Store<'charge'> {\n // justified: both assignment branches of rateLimiter are Store<'charge'> at the hosts\n return this.config.store ?? (this.rateLimiter as Store<'charge'>);\n }\n\n /** @internal releases the signal handlers and clears the singleton so the next host can construct */\n protected static reset(): void {\n Pluggable.liveShutdown?.removeSignalHandlers();\n Pluggable.liveShutdown = undefined;\n Pluggable.isInstantiated = false;\n }\n\n /**\n * Attaches a plugin to this instance.\n *\n * The plugin initializes during startup, after every plugin attached before it, and becomes\n * available as a property on `core`. Augment the transport's `Core` interface with the plugin\n * type for intellisense.\n *\n * @typeParam Key - The property name for accessing the plugin\n * @typeParam Ctor - The plugin constructor type\n * @param key - Property name to access the plugin instance\n * @param Plugin - Plugin constructor class\n * @param args - Additional arguments to pass to the plugin constructor\n * @returns This instance with the plugin attached as a typed property\n * @throws A **SeedcordError** When called after initialization or if key already exists\n * @example\n * ```typescript\n * seedcord.attach('db', Mongo, { uri: 'mongodb://...', name: 'seedcord', dir: ... })\n * ```\n */\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: Key,\n Plugin: Ctor,\n ...args: PluginArgs<Ctor>\n ): this & Record<Key, InstanceType<Ctor>> {\n if (this.isInitialized) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n if (key in this) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n const instance = new Plugin(this, ...args);\n this.plugins.push(instance);\n this.attachments.push({ key, instance });\n this.finalizeContext(key, instance);\n\n return Object.assign(this, { [key]: instance } as Record<Key, InstanceType<Ctor>>);\n }\n\n private finalizeContext(key: string, instance: Plugin): void {\n const caps = this.pluginCapabilities();\n const readToken = (): string | undefined => this.pluginCapabilities().token;\n const ctx: StoredPluginContext = {\n logger: new Logger(key),\n config: this.config,\n store: this.pluginStore(),\n client: caps.client,\n // http sets the token during Ready, read it live\n get token(): string | undefined {\n return readToken();\n },\n rest: caps.rest\n };\n finalizePluginContext(instance, ctx as PluginContext);\n }\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n private registerPluginTasks(): void {\n if (this.pluginTasksRegistered) return;\n this.pluginTasksRegistered = true;\n\n const groups = new Map<StartupPhase, Attachment[]>();\n for (const attachment of this.attachments) {\n const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;\n const group = groups.get(phase) ?? [];\n group.push(attachment);\n groups.set(phase, group);\n }\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);\n this.startup.addTask(phase, 'Plugins:init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance } = attachment;\n const spec = resolvedLifecycleSpecOf(instance);\n\n instance.logger.utils.initialization(key, 'start');\n await withTimeout(`Plugin:${key}`, () => instance.init(), spec.init.timeout);\n instance.logger.utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<{ key: string; run: () => Promise<void>; timeout: number }[]>(\n (acc, a) => {\n const ready = a.instance.ready?.bind(a.instance);\n if (ready) {\n acc.push({\n key: a.key,\n run: ready,\n timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout\n });\n }\n return acc;\n },\n []\n );\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'Plugins',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: ShutdownPhase): void {\n if (this.disposePhases.has(phase)) return;\n this.disposePhases.add(phase);\n\n // registered once per phase, later inits of that phase run in the same task\n const budget = this.attachments.reduce((sum, a) => {\n const spec = resolvedLifecycleSpecOf(a.instance);\n return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;\n }, 0);\n this.shutdown.addTask(phase, 'Plugins:dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n // keep disposing the rest even when one fails\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, 'plugin dispose failures');\n }\n\n // reverse attach order\n private async disposeCompleted(\n phase: ShutdownPhase | undefined,\n onError: (caught: unknown) => void\n ): Promise<void> {\n for (const attachment of [...this.attachments].reverse()) {\n if (!this.completedInits.has(attachment)) continue;\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n if (!dispose) continue;\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n\n private async rollback(): Promise<void> {\n this.startFailed = true;\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.pluginLogger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n\n /** @internal */\n override removeListener<TEventKey extends Extract<keyof TPluggableEvents, string | symbol>>(\n event: TEventKey,\n listener: (...args: TPluggableEvents[TEventKey]) => void\n ): this {\n return super.removeListener(event, listener);\n }\n\n /** @internal */\n override removeAllListeners(event?: Extract<keyof TPluggableEvents, string | symbol>): this {\n return super.removeAllListeners(event);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node-internal.index.mjs","names":["PHASE_ORDER"],"sources":["../src/node/Lifecycle/withTimeout.ts","../src/node/Lifecycle/CoordinatedLifecycle.ts","../src/node/Lifecycle/CoordinatedShutdown.ts","../src/node/Lifecycle/CoordinatedStartup.ts","../src/node/HealthCheck.ts","../src/node/Pluggable.ts","../src/node/subscribers/SubscriberLoader.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nasync function raceTimer(\n work: Promise<unknown>,\n timeoutMs: number,\n onTimeout: (resolve: () => void, reject: (error: unknown) => void) => void\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n work,\n new Promise<void>((resolve, reject) => {\n timer = setTimeout(() => onTimeout(resolve, reject), timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/** @internal rejects with a `LifecycleTaskTimeout` when `run` outlasts the bound. */\nexport function withTimeout(name: string, run: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run(), timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n/** @internal resolves when `work` settles or the bound elapses, whichever is first. */\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(work, timeoutMs, (resolve) => resolve());\n}\n","/*\n * Inspired by Akka Coordinated Shutdown: https://doc.akka.io/libraries/akka-core/current/coordinated-shutdown.html\n * and Lewis's implementation in a private repo elsewhere (https://github.com/Yomanz)\n */\n\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport chalk from 'chalk';\n\nimport { withTimeout } from './withTimeout';\n\nimport type { LifecycleTask } from './LifecycleTypes';\n\n/**\n * Base for the startup and shutdown coordinators. Runs phase-ordered tasks off a per-phase map.\n *\n * @internal\n */\nexport abstract class CoordinatedLifecycle<TPhase extends number> {\n protected readonly logger: Logger;\n protected readonly tasksMap = new Map<TPhase, LifecycleTask[]>();\n\n protected constructor(\n loggerName: string,\n protected readonly phaseOrder: TPhase[],\n protected readonly phaseEnum: Record<number, string>\n ) {\n this.logger = new Logger(loggerName, { channel: 'lifecycle' });\n this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));\n }\n\n /**\n * Adds a task to `phase`. Tasks run in phase order, each bounded by `timeoutMs`.\n *\n * @param phase - The lifecycle phase to add the task to\n * @param taskName - Unique name for the task, used for logging and removal\n * @param task - Async function to execute during the phase\n * @param timeoutMs - Maximum time allowed for task execution in milliseconds\n */\n public addTask(phase: TPhase, taskName: string, task: () => Promise<void>, timeoutMs: number): void {\n if (!this.canAddTask()) return;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleUnknownPhase, [phase]);\n }\n\n tasks.push({ name: taskName, task, timeout: timeoutMs });\n this.logger.debug(\n `${chalk.italic('Added')} ${this.getTaskType()} task ${chalk.bold.cyan(taskName)} to phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n }\n\n /**\n * Removes a lifecycle task from a specific phase.\n *\n * @param phase - The lifecycle phase to remove the task from\n * @param taskName - Name of the task to remove\n * @returns True if the task was found and removed, false otherwise\n */\n public removeTask(phase: TPhase, taskName: string): boolean {\n if (!this.canRemoveTask()) return false;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) return false;\n\n const initialLength = tasks.length;\n const filteredTasks = tasks.filter((task) => task.name !== taskName);\n this.tasksMap.set(phase, filteredTasks);\n\n const removed = initialLength !== filteredTasks.length;\n if (removed) {\n this.logger.debug(\n `${chalk.italic('Removed')} ${this.getTaskType()} task ${chalk.bold.cyan(taskName)} from phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n }\n\n return removed;\n }\n\n protected async runPhase(phase: TPhase): Promise<void> {\n const tasks = this.tasksMap.get(phase) ?? [];\n if (tasks.length === 0) {\n this.logger.debug(`No tasks to run in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`);\n return;\n }\n\n this.logger.info(\n `${chalk.bold.yellow('Running')} ${this.getTaskType()} phase ${chalk.bold.magenta(this.phaseEnum[phase])} with ${chalk.bold.cyan(tasks.length)} tasks`\n );\n\n const results: PromiseSettledResult<void>[] = await this.executeTasksInPhase(phase, tasks);\n\n const failures = results.filter((r) => r.status === 'rejected').length;\n if (failures > 0) {\n // chalk here would leak ANSI codes into the serialized error message (the unknown-exception webhook)\n throw new SeedcordError(SeedcordErrorCode.LifecyclePhaseFailures, [this.phaseEnum[phase], failures]);\n }\n\n this.logger.info(\n `Phase ${chalk.bold.magenta(this.phaseEnum[phase])} ${chalk.bold.green('completed successfully')}`\n );\n }\n\n protected async runTaskWithTimeout(phase: TPhase, task: LifecycleTask): Promise<void> {\n this.logger.info(\n `${chalk.italic('Starting')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n\n try {\n await withTimeout(task.name, task.task, task.timeout);\n\n this.logger.info(\n `${chalk.italic('Completed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}`\n );\n } catch (error) {\n this.logger.error(\n `${chalk.italic('Failed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(this.phaseEnum[phase])}:`,\n error\n );\n throw error;\n }\n }\n\n protected abstract canAddTask(): boolean;\n protected abstract canRemoveTask(): boolean;\n protected abstract getTaskType(): string;\n protected abstract executeTasksInPhase(\n phase: TPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]>;\n}\n","import chalk from 'chalk';\n\nimport { ShutdownPhase } from '@src/lifecycle/phases';\n\nimport { CoordinatedLifecycle } from './CoordinatedLifecycle';\n\nimport type { LifecycleTask } from './LifecycleTypes';\n\nconst PHASE_ORDER: ShutdownPhase[] = [\n ShutdownPhase.Unbind,\n ShutdownPhase.Drain,\n ShutdownPhase.Disconnect,\n ShutdownPhase.Logout\n];\n\n// gives the logger's file sink time to flush before process.exit\nconst LOG_FLUSH_DELAY_MS = 3000;\n\n/**\n * Runs registered shutdown tasks across `ShutdownPhase` in order. Registers SIGINT and SIGTERM\n * handlers. Tasks within a phase run in parallel.\n */\nexport class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {\n private isShuttingDown = false;\n private hasShutdown = false;\n private exitCode = 0;\n private onSigTerm: (() => void) | null = null;\n private onSigInt: (() => void) | null = null;\n private startupGate?: Promise<void>;\n\n public constructor() {\n super('Shutdown', PHASE_ORDER, ShutdownPhase);\n\n this.registerSignalHandlers();\n }\n\n protected canAddTask(): boolean {\n return true;\n }\n\n protected canRemoveTask(): boolean {\n return true;\n }\n\n protected getTaskType(): string {\n return 'shutdown';\n }\n\n protected async executeTasksInPhase(\n phase: ShutdownPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n private registerSignalHandlers(): void {\n this.onSigTerm = () => {\n this.logger.info(`Received ${chalk.yellow.bold('SIGTERM')} signal`);\n void this.run(0);\n };\n\n this.onSigInt = () => {\n this.logger.info(`Received ${chalk.yellow.bold('SIGINT')} signal`);\n void this.run(0);\n };\n\n process.on('SIGTERM', this.onSigTerm);\n process.on('SIGINT', this.onSigInt);\n }\n\n /** @internal */\n public removeSignalHandlers(): void {\n if (this.onSigTerm) {\n process.off('SIGTERM', this.onSigTerm);\n this.onSigTerm = null;\n }\n if (this.onSigInt) {\n process.off('SIGINT', this.onSigInt);\n this.onSigInt = null;\n }\n }\n\n /** @internal run() awaits this so boot finishes registering its dispose tasks first */\n public gateOnStartup(settled: Promise<void>): void {\n this.startupGate = settled;\n }\n\n /**\n * Adds a shutdown-phase task.\n *\n * @param phase - The shutdown phase to run the task in.\n * @param taskName - A descriptive name for the task.\n * @param task - The async function to run.\n * @param timeoutMs - Task timeout in ms. {@default `5000` }\n */\n public override addTask(phase: ShutdownPhase, taskName: string, task: () => Promise<void>, timeoutMs = 5000): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n /** Removes a shutdown-phase task, returning whether one was removed. */\n public override removeTask(phase: ShutdownPhase, taskName: string): boolean {\n return super.removeTask(phase, taskName);\n }\n\n /**\n * Runs registered tasks across shutdown phases in `ShutdownPhase` order.\n * Tasks within each phase run in parallel.\n *\n * @param exitCode - Process exit code. {@default `0` }\n * @param exitProcess - Whether to exit the process after shutdown. {@default `true` }\n * @example\n * ```typescript\n * shutdown.addTask(ShutdownPhase.Disconnect, 'database', () => db.disconnect(), 5000);\n * await shutdown.run(0);\n * ```\n */\n public async run(exitCode = 0, exitProcess = true): Promise<void> {\n this.removeSignalHandlers();\n\n // a dev-mode run leaves the process alive, so a second call would re-execute every task\n if (this.hasShutdown || this.isShuttingDown) {\n this.logger.warn('Shutdown sequence already ran or is in progress');\n return;\n }\n\n this.isShuttingDown = true;\n this.exitCode = exitCode;\n this.logger.info(\n `${chalk.bold.yellow('Starting')} coordinated shutdown with exit code ${chalk.bold.cyan(exitCode)}`\n );\n\n try {\n if (this.startupGate) await this.startupGate;\n const failures: unknown[] = [];\n for (const phase of PHASE_ORDER) {\n // run every phase so a mid-shutdown failure still attempts the later teardowns\n try {\n await this.runPhase(phase);\n } catch (error) {\n failures.push(error);\n }\n }\n\n if (failures.length > 0) {\n this.logger.error(`${chalk.bold.red('Coordinated shutdown failed')}`, ...failures);\n } else {\n this.logger.info(`${chalk.bold.green('Coordinated shutdown completed')} successfully`);\n }\n } finally {\n this.hasShutdown = true;\n if (exitProcess) {\n this.logger.info(`${chalk.bold.red('Exiting')} process with code ${chalk.bold.cyan(this.exitCode)}`);\n setTimeout(() => {\n process.exit(this.exitCode);\n }, LOG_FLUSH_DELAY_MS);\n } else {\n this.logger.info(`${chalk.bold.yellow('Skipping')} process exit (dev mode)`);\n this.isShuttingDown = false;\n }\n }\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport chalk from 'chalk';\n\nimport { StartupPhase } from '@src/lifecycle/phases';\n\nimport { CoordinatedLifecycle } from './CoordinatedLifecycle';\nimport { withTimeout } from './withTimeout';\n\nimport type { LifecycleTask } from './LifecycleTypes';\n\nconst PHASE_ORDER: StartupPhase[] = [StartupPhase.Configuration, StartupPhase.Login, StartupPhase.Ready];\n\n/**\n * Runs registered startup tasks across `StartupPhase` in order. Tasks within a phase run in parallel.\n */\nexport class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase> {\n private isStartingUp = false;\n private hasStarted = false;\n\n public constructor() {\n super('Startup', PHASE_ORDER, StartupPhase);\n }\n\n /**\n * Adds a startup-phase task.\n *\n * @param phase - The startup phase to run the task in.\n * @param taskName - A descriptive name for the task.\n * @param task - The async function to run.\n * @param timeoutMs - Task timeout in ms. {@default `10000` }\n */\n public override addTask(\n phase: StartupPhase,\n taskName: string,\n task: () => Promise<void>,\n timeoutMs = 10_000\n ): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n protected canAddTask(): boolean {\n if (this.hasStarted) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleAddAfterCompletion);\n }\n\n if (this.isStartingUp) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleAddDuringRun);\n }\n\n return true;\n }\n\n protected canRemoveTask(): boolean {\n if (this.isStartingUp) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleRemoveDuringRun);\n }\n\n return true;\n }\n\n protected getTaskType(): string {\n return 'startup';\n }\n\n protected async executeTasksInPhase(\n phase: StartupPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n /**\n * Runs the registered tasks across startup phases in `StartupPhase` order. Each phase completes\n * before the next begins, and tasks within a phase run concurrently.\n *\n * @throws {@link SeedcordError} if a startup task throws.\n */\n public async run(): Promise<void> {\n if (this.hasStarted) {\n this.logger.warn('Startup sequence has already completed');\n return;\n }\n\n if (this.isStartingUp) {\n this.logger.warn('Startup sequence already in progress');\n return;\n }\n\n this.isStartingUp = true;\n this.logger.info(`${chalk.bold.green('Starting')} coordinated startup sequence`);\n\n try {\n for (const phase of PHASE_ORDER) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- abort() can flip isStartingUp to false mid-loop from the cli\n if (!this.isStartingUp) {\n this.logger.warn('Startup sequence aborted');\n return;\n }\n await this.runPhase(phase);\n }\n\n this.hasStarted = true;\n this.logger.info(`${chalk.bold.green('Coordinated startup completed')} successfully`);\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- abort() can flip isStartingUp to false before this catch runs\n if (!this.isStartingUp) {\n this.logger.warn('Startup sequence aborted during error handling');\n return;\n }\n this.logger.error(`${chalk.bold.red('Coordinated startup failed')}`);\n throw error;\n } finally {\n this.isStartingUp = false;\n }\n }\n\n protected override async runTaskWithTimeout(phase: StartupPhase, task: LifecycleTask): Promise<void> {\n this.logger.info(\n `${chalk.italic('Starting')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(StartupPhase[phase])}`\n );\n\n try {\n await withTimeout(task.name, task.task, task.timeout);\n\n this.logger.info(\n `${chalk.italic('Completed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(StartupPhase[phase])}`\n );\n } catch (error) {\n if (!this.isStartingUp) {\n return;\n }\n\n this.logger.error(\n `${chalk.italic('Failed')} task ${chalk.bold.cyan(task.name)} in phase ${chalk.bold.magenta(StartupPhase[phase])}:`,\n error\n );\n throw error;\n }\n }\n\n /**\n * Aborts the startup sequence if it is currently running.\n */\n public abort(): void {\n if (!this.isStartingUp) return;\n\n this.isStartingUp = false;\n this.logger.warn('Aborting coordinated startup sequence');\n }\n\n public get isReady(): boolean {\n return this.hasStarted;\n }\n\n public get isRunning(): boolean {\n return this.isStartingUp;\n }\n}\n","import { createServer } from 'node:http';\n\nimport { Logger, paint } from '@seedcord/logger';\n\nimport { ShutdownPhase } from '@src/lifecycle/phases';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\n\n/**\n * HTTP server answering `GET` on the configured path with a JSON `{ status, timestamp }` body.\n */\nexport class HealthCheck {\n /** @internal */\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n public readonly port: number;\n public readonly path: string;\n public readonly host: string | undefined;\n\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this.port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this.path = options?.path ?? DEFAULT_HEALTH_CHECK_PATH;\n this.host = options?.host;\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n /**\n * Resolves the `healthCheck` option. `undefined` and `true` build the defaults, an object uses\n * its config, `false` returns `undefined`.\n */\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n if (option === false) return undefined;\n return new HealthCheck(shutdown, typeof option === 'object' ? option : undefined);\n }\n\n /**\n * Starts the health check server.\n * @returns Promise that resolves when the server is listening\n * @internal\n */\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (req.method === 'GET' && req.url === this.path) {\n res.writeHead(HTTP_OK, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));\n } else {\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n }\n });\n this.server = server;\n\n const onListenError = (err: Error): void => reject(err);\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and no 'error' listener at all crashes the process\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this.host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold('✔︎')} Health check server listening on ${paint.sky(`http://${address}:${this.port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this.host) {\n this.logger.debug(`Binding health check server to ${this.host}`);\n server.listen(this.port, this.host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this.port);\n }\n });\n }\n\n /**\n * Stops the health check server.\n * @internal\n */\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS, Logger } from '@seedcord/logger';\n\nimport { StartupPhase } from '@src/lifecycle/phases';\nimport { finalizePluginContext } from '@src/plugin/context';\nimport { resolvedLifecycleSpecOf } from '@src/plugin/Plugin';\n\nimport { withTimeout } from './Lifecycle/withTimeout';\n\nimport type { CoreBase } from '@interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '@node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '@node/Lifecycle/CoordinatedStartup';\nimport type { Config, IRateLimiter, Store } from '@seedcord/types';\nimport type { ShutdownPhase } from '@src/lifecycle/phases';\nimport type { PluginCapabilities, PluginContext, StoredPluginContext } from '@src/plugin/context';\nimport type { ChannelKeyAssert, Runtime, RuntimeAssert, Transport, TransportAssert } from '@src/plugin/options';\nimport type { CoreParamAssert, PluginArgs, PluginCtor, PluginLike } from '@src/plugin/Plugin';\nimport type { Bus } from '@subscribers/Bus';\n\ninterface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\n/**\n * Base class for objects that can have plugins attached.\n *\n * Plugins are attached during configuration and initialized during startup, sequentially in attach\n * order within each phase. Not constructed directly, the host is a transport `Seedcord` class.\n */\n// no defaults, a default runtime of the full union contains 'edge' and RuntimeAssert rejects every plugin on that\nexport abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {\n public abstract readonly config: Config;\n public abstract readonly rateLimiter: IRateLimiter;\n public abstract readonly bus: Bus;\n\n /** @see {@link CoordinatedShutdown} */\n public readonly shutdown: CoordinatedShutdown;\n\n /** @see {@link CoordinatedStartup} */\n public readonly startup: CoordinatedStartup;\n\n protected isInitialized = false;\n protected startFailed = false;\n protected readonly plugins: PluginLike[] = [];\n\n private readonly pluginLogger = new Logger('Plugins', { channel: 'plugins' });\n\n private readonly attachments: Attachment[] = [];\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private pluginTasksRegistered = false;\n\n private static isInstantiated = false;\n private static liveShutdown?: CoordinatedShutdown | undefined;\n\n constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n if (Pluggable.isInstantiated) {\n // the caller constructed this shutdown, its signal handlers leak unless dropped here\n shutdown.removeSignalHandlers();\n throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n }\n\n Pluggable.isInstantiated = true;\n Pluggable.liveShutdown = shutdown;\n this.shutdown = shutdown;\n this.startup = startup;\n }\n\n /** @internal */\n protected async init(): Promise<this> {\n if (this.isInitialized) return this;\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n this.registerPluginTasks();\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this.shutdown.gateOnStartup(startupSettled.promise);\n\n try {\n await this.startup.run();\n } catch (caught) {\n await this.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n this.isInitialized = true;\n return this;\n }\n\n // transports override to inject client/token/rest\n protected pluginCapabilities(): PluginCapabilities {\n return {};\n }\n\n protected pluginStore(): Store<'charge'> {\n // justified: both assignment branches of rateLimiter are Store<'charge'> at the hosts\n return this.config.store ?? (this.rateLimiter as Store<'charge'>);\n }\n\n /** @internal codegen reads these to emit the `Core` augmentation */\n public get pluginKeys(): readonly string[] {\n return this.attachments.map((attachment) => attachment.key);\n }\n\n /** @internal releases the signal handlers and clears the singleton so the next host can construct */\n protected static reset(): void {\n Pluggable.liveShutdown?.removeSignalHandlers();\n Pluggable.liveShutdown = undefined;\n Pluggable.isInstantiated = false;\n }\n\n /**\n * Attaches a plugin to this instance.\n *\n * The plugin initializes during startup, after every plugin attached before it, and becomes\n * available as a property on `core`. `seedcord codegen` writes the `Core` augmentation that\n * types it.\n *\n * A plugin declaring a `transport` or `runtime` this host does not run is a compile error on\n * this call. An edge host takes no plugins at all. A plugin constructor narrowing its first\n * parameter past `CoreBase` is a compile error here too. Read the transport `Core` off `this.core`.\n *\n * @typeParam Key - The property name for accessing the plugin\n * @typeParam Ctor - The plugin constructor type\n * @param key - Property name to access the plugin instance\n * @param Plugin - Plugin constructor class\n * @param args - Additional arguments to pass to the plugin constructor\n * @returns This instance with the plugin attached as a typed property\n * @throws A **SeedcordError** When called after initialization or if key already exists or is reserved\n * @example\n * ```typescript\n * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... })\n * ```\n */\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: ChannelKeyAssert<Key>,\n Plugin: Ctor &\n TransportAssert<InstanceType<Ctor>, BotT> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor>,\n ...args: PluginArgs<Ctor>\n ): this & Record<Key, InstanceType<Ctor>> {\n if (this.isInitialized) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n // several reserved channels are also members on a host, which the next check would report first\n if (RESERVED_KEYS.has(key)) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginReservedChannel, [key]);\n }\n if (key in this) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n const instance = new Plugin(this, ...args);\n this.plugins.push(instance);\n this.attachments.push({ key, instance });\n this.finalizeContext(key, instance);\n\n return Object.assign(this, { [key]: instance } as Record<Key, InstanceType<Ctor>>);\n }\n\n private finalizeContext(key: string, instance: PluginLike): void {\n const caps = this.pluginCapabilities();\n const readToken = (): string | undefined => this.pluginCapabilities().token;\n instance.logger.setChannel(key);\n const ctx: StoredPluginContext = {\n config: this.config,\n store: this.pluginStore(),\n client: caps.client,\n // http sets the token during Ready, read it live\n get token(): string | undefined {\n return readToken();\n },\n rest: caps.rest\n };\n finalizePluginContext(instance, ctx as PluginContext);\n }\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n private registerPluginTasks(): void {\n if (this.pluginTasksRegistered) return;\n this.pluginTasksRegistered = true;\n\n const groups = new Map<StartupPhase, Attachment[]>();\n for (const attachment of this.attachments) {\n const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;\n const group = groups.get(phase) ?? [];\n group.push(attachment);\n groups.set(phase, group);\n }\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance } = attachment;\n const spec = resolvedLifecycleSpecOf(instance);\n\n instance.logger.utils.initialization(key, 'start');\n await withTimeout(`Plugin (${key})`, () => instance.init(), spec.init.timeout);\n instance.logger.utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<{ key: string; run: () => Promise<void>; timeout: number }[]>(\n (acc, a) => {\n const ready = a.instance.ready?.bind(a.instance);\n if (ready) {\n acc.push({\n key: a.key,\n run: ready,\n timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout\n });\n }\n return acc;\n },\n []\n );\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'plugins-ready',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: ShutdownPhase): void {\n if (this.disposePhases.has(phase)) return;\n this.disposePhases.add(phase);\n\n // registered once per phase, later inits of that phase run in the same task\n const budget = this.attachments.reduce((sum, a) => {\n const spec = resolvedLifecycleSpecOf(a.instance);\n return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;\n }, 0);\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n // keep disposing the rest even when one fails\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, 'plugin dispose failures');\n }\n\n private async disposeCompleted(\n phase: ShutdownPhase | undefined,\n onError: (caught: unknown) => void\n ): Promise<void> {\n for (const attachment of [...this.attachments].reverse()) {\n if (!this.completedInits.has(attachment)) continue;\n const dispose = attachment.instance.dispose?.bind(attachment.instance);\n if (!dispose) continue;\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n\n private async rollback(): Promise<void> {\n this.startFailed = true;\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.pluginLogger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n}\n","import { traverseDirectory } from '@seedcord/utils/node';\nimport chalk from 'chalk';\nimport { Envapter } from 'envapt';\n\nimport { HmrModuleHandler } from '@hmr/HmrModuleHandler';\nimport { SubscribeMetadataKey } from '@src/metadataKeys';\nimport { registrationFor, Subscriber } from '@subscribers/index';\n\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { Initializeable } from '@src/plugin/Plugin';\nimport type { SubscribeMetadataEntry } from '@subscribers/decorators/Subscribe';\nimport type { StoredSubscriberCtor, Bus } from '@subscribers/index';\nimport type { SubscriptionKey } from '@subscribers/types/Subscriptions';\n\ntype SubscriberArtifact = SubscriptionKey[];\n\n/**\n * Loads subscribers off the filesystem into a {@link Bus} and stores their hot-reload wiring. Node\n * only, a bundled isolate registers from the manifest instead.\n *\n * @internal\n */\nexport class SubscriberLoader implements Initializeable, HmrAware {\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<StoredSubscriberCtor, void, SubscriberArtifact>;\n\n constructor(\n private readonly bus: Bus,\n private readonly directory: string | null\n ) {\n if (!this.directory) return;\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n\n this.hmrHandler = new HmrModuleHandler({\n handlersDir: this.directory,\n isHandler: this.isSubscriber.bind(this),\n registerHandler: this.registerSubscriber.bind(this),\n unregisterHandler: this.unregisterSubscriber.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: this.bus.logger\n });\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n this.bus.registerDefaults();\n\n const { directory } = this;\n if (directory) {\n this.bus.logger.info(chalk.bold(directory));\n await this.load(directory);\n this.bus.logger.utils.list(\n [`${chalk.bold.magenta(this.bus.registeredCount)} subscribers`],\n chalk.bold.green('Loaded')\n );\n }\n\n if (!Envapter.isTest) await this.bus.verifyWebhooks();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (this.hmrHandler) await this.hmrHandler.handle(event);\n }\n\n private async load(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, relativePath, imported) => {\n for (const exportName of Object.keys(imported)) {\n const value = imported[exportName];\n if (!this.isSubscriber(value)) continue;\n\n this.registerSubscriber(value);\n this.hmrHandler?.trackHandler(fullPath, value);\n this.bus.logger.utils.registration(value.name, relativePath);\n }\n });\n }\n\n private registerSubscriber(ctor: StoredSubscriberCtor): void {\n this.bus.register(registrationFor(ctor));\n }\n\n private unregisterSubscriber(ctor: StoredSubscriberCtor, artifacts?: SubscriberArtifact): void {\n this.bus.unregister(ctor, artifacts);\n }\n\n private getArtifacts(ctor: StoredSubscriberCtor): SubscriberArtifact {\n const meta = Reflect.getMetadata(SubscribeMetadataKey, ctor) as SubscribeMetadataEntry;\n return [meta.subscriber];\n }\n\n private isSubscriber(value: unknown): value is StoredSubscriberCtor {\n if (typeof value !== 'function') return false;\n return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);\n }\n}\n"],"mappings":"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"}
|
package/dist/plugin.index.cjs
CHANGED