@seedcord/services 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +380 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +247 -38
- package/dist/index.d.ts +247 -38
- package/dist/index.mjs +371 -70
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Envapter, Envapt } from 'envapt';
|
|
2
2
|
import { format, transports, createLogger } from 'winston';
|
|
3
|
+
import chalk2 from 'chalk';
|
|
3
4
|
import { createServer } from 'http';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
5
|
import { EventEmitter } from 'events';
|
|
6
6
|
|
|
7
7
|
var __defProp = Object.defineProperty;
|
|
@@ -230,6 +230,232 @@ ${parts.join(" ")}`;
|
|
|
230
230
|
logger.silly(msg, ...args);
|
|
231
231
|
}
|
|
232
232
|
};
|
|
233
|
+
|
|
234
|
+
// src/CooldownManager.ts
|
|
235
|
+
var CooldownManager = class {
|
|
236
|
+
static {
|
|
237
|
+
__name(this, "CooldownManager");
|
|
238
|
+
}
|
|
239
|
+
window;
|
|
240
|
+
Err;
|
|
241
|
+
msg;
|
|
242
|
+
map = /* @__PURE__ */ new Map();
|
|
243
|
+
/**
|
|
244
|
+
* Creates a new CooldownManager instance.
|
|
245
|
+
*
|
|
246
|
+
* @param opts - Configuration options for the cooldown behavior
|
|
247
|
+
*/
|
|
248
|
+
constructor(opts = {}) {
|
|
249
|
+
this.window = opts.cooldown ?? 1e3;
|
|
250
|
+
this.Err = opts.err ?? Error;
|
|
251
|
+
this.msg = opts.message ?? "Cooldown active";
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Records usage timestamp for a key without any cooldown checks.
|
|
255
|
+
*
|
|
256
|
+
* @param key - The unique identifier for the cooldown entry
|
|
257
|
+
*/
|
|
258
|
+
set(key) {
|
|
259
|
+
this.map.set(key, Date.now());
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Verifies cooldown status for a key and updates timestamp if not active.
|
|
263
|
+
*
|
|
264
|
+
* If the cooldown is still active, throws the configured error.
|
|
265
|
+
* If not active, updates the timestamp and returns successfully.
|
|
266
|
+
*
|
|
267
|
+
* @param key - The unique identifier to check cooldown for
|
|
268
|
+
* @throws An {@link Err} When the cooldown is still active for the given key
|
|
269
|
+
*/
|
|
270
|
+
check(key) {
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
const last = this.map.get(key);
|
|
273
|
+
const remaining = this.window - (now - (last ?? 0));
|
|
274
|
+
if (Envapter.isDevelopment && remaining > 0) {
|
|
275
|
+
Logger.Debug("CooldownManager", `${key} - ${remaining}ms remaining`);
|
|
276
|
+
}
|
|
277
|
+
if (last !== void 0 && remaining > 0) {
|
|
278
|
+
throw new this.Err(this.msg, remaining);
|
|
279
|
+
}
|
|
280
|
+
this.map.set(key, now);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Checks if a key is currently cooling down without updating timestamp.
|
|
284
|
+
*
|
|
285
|
+
* @param key - The unique identifier to check
|
|
286
|
+
* @returns True if the key is still cooling down, false otherwise
|
|
287
|
+
*/
|
|
288
|
+
isActive(key) {
|
|
289
|
+
const last = this.map.get(key);
|
|
290
|
+
return last !== void 0 && Date.now() - last < this.window;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Removes a key from the cooldown map.
|
|
294
|
+
*
|
|
295
|
+
* @param key - The unique identifier to remove (useful for manual resets)
|
|
296
|
+
*/
|
|
297
|
+
clear(key) {
|
|
298
|
+
this.map.delete(key);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/Errors/ErrorCodes.ts
|
|
303
|
+
var SeedcordErrorCode = /* @__PURE__ */ (function(SeedcordErrorCode2) {
|
|
304
|
+
SeedcordErrorCode2[SeedcordErrorCode2["ConfigMissingDiscordToken"] = 1001] = "ConfigMissingDiscordToken";
|
|
305
|
+
SeedcordErrorCode2[SeedcordErrorCode2["ConfigUnknownExceptionWebhookMissing"] = 1002] = "ConfigUnknownExceptionWebhookMissing";
|
|
306
|
+
SeedcordErrorCode2[SeedcordErrorCode2["ConfigUnknownExceptionWebhookInvalid"] = 1003] = "ConfigUnknownExceptionWebhookInvalid";
|
|
307
|
+
SeedcordErrorCode2[SeedcordErrorCode2["LifecycleAddAfterCompletion"] = 1101] = "LifecycleAddAfterCompletion";
|
|
308
|
+
SeedcordErrorCode2[SeedcordErrorCode2["LifecycleAddDuringRun"] = 1102] = "LifecycleAddDuringRun";
|
|
309
|
+
SeedcordErrorCode2[SeedcordErrorCode2["LifecycleRemoveDuringRun"] = 1103] = "LifecycleRemoveDuringRun";
|
|
310
|
+
SeedcordErrorCode2[SeedcordErrorCode2["LifecycleUnknownPhase"] = 1104] = "LifecycleUnknownPhase";
|
|
311
|
+
SeedcordErrorCode2[SeedcordErrorCode2["LifecyclePhaseFailures"] = 1105] = "LifecyclePhaseFailures";
|
|
312
|
+
SeedcordErrorCode2[SeedcordErrorCode2["LifecycleTaskTimeout"] = 1106] = "LifecycleTaskTimeout";
|
|
313
|
+
SeedcordErrorCode2[SeedcordErrorCode2["CoreSingletonViolation"] = 1201] = "CoreSingletonViolation";
|
|
314
|
+
SeedcordErrorCode2[SeedcordErrorCode2["CorePluginAfterInit"] = 1202] = "CorePluginAfterInit";
|
|
315
|
+
SeedcordErrorCode2[SeedcordErrorCode2["CorePluginKeyExists"] = 1203] = "CorePluginKeyExists";
|
|
316
|
+
SeedcordErrorCode2[SeedcordErrorCode2["CoreClientUserUnavailable"] = 1204] = "CoreClientUserUnavailable";
|
|
317
|
+
SeedcordErrorCode2[SeedcordErrorCode2["CoreBotRoleMissing"] = 1205] = "CoreBotRoleMissing";
|
|
318
|
+
SeedcordErrorCode2[SeedcordErrorCode2["DecoratorInteractionEventFilter"] = 1301] = "DecoratorInteractionEventFilter";
|
|
319
|
+
SeedcordErrorCode2[SeedcordErrorCode2["DecoratorMethodNotFound"] = 1302] = "DecoratorMethodNotFound";
|
|
320
|
+
SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandAlreadyRegistered"] = 1303] = "DecoratorCommandAlreadyRegistered";
|
|
321
|
+
SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandGlobalWithGuilds"] = 1304] = "DecoratorCommandGlobalWithGuilds";
|
|
322
|
+
SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandGuildWithoutGuilds"] = 1305] = "DecoratorCommandGuildWithoutGuilds";
|
|
323
|
+
SeedcordErrorCode2[SeedcordErrorCode2["DecoratorInvalidMiddlewarePriority"] = 1306] = "DecoratorInvalidMiddlewarePriority";
|
|
324
|
+
SeedcordErrorCode2[SeedcordErrorCode2["UtilHexInputType"] = 1401] = "UtilHexInputType";
|
|
325
|
+
SeedcordErrorCode2[SeedcordErrorCode2["UtilHexInvalid"] = 1402] = "UtilHexInvalid";
|
|
326
|
+
SeedcordErrorCode2[SeedcordErrorCode2["UtilInvalidSlashRouteArgument"] = 1403] = "UtilInvalidSlashRouteArgument";
|
|
327
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoServiceDecoratorMissing"] = 2101] = "PluginMongoServiceDecoratorMissing";
|
|
328
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoModelDecoratorMissing"] = 2102] = "PluginMongoModelDecoratorMissing";
|
|
329
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoConnectionFailed"] = 2103] = "PluginMongoConnectionFailed";
|
|
330
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgServiceDecoratorMissing"] = 2201] = "PluginKpgServiceDecoratorMissing";
|
|
331
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgServiceTableMissing"] = 2202] = "PluginKpgServiceTableMissing";
|
|
332
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgInvalidStepCount"] = 2203] = "PluginKpgInvalidStepCount";
|
|
333
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgUnknownDirection"] = 2204] = "PluginKpgUnknownDirection";
|
|
334
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgUnresolvedMigrationsPath"] = 2205] = "PluginKpgUnresolvedMigrationsPath";
|
|
335
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgNoMigrationFiles"] = 2206] = "PluginKpgNoMigrationFiles";
|
|
336
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgInvalidMigrationModule"] = 2207] = "PluginKpgInvalidMigrationModule";
|
|
337
|
+
SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgNonErrorFailure"] = 2208] = "PluginKpgNonErrorFailure";
|
|
338
|
+
return SeedcordErrorCode2;
|
|
339
|
+
})({});
|
|
340
|
+
|
|
341
|
+
// src/Errors/ErrorMessages.ts
|
|
342
|
+
var messages = {
|
|
343
|
+
[SeedcordErrorCode.ConfigMissingDiscordToken]: () => "Missing DISCORD_BOT_TOKEN environment variable.",
|
|
344
|
+
[SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing]: () => "Missing UNKNOWN_EXCEPTION_WEBHOOK_URL environment variable.",
|
|
345
|
+
[SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid]: () => "Invalid UNKNOWN_EXCEPTION_WEBHOOK_URL value.",
|
|
346
|
+
[SeedcordErrorCode.LifecycleAddAfterCompletion]: () => "Cannot add tasks after startup sequence has already completed.",
|
|
347
|
+
[SeedcordErrorCode.LifecycleAddDuringRun]: () => "Cannot add tasks while startup sequence is in progress.",
|
|
348
|
+
[SeedcordErrorCode.LifecycleRemoveDuringRun]: () => "Cannot remove tasks while startup sequence is in progress.",
|
|
349
|
+
[SeedcordErrorCode.LifecycleUnknownPhase]: (phase) => `Unknown phase: ${String(phase)}.`,
|
|
350
|
+
[SeedcordErrorCode.LifecyclePhaseFailures]: (phase, failures) => `Phase ${phase} completed with ${failures} failed task${failures === 1 ? "" : "s"}.`,
|
|
351
|
+
[SeedcordErrorCode.LifecycleTaskTimeout]: (taskName, timeout) => `Task "${taskName}" timed out after ${timeout}ms.`,
|
|
352
|
+
[SeedcordErrorCode.CoreSingletonViolation]: () => "Seedcord can only be instantiated once. Use the existing instance instead.",
|
|
353
|
+
[SeedcordErrorCode.CorePluginAfterInit]: () => "Cannot attach a plugin after initialization.",
|
|
354
|
+
[SeedcordErrorCode.CorePluginKeyExists]: (key) => `Plugin with key "${key}" already exists.`,
|
|
355
|
+
[SeedcordErrorCode.CoreClientUserUnavailable]: () => "Client user is not available.",
|
|
356
|
+
[SeedcordErrorCode.CoreBotRoleMissing]: (guildId) => guildId ? `Bot role not found in guild ${guildId}.` : "Bot role not found in guild.",
|
|
357
|
+
[SeedcordErrorCode.DecoratorInteractionEventFilter]: () => "Interaction middleware cannot specify event filters.",
|
|
358
|
+
[SeedcordErrorCode.DecoratorMethodNotFound]: () => "Decorator could not locate the original method. Ensure the method exists before applying the decorator.",
|
|
359
|
+
[SeedcordErrorCode.DecoratorCommandAlreadyRegistered]: (commandName, existingScope, requestedScope) => `Command "${commandName}" is already registered as a "${existingScope}" command and cannot be re-registered as a "${requestedScope}" command.`,
|
|
360
|
+
[SeedcordErrorCode.DecoratorCommandGlobalWithGuilds]: () => 'RegisterCommand("global") cannot have guilds specified.',
|
|
361
|
+
[SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds]: () => 'RegisterCommand("guild") requires a non-empty guilds array.',
|
|
362
|
+
[SeedcordErrorCode.DecoratorInvalidMiddlewarePriority]: () => "Middleware priority must be a finite number.",
|
|
363
|
+
[SeedcordErrorCode.UtilHexInputType]: () => "hexToNumber expects a string input.",
|
|
364
|
+
[SeedcordErrorCode.UtilHexInvalid]: () => "Invalid hex string.",
|
|
365
|
+
[SeedcordErrorCode.UtilInvalidSlashRouteArgument]: () => "Invalid argument passed to buildSlashRoute.",
|
|
366
|
+
[SeedcordErrorCode.PluginMongoServiceDecoratorMissing]: (className) => `Missing @RegisterMongoService on ${className}.`,
|
|
367
|
+
[SeedcordErrorCode.PluginMongoModelDecoratorMissing]: (className) => `Missing @RegisterMongoModel on ${className}.`,
|
|
368
|
+
[SeedcordErrorCode.PluginMongoConnectionFailed]: (databaseName) => databaseName ? `Could not connect to MongoDB (${databaseName}).` : "Could not connect to MongoDB.",
|
|
369
|
+
[SeedcordErrorCode.PluginKpgServiceDecoratorMissing]: (className) => `Missing @RegisterKpgService on ${className}.`,
|
|
370
|
+
[SeedcordErrorCode.PluginKpgServiceTableMissing]: (className) => `Missing table metadata for ${className}. Provide a table via @RegisterKpgService().`,
|
|
371
|
+
[SeedcordErrorCode.PluginKpgInvalidStepCount]: () => "Migration step count must be a non-negative integer.",
|
|
372
|
+
[SeedcordErrorCode.PluginKpgUnknownDirection]: (direction) => `Unknown migration direction: ${String(direction)}.`,
|
|
373
|
+
[SeedcordErrorCode.PluginKpgUnresolvedMigrationsPath]: (label) => `Unable to resolve migrations at path: ${label}.`,
|
|
374
|
+
[SeedcordErrorCode.PluginKpgNoMigrationFiles]: () => "No migration files provided.",
|
|
375
|
+
[SeedcordErrorCode.PluginKpgInvalidMigrationModule]: (filePath) => `Migration file ${filePath} must export async functions up and down.`,
|
|
376
|
+
[SeedcordErrorCode.PluginKpgNonErrorFailure]: (message) => `Migration failure: ${message}.`
|
|
377
|
+
};
|
|
378
|
+
function formatSeedcordErrorMessage(code, args) {
|
|
379
|
+
const formatter = messages[code];
|
|
380
|
+
const resolvedArgs = args ?? [];
|
|
381
|
+
return formatter(...resolvedArgs);
|
|
382
|
+
}
|
|
383
|
+
__name(formatSeedcordErrorMessage, "formatSeedcordErrorMessage");
|
|
384
|
+
function resolveIdentifier(code) {
|
|
385
|
+
return SeedcordErrorCode[code];
|
|
386
|
+
}
|
|
387
|
+
__name(resolveIdentifier, "resolveIdentifier");
|
|
388
|
+
function resolveMessage(code, args) {
|
|
389
|
+
return formatSeedcordErrorMessage(code, args);
|
|
390
|
+
}
|
|
391
|
+
__name(resolveMessage, "resolveMessage");
|
|
392
|
+
function formatErrorName(name, _identifier, code) {
|
|
393
|
+
return `${chalk2.bold.red(name)}[${chalk2.gray(code)}]`;
|
|
394
|
+
}
|
|
395
|
+
__name(formatErrorName, "formatErrorName");
|
|
396
|
+
var SeedcordError = class extends Error {
|
|
397
|
+
static {
|
|
398
|
+
__name(this, "SeedcordError");
|
|
399
|
+
}
|
|
400
|
+
code;
|
|
401
|
+
identifier;
|
|
402
|
+
constructor(code, args, options) {
|
|
403
|
+
const message = resolveMessage(code, args);
|
|
404
|
+
super(message, options);
|
|
405
|
+
this.code = code;
|
|
406
|
+
this.identifier = resolveIdentifier(code);
|
|
407
|
+
this.name = formatErrorName(new.target.name, this.identifier, this.code);
|
|
408
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
409
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
410
|
+
Error.captureStackTrace(this, new.target);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
var SeedcordTypeError = class extends TypeError {
|
|
415
|
+
static {
|
|
416
|
+
__name(this, "SeedcordTypeError");
|
|
417
|
+
}
|
|
418
|
+
code;
|
|
419
|
+
identifier;
|
|
420
|
+
constructor(code, args, options) {
|
|
421
|
+
const message = resolveMessage(code, args);
|
|
422
|
+
super(message, options);
|
|
423
|
+
this.code = code;
|
|
424
|
+
this.identifier = resolveIdentifier(code);
|
|
425
|
+
this.name = formatErrorName(new.target.name, this.identifier, this.code);
|
|
426
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
427
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
428
|
+
Error.captureStackTrace(this, new.target);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
var SeedcordRangeError = class extends RangeError {
|
|
433
|
+
static {
|
|
434
|
+
__name(this, "SeedcordRangeError");
|
|
435
|
+
}
|
|
436
|
+
code;
|
|
437
|
+
identifier;
|
|
438
|
+
constructor(code, args, options) {
|
|
439
|
+
const message = resolveMessage(code, args);
|
|
440
|
+
super(message, options);
|
|
441
|
+
this.code = code;
|
|
442
|
+
this.identifier = resolveIdentifier(code);
|
|
443
|
+
this.name = formatErrorName(new.target.name, this.identifier, this.code);
|
|
444
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
445
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
446
|
+
Error.captureStackTrace(this, new.target);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
var SeedcordErrors = {
|
|
451
|
+
Error: SeedcordError,
|
|
452
|
+
TypeError: SeedcordTypeError,
|
|
453
|
+
RangeError: SeedcordRangeError
|
|
454
|
+
};
|
|
455
|
+
function isSeedcordError(error) {
|
|
456
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "number" && "identifier" in error && typeof error.identifier === "string";
|
|
457
|
+
}
|
|
458
|
+
__name(isSeedcordError, "isSeedcordError");
|
|
233
459
|
var CoordinatedLifecycle = class {
|
|
234
460
|
static {
|
|
235
461
|
__name(this, "CoordinatedLifecycle");
|
|
@@ -265,13 +491,17 @@ var CoordinatedLifecycle = class {
|
|
|
265
491
|
addTask(phase, taskName, task, timeoutMs) {
|
|
266
492
|
if (!this.canAddTask()) return;
|
|
267
493
|
const tasks = this.tasksMap.get(phase);
|
|
268
|
-
if (!tasks)
|
|
494
|
+
if (!tasks) {
|
|
495
|
+
throw new SeedcordError(SeedcordErrorCode.LifecycleUnknownPhase, [
|
|
496
|
+
phase
|
|
497
|
+
]);
|
|
498
|
+
}
|
|
269
499
|
tasks.push({
|
|
270
500
|
name: taskName,
|
|
271
501
|
task,
|
|
272
502
|
timeout: timeoutMs
|
|
273
503
|
});
|
|
274
|
-
this.logger.debug(`${
|
|
504
|
+
this.logger.debug(`${chalk2.italic("Added")} ${this.getTaskType()} task ${chalk2.bold.cyan(taskName)} to phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
|
|
275
505
|
}
|
|
276
506
|
/**
|
|
277
507
|
* Removes a lifecycle task from a specific phase.
|
|
@@ -289,7 +519,7 @@ var CoordinatedLifecycle = class {
|
|
|
289
519
|
this.tasksMap.set(phase, filteredTasks);
|
|
290
520
|
const removed = initialLength !== filteredTasks.length;
|
|
291
521
|
if (removed) {
|
|
292
|
-
this.logger.debug(`${
|
|
522
|
+
this.logger.debug(`${chalk2.italic("Removed")} ${this.getTaskType()} task ${chalk2.bold.cyan(taskName)} from phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
|
|
293
523
|
}
|
|
294
524
|
return removed;
|
|
295
525
|
}
|
|
@@ -299,18 +529,20 @@ var CoordinatedLifecycle = class {
|
|
|
299
529
|
async runPhase(phase) {
|
|
300
530
|
const tasks = this.tasksMap.get(phase) ?? [];
|
|
301
531
|
if (tasks.length === 0) {
|
|
302
|
-
this.logger.warn(`No tasks to run in phase ${
|
|
532
|
+
this.logger.warn(`No tasks to run in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
|
|
303
533
|
return;
|
|
304
534
|
}
|
|
305
|
-
this.logger.info(`${
|
|
535
|
+
this.logger.info(`${chalk2.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk2.bold.magenta(this.phaseEnum[phase])} with ${chalk2.bold.cyan(tasks.length)} tasks`);
|
|
306
536
|
this.emit(`phase:${phase}:start`);
|
|
307
537
|
const results = await this.executeTasksInPhase(phase, tasks);
|
|
308
538
|
const failures = results.filter((r) => r.status === "rejected").length;
|
|
309
539
|
if (failures > 0) {
|
|
310
|
-
|
|
311
|
-
|
|
540
|
+
throw new SeedcordError(SeedcordErrorCode.LifecyclePhaseFailures, [
|
|
541
|
+
chalk2.bold.magenta(this.phaseEnum[phase]),
|
|
542
|
+
failures
|
|
543
|
+
]);
|
|
312
544
|
} else {
|
|
313
|
-
this.logger.info(`Phase ${
|
|
545
|
+
this.logger.info(`Phase ${chalk2.bold.magenta(this.phaseEnum[phase])} ${chalk2.bold.green("completed successfully")}`);
|
|
314
546
|
}
|
|
315
547
|
this.emit(`phase:${phase}:complete`);
|
|
316
548
|
}
|
|
@@ -318,19 +550,22 @@ var CoordinatedLifecycle = class {
|
|
|
318
550
|
* Run a single task with timeout
|
|
319
551
|
*/
|
|
320
552
|
async runTaskWithTimeout(phase, task) {
|
|
321
|
-
this.logger.info(`${
|
|
553
|
+
this.logger.info(`${chalk2.italic("Starting")} task ${chalk2.bold.cyan(task.name)} in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
|
|
322
554
|
try {
|
|
323
555
|
await Promise.race([
|
|
324
556
|
task.task(),
|
|
325
557
|
new Promise((_, reject) => {
|
|
326
558
|
setTimeout(() => {
|
|
327
|
-
reject(new
|
|
559
|
+
reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [
|
|
560
|
+
task.name,
|
|
561
|
+
task.timeout
|
|
562
|
+
]));
|
|
328
563
|
}, task.timeout);
|
|
329
564
|
})
|
|
330
565
|
]);
|
|
331
|
-
this.logger.info(`${
|
|
566
|
+
this.logger.info(`${chalk2.italic("Completed")} task ${chalk2.bold.cyan(task.name)} in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}`);
|
|
332
567
|
} catch (error) {
|
|
333
|
-
this.logger.error(`${
|
|
568
|
+
this.logger.error(`${chalk2.italic("Failed")} task ${chalk2.bold.cyan(task.name)} in phase ${chalk2.bold.magenta(this.phaseEnum[phase])}:`, error);
|
|
334
569
|
throw error;
|
|
335
570
|
}
|
|
336
571
|
}
|
|
@@ -418,11 +653,11 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
418
653
|
registerSignalHandlers() {
|
|
419
654
|
if (!this.isShutdownEnabled) return;
|
|
420
655
|
process.on("SIGTERM", () => {
|
|
421
|
-
this.logger.info(`Received ${
|
|
656
|
+
this.logger.info(`Received ${chalk2.yellow.bold("SIGTERM")} signal`);
|
|
422
657
|
void this.run(0);
|
|
423
658
|
});
|
|
424
659
|
process.on("SIGINT", () => {
|
|
425
|
-
this.logger.info(`Received ${
|
|
660
|
+
this.logger.info(`Received ${chalk2.yellow.bold("SIGINT")} signal`);
|
|
426
661
|
void this.run(0);
|
|
427
662
|
});
|
|
428
663
|
}
|
|
@@ -469,19 +704,19 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
|
469
704
|
}
|
|
470
705
|
this.isShuttingDown = true;
|
|
471
706
|
this.exitCode = exitCode;
|
|
472
|
-
this.logger.info(`${
|
|
707
|
+
this.logger.info(`${chalk2.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk2.bold.cyan(exitCode)}`);
|
|
473
708
|
this.emit("shutdown:start");
|
|
474
709
|
try {
|
|
475
710
|
for (const phase of PHASE_ORDER) {
|
|
476
711
|
await this.runPhase(phase);
|
|
477
712
|
}
|
|
478
|
-
this.logger.info(`${
|
|
713
|
+
this.logger.info(`${chalk2.bold.green("Coordinated shutdown completed")} successfully`);
|
|
479
714
|
this.emit("shutdown:complete");
|
|
480
715
|
} catch (error) {
|
|
481
|
-
this.logger.error(`${
|
|
716
|
+
this.logger.error(`${chalk2.bold.red("Coordinated shutdown failed")}`);
|
|
482
717
|
this.emit("shutdown:error", error);
|
|
483
718
|
} finally {
|
|
484
|
-
this.logger.info(`${
|
|
719
|
+
this.logger.info(`${chalk2.bold.red("Exiting")} process with code ${chalk2.bold.cyan(this.exitCode)}`);
|
|
485
720
|
setTimeout(() => {
|
|
486
721
|
process.exit(this.exitCode);
|
|
487
722
|
}, LOG_FLUSH_DELAY_MS);
|
|
@@ -557,7 +792,7 @@ var HealthCheck = class {
|
|
|
557
792
|
this.server.on("error", reject);
|
|
558
793
|
this.server.once("listening", () => {
|
|
559
794
|
const address = this.host ?? "localhost";
|
|
560
|
-
this.logger.info(`${
|
|
795
|
+
this.logger.info(`${chalk2.green.bold("\u2713")} Health check server listening on ${chalk2.cyan(`http://${address}:${this.port}${this.path}`)}`);
|
|
561
796
|
resolve();
|
|
562
797
|
});
|
|
563
798
|
if (this.host) {
|
|
@@ -580,7 +815,7 @@ var HealthCheck = class {
|
|
|
580
815
|
return new Promise((resolve) => {
|
|
581
816
|
server.once("close", () => resolve());
|
|
582
817
|
server.close(() => {
|
|
583
|
-
this.logger.info(
|
|
818
|
+
this.logger.info(chalk2.bold.red("Health check server stopped"));
|
|
584
819
|
});
|
|
585
820
|
});
|
|
586
821
|
}
|
|
@@ -603,70 +838,136 @@ _ts_decorate2([
|
|
|
603
838
|
Envapt("HEALTH_CHECK_HOST"),
|
|
604
839
|
_ts_metadata2("design:type", Object)
|
|
605
840
|
], HealthCheck.prototype, "host", void 0);
|
|
606
|
-
var
|
|
841
|
+
var StrictEventEmitter = class extends EventEmitter {
|
|
607
842
|
static {
|
|
608
|
-
__name(this, "
|
|
843
|
+
__name(this, "StrictEventEmitter");
|
|
609
844
|
}
|
|
610
|
-
window;
|
|
611
|
-
Err;
|
|
612
|
-
msg;
|
|
613
|
-
map = /* @__PURE__ */ new Map();
|
|
614
845
|
/**
|
|
615
|
-
*
|
|
846
|
+
* Registers a persistent listener with tuple-safe arguments for the given event.
|
|
616
847
|
*
|
|
617
|
-
* @param
|
|
848
|
+
* @param event - The event name to attach to
|
|
849
|
+
* @param listener - Callback operating on the typed argument tuple for the event
|
|
850
|
+
* @returns This emitter instance for chaining
|
|
618
851
|
*/
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.Err = opts.err ?? Error;
|
|
622
|
-
this.msg = opts.message ?? "Cooldown active";
|
|
852
|
+
on(event, listener) {
|
|
853
|
+
return super.on(event, listener);
|
|
623
854
|
}
|
|
624
855
|
/**
|
|
625
|
-
*
|
|
856
|
+
* Registers a one time listener that is removed after the first invocation.
|
|
626
857
|
*
|
|
627
|
-
* @param
|
|
858
|
+
* @param event - The event name to attach to
|
|
859
|
+
* @param listener - Callback operating on the typed argument tuple for the event
|
|
860
|
+
* @returns This emitter instance for chaining
|
|
628
861
|
*/
|
|
629
|
-
|
|
630
|
-
|
|
862
|
+
once(event, listener) {
|
|
863
|
+
return super.once(event, listener);
|
|
631
864
|
}
|
|
632
865
|
/**
|
|
633
|
-
*
|
|
866
|
+
* Removes a previously registered listener for the given event.
|
|
634
867
|
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
868
|
+
* @param event - The event name whose listener should be removed
|
|
869
|
+
* @param listener - Callback originally registered for the event
|
|
870
|
+
* @returns This emitter instance for chaining
|
|
871
|
+
*/
|
|
872
|
+
off(event, listener) {
|
|
873
|
+
return super.off(event, listener);
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Alias of {@link StrictEventEmitter.on} for compatibility with Node.js EventEmitter APIs.
|
|
637
877
|
*
|
|
638
|
-
* @param
|
|
639
|
-
* @
|
|
878
|
+
* @param event - The event name to attach to
|
|
879
|
+
* @param listener - Callback operating on the typed argument tuple for the event
|
|
880
|
+
* @returns This emitter instance for chaining
|
|
640
881
|
*/
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const last = this.map.get(key);
|
|
644
|
-
const remaining = this.window - (now - (last ?? 0));
|
|
645
|
-
if (Envapter.isDevelopment && remaining > 0) {
|
|
646
|
-
Logger.Debug("CooldownManager", `${key} - ${remaining}ms remaining`);
|
|
647
|
-
}
|
|
648
|
-
if (last !== void 0 && remaining > 0) {
|
|
649
|
-
throw new this.Err(this.msg, remaining);
|
|
650
|
-
}
|
|
651
|
-
this.map.set(key, now);
|
|
882
|
+
addListener(event, listener) {
|
|
883
|
+
return this.on(event, listener);
|
|
652
884
|
}
|
|
653
885
|
/**
|
|
654
|
-
*
|
|
886
|
+
* Alias of {@link StrictEventEmitter.off} for compatibility with Node.js EventEmitter APIs.
|
|
655
887
|
*
|
|
656
|
-
* @param
|
|
657
|
-
* @
|
|
888
|
+
* @param event - The event name whose listener should be removed
|
|
889
|
+
* @param listener - Callback originally registered for the event
|
|
890
|
+
* @returns This emitter instance for chaining
|
|
658
891
|
*/
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
return last !== void 0 && Date.now() - last < this.window;
|
|
892
|
+
removeListener(event, listener) {
|
|
893
|
+
return super.removeListener(event, listener);
|
|
662
894
|
}
|
|
663
895
|
/**
|
|
664
|
-
*
|
|
896
|
+
* Emits an event with the strictly typed argument tuple for the event name.
|
|
665
897
|
*
|
|
666
|
-
* @param
|
|
898
|
+
* @param event - The event name to emit
|
|
899
|
+
* @param args - Tuple payload for the event
|
|
900
|
+
* @returns True when the event had listeners, false otherwise
|
|
667
901
|
*/
|
|
668
|
-
|
|
669
|
-
|
|
902
|
+
emit(event, ...args) {
|
|
903
|
+
return super.emit(event, ...args);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Retrieves the listener list for a given event with the correct tuple signature.
|
|
907
|
+
*
|
|
908
|
+
* @param event - The event name to inspect
|
|
909
|
+
* @returns Array of listeners registered for the event
|
|
910
|
+
*/
|
|
911
|
+
listeners(event) {
|
|
912
|
+
return super.listeners(event);
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Counts listeners for an event without widening the return type of {@link EventEmitter.listenerCount}.
|
|
916
|
+
*
|
|
917
|
+
* @param event - The event name to inspect
|
|
918
|
+
* @returns The total number of listeners registered for the event
|
|
919
|
+
*/
|
|
920
|
+
listenerCountTyped(event) {
|
|
921
|
+
return super.listenerCount(event);
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Returns the list of event names known to the emitter with the mapped key type.
|
|
925
|
+
*
|
|
926
|
+
* @returns Array of event keys supported by the emitter
|
|
927
|
+
*/
|
|
928
|
+
eventNamesTyped() {
|
|
929
|
+
return super.eventNames();
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Waits for an event to be emitted, resolving with the listener arguments tuple once triggered.
|
|
933
|
+
* Supports optional abort signals and timeouts for cancellation semantics.
|
|
934
|
+
*
|
|
935
|
+
* @param event - The event name to wait for
|
|
936
|
+
* @param opts - Optional abort signal or timeout in milliseconds
|
|
937
|
+
* @returns Promise resolving with the emitted argument tuple; rejects when aborted or timed out
|
|
938
|
+
*/
|
|
939
|
+
waitFor(event, opts) {
|
|
940
|
+
return new Promise((resolve, reject) => {
|
|
941
|
+
const onEvent = /* @__PURE__ */ __name((...args) => {
|
|
942
|
+
cleanup();
|
|
943
|
+
resolve(args);
|
|
944
|
+
}, "onEvent");
|
|
945
|
+
const onAbort = /* @__PURE__ */ __name(() => {
|
|
946
|
+
cleanup();
|
|
947
|
+
reject(Object.assign(new Error("Aborted"), {
|
|
948
|
+
name: "AbortError"
|
|
949
|
+
}));
|
|
950
|
+
}, "onAbort");
|
|
951
|
+
let timeoutId = null;
|
|
952
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
953
|
+
this.off(event, onEvent);
|
|
954
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
955
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
956
|
+
}, "cleanup");
|
|
957
|
+
this.once(event, onEvent);
|
|
958
|
+
if (opts?.signal) {
|
|
959
|
+
if (opts.signal.aborted) return onAbort();
|
|
960
|
+
opts.signal.addEventListener("abort", onAbort, {
|
|
961
|
+
once: true
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
if (opts?.timeoutMs !== void 0) {
|
|
965
|
+
timeoutId = setTimeout(() => {
|
|
966
|
+
cleanup();
|
|
967
|
+
reject(new Error("Timed out"));
|
|
968
|
+
}, opts.timeoutMs);
|
|
969
|
+
}
|
|
970
|
+
});
|
|
670
971
|
}
|
|
671
972
|
};
|
|
672
973
|
var StartupPhase = /* @__PURE__ */ (function(StartupPhase2) {
|
|
@@ -710,16 +1011,16 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
710
1011
|
}
|
|
711
1012
|
canAddTask() {
|
|
712
1013
|
if (this.hasStarted) {
|
|
713
|
-
throw new
|
|
1014
|
+
throw new SeedcordError(SeedcordErrorCode.LifecycleAddAfterCompletion);
|
|
714
1015
|
}
|
|
715
1016
|
if (this.isStartingUp) {
|
|
716
|
-
throw new
|
|
1017
|
+
throw new SeedcordError(SeedcordErrorCode.LifecycleAddDuringRun);
|
|
717
1018
|
}
|
|
718
1019
|
return true;
|
|
719
1020
|
}
|
|
720
1021
|
canRemoveTask() {
|
|
721
1022
|
if (this.isStartingUp) {
|
|
722
|
-
throw new
|
|
1023
|
+
throw new SeedcordError(SeedcordErrorCode.LifecycleRemoveDuringRun);
|
|
723
1024
|
}
|
|
724
1025
|
return true;
|
|
725
1026
|
}
|
|
@@ -769,15 +1070,15 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
769
1070
|
return;
|
|
770
1071
|
}
|
|
771
1072
|
this.isStartingUp = true;
|
|
772
|
-
this.logger.info(`${
|
|
1073
|
+
this.logger.info(`${chalk2.bold.green("Starting")} coordinated startup sequence`);
|
|
773
1074
|
this.emit("startup:start");
|
|
774
1075
|
try {
|
|
775
1076
|
for (const phase of PHASE_ORDER2) await this.runPhase(phase);
|
|
776
1077
|
this.hasStarted = true;
|
|
777
|
-
this.logger.info(`${
|
|
1078
|
+
this.logger.info(`${chalk2.bold.green("Coordinated startup completed")} successfully`);
|
|
778
1079
|
this.emit("startup:complete");
|
|
779
1080
|
} catch (error) {
|
|
780
|
-
this.logger.error(`${
|
|
1081
|
+
this.logger.error(`${chalk2.bold.red("Coordinated startup failed")}`);
|
|
781
1082
|
this.emit("startup:error", error);
|
|
782
1083
|
throw error;
|
|
783
1084
|
} finally {
|
|
@@ -810,6 +1111,6 @@ var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
|
810
1111
|
}
|
|
811
1112
|
};
|
|
812
1113
|
|
|
813
|
-
export { CooldownManager, CoordinatedLifecycle, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Logger, ShutdownPhase, StartupPhase };
|
|
1114
|
+
export { CooldownManager, CoordinatedLifecycle, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Logger, SeedcordError, SeedcordErrorCode, SeedcordErrors, SeedcordRangeError, SeedcordTypeError, ShutdownPhase, StartupPhase, StrictEventEmitter, formatSeedcordErrorMessage, isSeedcordError, messages as seedcordErrorMessages };
|
|
814
1115
|
//# sourceMappingURL=index.mjs.map
|
|
815
1116
|
//# sourceMappingURL=index.mjs.map
|