@seedcord/services 0.6.0 → 0.7.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 CHANGED
@@ -1,1136 +1,1518 @@
1
- 'use strict';
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_SeedcordError = require('./SeedcordError-wt7_KePP.cjs');
3
+ let envapt = require("envapt");
4
+ let node_path = require("node:path");
5
+ node_path = require_SeedcordError.__toESM(node_path, 1);
6
+ let winston = require("winston");
7
+ winston = require_SeedcordError.__toESM(winston, 1);
8
+ let node_fs = require("node:fs");
9
+ node_fs = require_SeedcordError.__toESM(node_fs, 1);
10
+ let strip_ansi = require("strip-ansi");
11
+ strip_ansi = require_SeedcordError.__toESM(strip_ansi, 1);
12
+ let winston_transport = require("winston-transport");
13
+ winston_transport = require_SeedcordError.__toESM(winston_transport, 1);
14
+ let _seedcord_utils = require("@seedcord/utils");
15
+ let chalk = require("chalk");
16
+ chalk = require_SeedcordError.__toESM(chalk, 1);
17
+ let http = require("http");
18
+ let node_events = require("node:events");
2
19
 
3
- var envapt = require('envapt');
4
- var winston = require('winston');
5
- var chalk2 = require('chalk');
6
- var http = require('http');
7
- var events = require('events');
8
-
9
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
-
11
- var chalk2__default = /*#__PURE__*/_interopDefault(chalk2);
12
-
13
- var __defProp = Object.defineProperty;
14
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
- var Logger = class _Logger {
16
- static {
17
- __name(this, "Logger");
18
- }
19
- static instances = /* @__PURE__ */ new Map();
20
- static instance(prefix) {
21
- let instance = this.instances.get(prefix);
22
- if (!instance) {
23
- instance = new _Logger(prefix);
24
- this.instances.set(prefix, instance);
25
- }
26
- return instance;
27
- }
28
- constructor(transportName) {
29
- const consoleTransport = this.createConsoleTransport(transportName);
30
- this.initializeLogger(consoleTransport);
31
- }
32
- getFormatCustomizations() {
33
- const padding = 7;
34
- return [
35
- winston.format.errors({
36
- stack: true
37
- }),
38
- winston.format.splat(),
39
- winston.format.colorize({
40
- level: true
41
- }),
42
- winston.format.timestamp({
43
- format: "D MMM, hh:mm:ss a"
44
- }),
45
- winston.format.printf((info) => {
46
- const ts = String(info.timestamp ?? "");
47
- const lvl = String(info.level).padEnd(padding);
48
- const lbl = String(info.label ?? "");
49
- const msg = String(info.message ?? "");
50
- const base = `${ts} [${lvl}]: ${lbl} - ${msg}`;
51
- const splatSym = Symbol.for("splat");
52
- const raw = info[splatSym];
53
- const extras = Array.isArray(raw) ? raw : [];
54
- const cleaned = extras.filter((x) => !(x instanceof Error)).filter((x) => {
55
- if (!x) return false;
56
- if (typeof x !== "object") return true;
57
- return Object.keys(x).length > 0;
58
- });
59
- let rendered = base;
60
- if (typeof info.stack === "string") {
61
- rendered += `
62
- ${String(info.stack)}`;
63
- }
64
- if (cleaned.length) {
65
- const parts = [];
66
- for (const x of cleaned) {
67
- if (typeof x === "string") parts.push(x);
68
- else {
69
- try {
70
- parts.push(JSON.stringify(x, null, 2));
71
- } catch {
72
- parts.push(String(x));
73
- }
74
- }
75
- }
76
- rendered += `
77
- ${parts.join(" ")}`;
78
- }
79
- return rendered;
80
- })
81
- ];
82
- }
83
- createConsoleTransport(transportName) {
84
- return new winston.transports.Console({
85
- format: winston.format.combine(winston.format.label({
86
- label: transportName
87
- }), ...this.getFormatCustomizations()),
88
- level: envapt.Envapter.isDevelopment ? "silly" : envapt.Envapter.isStaging ? "debug" : "info"
89
- });
90
- }
91
- initializeLogger(consoleTransport) {
92
- const transportsArray = [
93
- consoleTransport
94
- ];
95
- if (envapt.Envapter.isDevelopment) {
96
- const maxSizeInMB = 10;
97
- transportsArray.push(new winston.transports.File({
98
- filename: "logs/application.log",
99
- level: "debug",
100
- format: winston.format.combine(winston.format.uncolorize(), winston.format.errors({
101
- stack: true
102
- }), winston.format.timestamp(), winston.format.json({
103
- bigint: true,
104
- space: 2
105
- })),
106
- maxsize: maxSizeInMB * 1024 * 1024,
107
- maxFiles: 5,
108
- tailable: true
109
- }));
110
- }
111
- this.logger = winston.createLogger({
112
- transports: transportsArray
113
- });
114
- }
115
- /**
116
- * Logs an error message with optional additional data.
117
- *
118
- * @param msg - The error message to log
119
- * @param args - Additional data to include in the log entry
120
- */
121
- error(msg, ...args) {
122
- this.logger.error(msg, ...args);
123
- }
124
- /**
125
- * Logs a warning message with optional additional data.
126
- *
127
- * @param msg - The warning message to log
128
- * @param args - Additional data to include in the log entry
129
- */
130
- warn(msg, ...args) {
131
- this.logger.warn(msg, ...args);
132
- }
133
- /**
134
- * Logs an informational message with optional additional data.
135
- *
136
- * @param msg - The informational message to log
137
- * @param args - Additional data to include in the log entry
138
- */
139
- info(msg, ...args) {
140
- this.logger.info(msg, ...args);
141
- }
142
- /**
143
- * Logs an HTTP-related message with optional additional data.
144
- *
145
- * @param msg - The HTTP message to log
146
- * @param args - Additional data to include in the log entry
147
- */
148
- http(msg, ...args) {
149
- this.logger.http(msg, ...args);
150
- }
151
- /**
152
- * Logs a verbose message with optional additional data.
153
- *
154
- * @param msg - The verbose message to log
155
- * @param args - Additional data to include in the log entry
156
- */
157
- verbose(msg, ...args) {
158
- this.logger.verbose(msg, ...args);
159
- }
160
- /**
161
- * Logs a debug message with optional additional data.
162
- *
163
- * @param msg - The debug message to log
164
- * @param args - Additional data to include in the log entry
165
- */
166
- debug(msg, ...args) {
167
- this.logger.debug(msg, ...args);
168
- }
169
- /**
170
- * Logs a silly/trace level message with optional additional data.
171
- *
172
- * @param msg - The silly message to log
173
- * @param args - Additional data to include in the log entry
174
- */
175
- silly(msg, ...args) {
176
- this.logger.silly(msg, ...args);
177
- }
178
- /**
179
- * Static method to log an error message with a specific prefix.
180
- * Creates or retrieves a logger instance for the given prefix.
181
- *
182
- * @param prefix - The logger prefix/label to use
183
- * @param msg - The error message to log
184
- * @param args - Additional data to include in the log entry
185
- */
186
- static Error(prefix, msg, ...args) {
187
- const logger = this.instance(prefix);
188
- logger.error(msg, ...args);
189
- }
190
- /**
191
- * Static method to log an informational message with a specific prefix.
192
- * Creates or retrieves a logger instance for the given prefix.
193
- *
194
- * @param prefix - The logger prefix/label to use
195
- * @param msg - The informational message to log
196
- * @param args - Additional data to include in the log entry
197
- */
198
- static Info(prefix, msg, ...args) {
199
- const logger = this.instance(prefix);
200
- logger.info(msg, ...args);
201
- }
202
- /**
203
- * Static method to log a warning message with a specific prefix.
204
- * Creates or retrieves a logger instance for the given prefix.
205
- *
206
- * @param prefix - The logger prefix/label to use
207
- * @param msg - The warning message to log
208
- * @param args - Additional data to include in the log entry
209
- */
210
- static Warn(prefix, msg, ...args) {
211
- const logger = this.instance(prefix);
212
- logger.warn(msg, ...args);
213
- }
214
- /**
215
- * Static method to log a debug message with a specific prefix.
216
- * Creates or retrieves a logger instance for the given prefix.
217
- *
218
- * @param prefix - The logger prefix/label to use
219
- * @param msg - The debug message to log
220
- * @param args - Additional data to include in the log entry
221
- */
222
- static Debug(prefix, msg, ...args) {
223
- const logger = this.instance(prefix);
224
- logger.debug(msg, ...args);
225
- }
226
- /**
227
- * Static method to log a silly/trace level message with a specific prefix.
228
- * Creates or retrieves a logger instance for the given prefix.
229
- *
230
- * @param prefix - The logger prefix/label to use
231
- * @param msg - The silly message to log
232
- * @param args - Additional data to include in the log entry
233
- */
234
- static Silly(prefix, msg, ...args) {
235
- const logger = this.instance(prefix);
236
- logger.silly(msg, ...args);
237
- }
20
+ //#region src/Logger/LogFormatter.ts
21
+ /**
22
+ * Formats log records for console and file outputs.
23
+ *
24
+ * Supports pretty-printed colored logs for development
25
+ * and JSON logs for production with optional ANSI stripping.
26
+ * @internal
27
+ */
28
+ var LogFormatter = class {
29
+ DEFAULT_PADDING = 7;
30
+ SPLAT = Symbol.for("splat");
31
+ safeString(value) {
32
+ if (typeof value === "string") return value;
33
+ if (value === void 0 || value === null) return "";
34
+ if (typeof value.toString === "function") return String(value.toString());
35
+ if (typeof value === "object") try {
36
+ return JSON.stringify(value);
37
+ } catch {
38
+ return "";
39
+ }
40
+ return "";
41
+ }
42
+ sanitizeAnsi(value) {
43
+ if (typeof value === "string") return (0, strip_ansi.default)(value);
44
+ if (value instanceof Error) {
45
+ const error = value;
46
+ const sanitized = new Error((0, strip_ansi.default)(error.message));
47
+ sanitized.name = error.name;
48
+ if (typeof error.stack === "string") sanitized.stack = (0, strip_ansi.default)(error.stack);
49
+ return sanitized;
50
+ }
51
+ return value;
52
+ }
53
+ getExtras(info) {
54
+ const raw = info[this.SPLAT];
55
+ return Array.isArray(raw) ? raw : [];
56
+ }
57
+ FORMAT_SPECIFIERS = /%[sdifjoO]/gu;
58
+ HAD_FORMAT_KEY = Symbol.for("hadFormatSpecifiers");
59
+ SAVED_SPLAT_KEY = Symbol.for("savedSplat");
60
+ markFormatSpecifiers() {
61
+ return (0, winston.format)((info) => {
62
+ const msg = typeof info.message === "string" ? info.message : "";
63
+ const extras = this.getExtras(info);
64
+ const matches = msg.match(this.FORMAT_SPECIFIERS);
65
+ const formatCount = matches ? matches.length : 0;
66
+ info[this.HAD_FORMAT_KEY] = formatCount;
67
+ info[this.SAVED_SPLAT_KEY] = [...extras];
68
+ return info;
69
+ })();
70
+ }
71
+ createPreFormat() {
72
+ return this.markFormatSpecifiers();
73
+ }
74
+ preserveErrorFormatting() {
75
+ return (0, winston.format)((info) => {
76
+ const extras = this.getExtras(info);
77
+ for (const item of extras) if (item instanceof Error && /\u001b/.test(item.name)) {
78
+ const originalName = item.name;
79
+ const plainName = (0, strip_ansi.default)(item.name);
80
+ const formatted = item;
81
+ formatted.__formattedName = originalName;
82
+ formatted.__plainName = plainName;
83
+ }
84
+ return info;
85
+ })();
86
+ }
87
+ restoreErrorFormatting() {
88
+ return (0, winston.format)((info) => {
89
+ if (typeof info.stack === "string") {
90
+ const extras = this.getExtras(info);
91
+ for (const item of extras) if (item instanceof Error) {
92
+ const { __formattedName: formattedName, __plainName: plainName } = item;
93
+ if (typeof formattedName === "string" && typeof plainName === "string") info.stack = info.stack.replace(new RegExp(`^${this.escapeRegex(plainName)}`, "m"), formattedName);
94
+ }
95
+ }
96
+ return info;
97
+ })();
98
+ }
99
+ escapeRegex(str) {
100
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
101
+ }
102
+ /**
103
+ * Creates pretty-printed format with colors and timestamps.
104
+ *
105
+ * Ideal for development environments where human readability matters.
106
+ *
107
+ * @param options - Formatting options including padding and ANSI stripping
108
+ * @returns Array of Winston format transformers
109
+ */
110
+ pretty(options = {}) {
111
+ const padding = options.padding ?? this.DEFAULT_PADDING;
112
+ return [
113
+ this.preserveErrorFormatting(),
114
+ winston.format.errors({ stack: true }),
115
+ this.restoreErrorFormatting(),
116
+ winston.format.splat(),
117
+ winston.format.colorize({ level: true }),
118
+ winston.format.timestamp({ format: "D MMM, hh:mm:ss a" }),
119
+ winston.format.printf((info) => {
120
+ let ts = this.safeString(info.timestamp);
121
+ let lvl = this.safeString(info.level).padEnd(padding);
122
+ let lbl = this.safeString(info.label);
123
+ let msg = this.safeString(info.message);
124
+ if (options.stripExtras) {
125
+ ts = (0, strip_ansi.default)(ts);
126
+ lvl = (0, strip_ansi.default)(lvl);
127
+ lbl = (0, strip_ansi.default)(lbl);
128
+ msg = (0, strip_ansi.default)(msg);
129
+ }
130
+ const base = `${ts} [${lvl}]: ${lbl} - ${msg}`;
131
+ const savedExtras = info[this.SAVED_SPLAT_KEY];
132
+ const extras = Array.isArray(savedExtras) ? savedExtras : this.getExtras(info);
133
+ let rendered = base;
134
+ if (typeof info.stack === "string") {
135
+ let stack = this.safeString(info.stack);
136
+ if (options.stripExtras) stack = (0, strip_ansi.default)(stack);
137
+ rendered += `\n${stack}`;
138
+ }
139
+ const cleaned = options.stripExtras ? extras.map((entry) => this.sanitizeAnsi(entry)) : extras;
140
+ const rawFormatCount = info[this.HAD_FORMAT_KEY];
141
+ const formatSpecifierCount = typeof rawFormatCount === "number" ? rawFormatCount : 0;
142
+ const filtered = cleaned.filter((x, index) => {
143
+ if (x === null || x === void 0) return false;
144
+ if (x instanceof Error && typeof info.stack === "string") return false;
145
+ if (typeof x !== "object") return index >= formatSpecifierCount;
146
+ return Object.keys(x).length > 0;
147
+ });
148
+ if (filtered.length) {
149
+ const primitives = [];
150
+ const objects = [];
151
+ for (const x of filtered) if (typeof x === "string" || typeof x === "number" || typeof x === "boolean") primitives.push(String(x));
152
+ else try {
153
+ objects.push(JSON.stringify(x, null, 2));
154
+ } catch {
155
+ objects.push(String(x));
156
+ }
157
+ if (primitives.length) rendered += ` ${primitives.join(" ")}`;
158
+ if (objects.length) rendered += `\n${objects.join("\n")}`;
159
+ }
160
+ return rendered;
161
+ })
162
+ ];
163
+ }
164
+ /**
165
+ * Creates JSON format for structured logging.
166
+ *
167
+ * Best for production environments where logs are parsed by tools.
168
+ *
169
+ * @param options - JSON formatting options including ANSI stripping and minimal mode
170
+ * @returns Array of Winston format transformers
171
+ */
172
+ json(options = {}) {
173
+ const base = [winston.format.timestamp(), winston.format.errors({ stack: true })];
174
+ if (options.stripAnsi) base.push((0, winston.format)((info) => {
175
+ info.message = typeof info.message === "string" ? (0, strip_ansi.default)(info.message) : info.message;
176
+ if (typeof info.stack === "string") info.stack = (0, strip_ansi.default)(info.stack);
177
+ const extras = this.getExtras(info);
178
+ if (extras.length) info.extras = extras.map((entry) => this.sanitizeAnsi(entry));
179
+ return info;
180
+ })());
181
+ base.push(options.minimal ? winston.format.json({}) : winston.format.json({
182
+ bigint: true,
183
+ space: 0
184
+ }));
185
+ return base;
186
+ }
238
187
  };
239
188
 
240
- // src/CooldownManager.ts
241
- var CooldownManager = class {
242
- static {
243
- __name(this, "CooldownManager");
244
- }
245
- window;
246
- Err;
247
- msg;
248
- map = /* @__PURE__ */ new Map();
249
- /**
250
- * Creates a new CooldownManager instance.
251
- *
252
- * @param opts - Configuration options for the cooldown behavior
253
- */
254
- constructor(opts = {}) {
255
- this.window = opts.cooldown ?? 1e3;
256
- this.Err = opts.err ?? Error;
257
- this.msg = opts.message ?? "Cooldown active";
258
- }
259
- /**
260
- * Records usage timestamp for a key without any cooldown checks.
261
- *
262
- * @param key - The unique identifier for the cooldown entry
263
- */
264
- set(key) {
265
- this.map.set(key, Date.now());
266
- }
267
- /**
268
- * Verifies cooldown status for a key and updates timestamp if not active.
269
- *
270
- * If the cooldown is still active, throws the configured error.
271
- * If not active, updates the timestamp and returns successfully.
272
- *
273
- * @param key - The unique identifier to check cooldown for
274
- * @throws An {@link Err} When the cooldown is still active for the given key
275
- */
276
- check(key) {
277
- const now = Date.now();
278
- const last = this.map.get(key);
279
- const remaining = this.window - (now - (last ?? 0));
280
- if (envapt.Envapter.isDevelopment && remaining > 0) {
281
- Logger.Debug("CooldownManager", `${key} - ${remaining}ms remaining`);
282
- }
283
- if (last !== void 0 && remaining > 0) {
284
- throw new this.Err(this.msg, remaining);
285
- }
286
- this.map.set(key, now);
287
- }
288
- /**
289
- * Checks if a key is currently cooling down without updating timestamp.
290
- *
291
- * @param key - The unique identifier to check
292
- * @returns True if the key is still cooling down, false otherwise
293
- */
294
- isActive(key) {
295
- const last = this.map.get(key);
296
- return last !== void 0 && Date.now() - last < this.window;
297
- }
298
- /**
299
- * Removes a key from the cooldown map.
300
- *
301
- * @param key - The unique identifier to remove (useful for manual resets)
302
- */
303
- clear(key) {
304
- this.map.delete(key);
305
- }
189
+ //#endregion
190
+ //#region src/Logger/Transports/SinkTransport.ts
191
+ /**
192
+ * Winston transport that forwards logs to custom sinks.
193
+ * @internal
194
+ */
195
+ var SinkTransport = class extends winston_transport.default {
196
+ channelName;
197
+ sink;
198
+ constructor(options) {
199
+ super(options);
200
+ this.channelName = options.channel;
201
+ this.sink = options.sink;
202
+ }
203
+ log(info, callback) {
204
+ setImmediate(() => this.emit("logged", info));
205
+ const rendered = this.resolveRendered(info);
206
+ const channel = this.resolveChannel(info);
207
+ this.sink.onLog({
208
+ channel,
209
+ rendered,
210
+ info
211
+ });
212
+ callback();
213
+ }
214
+ resolveChannel(info) {
215
+ const channel = info.channel;
216
+ return typeof channel === "string" ? channel : this.channelName;
217
+ }
218
+ resolveRendered(info) {
219
+ const msg = info[Symbol.for("message")];
220
+ if (typeof msg === "string") return msg;
221
+ const fallback = info.message;
222
+ if (typeof fallback === "string") return fallback;
223
+ if (fallback instanceof Error) return fallback.stack ?? fallback.message;
224
+ return String(fallback ?? "");
225
+ }
306
226
  };
307
227
 
308
- // src/Errors/ErrorCodes.ts
309
- var SeedcordErrorCode = /* @__PURE__ */ (function(SeedcordErrorCode2) {
310
- SeedcordErrorCode2[SeedcordErrorCode2["ConfigMissingDiscordToken"] = 1001] = "ConfigMissingDiscordToken";
311
- SeedcordErrorCode2[SeedcordErrorCode2["ConfigUnknownExceptionWebhookMissing"] = 1002] = "ConfigUnknownExceptionWebhookMissing";
312
- SeedcordErrorCode2[SeedcordErrorCode2["ConfigUnknownExceptionWebhookInvalid"] = 1003] = "ConfigUnknownExceptionWebhookInvalid";
313
- SeedcordErrorCode2[SeedcordErrorCode2["LifecycleAddAfterCompletion"] = 1101] = "LifecycleAddAfterCompletion";
314
- SeedcordErrorCode2[SeedcordErrorCode2["LifecycleAddDuringRun"] = 1102] = "LifecycleAddDuringRun";
315
- SeedcordErrorCode2[SeedcordErrorCode2["LifecycleRemoveDuringRun"] = 1103] = "LifecycleRemoveDuringRun";
316
- SeedcordErrorCode2[SeedcordErrorCode2["LifecycleUnknownPhase"] = 1104] = "LifecycleUnknownPhase";
317
- SeedcordErrorCode2[SeedcordErrorCode2["LifecyclePhaseFailures"] = 1105] = "LifecyclePhaseFailures";
318
- SeedcordErrorCode2[SeedcordErrorCode2["LifecycleTaskTimeout"] = 1106] = "LifecycleTaskTimeout";
319
- SeedcordErrorCode2[SeedcordErrorCode2["CoreSingletonViolation"] = 1201] = "CoreSingletonViolation";
320
- SeedcordErrorCode2[SeedcordErrorCode2["CorePluginAfterInit"] = 1202] = "CorePluginAfterInit";
321
- SeedcordErrorCode2[SeedcordErrorCode2["CorePluginKeyExists"] = 1203] = "CorePluginKeyExists";
322
- SeedcordErrorCode2[SeedcordErrorCode2["CoreBotRoleMissing"] = 1204] = "CoreBotRoleMissing";
323
- SeedcordErrorCode2[SeedcordErrorCode2["DecoratorInteractionEventFilter"] = 1301] = "DecoratorInteractionEventFilter";
324
- SeedcordErrorCode2[SeedcordErrorCode2["DecoratorMethodNotFound"] = 1302] = "DecoratorMethodNotFound";
325
- SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandAlreadyRegistered"] = 1303] = "DecoratorCommandAlreadyRegistered";
326
- SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandGlobalWithGuilds"] = 1304] = "DecoratorCommandGlobalWithGuilds";
327
- SeedcordErrorCode2[SeedcordErrorCode2["DecoratorCommandGuildWithoutGuilds"] = 1305] = "DecoratorCommandGuildWithoutGuilds";
328
- SeedcordErrorCode2[SeedcordErrorCode2["DecoratorInvalidMiddlewarePriority"] = 1306] = "DecoratorInvalidMiddlewarePriority";
329
- SeedcordErrorCode2[SeedcordErrorCode2["UtilHexInputType"] = 1401] = "UtilHexInputType";
330
- SeedcordErrorCode2[SeedcordErrorCode2["UtilHexInvalid"] = 1402] = "UtilHexInvalid";
331
- SeedcordErrorCode2[SeedcordErrorCode2["UtilInvalidSlashRouteArgument"] = 1403] = "UtilInvalidSlashRouteArgument";
332
- SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoServiceDecoratorMissing"] = 2101] = "PluginMongoServiceDecoratorMissing";
333
- SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoModelDecoratorMissing"] = 2102] = "PluginMongoModelDecoratorMissing";
334
- SeedcordErrorCode2[SeedcordErrorCode2["PluginMongoConnectionFailed"] = 2103] = "PluginMongoConnectionFailed";
335
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgServiceDecoratorMissing"] = 2201] = "PluginKpgServiceDecoratorMissing";
336
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgServiceTableMissing"] = 2202] = "PluginKpgServiceTableMissing";
337
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgInvalidStepCount"] = 2203] = "PluginKpgInvalidStepCount";
338
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgUnknownDirection"] = 2204] = "PluginKpgUnknownDirection";
339
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgUnresolvedMigrationsPath"] = 2205] = "PluginKpgUnresolvedMigrationsPath";
340
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgNoMigrationFiles"] = 2206] = "PluginKpgNoMigrationFiles";
341
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgInvalidMigrationModule"] = 2207] = "PluginKpgInvalidMigrationModule";
342
- SeedcordErrorCode2[SeedcordErrorCode2["PluginKpgNonErrorFailure"] = 2208] = "PluginKpgNonErrorFailure";
343
- return SeedcordErrorCode2;
344
- })({});
228
+ //#endregion
229
+ //#region src/Logger/TransportFactory.ts
230
+ /**
231
+ * Creates Winston transports with proper formatting and file path resolution.
232
+ *
233
+ * Builds console and file transports with environment-aware defaults,
234
+ * filename template expansion, and format selection.
235
+ * @internal
236
+ */
237
+ var TransportFactory = class {
238
+ formatter;
239
+ MILLISECOND_PAD = 3;
240
+ cachedSessionTimestamp = null;
241
+ constructor() {
242
+ this.formatter = new LogFormatter();
243
+ }
244
+ ensureDir(filepath) {
245
+ const dir = node_path.default.dirname(filepath);
246
+ if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
247
+ }
248
+ withDefaultLabel(label) {
249
+ return winston.format.combine((0, winston.format)((info) => {
250
+ info.label ??= label;
251
+ return info;
252
+ })());
253
+ }
254
+ buildPreFormat() {
255
+ return winston.format.combine(this.formatter.createPreFormat());
256
+ }
257
+ buildConsoleFormat(label) {
258
+ if (envapt.Envapter.isProduction) return winston.format.combine(this.withDefaultLabel(label), ...this.formatter.json({ stripAnsi: true }));
259
+ return winston.format.combine(this.withDefaultLabel(label), ...this.formatter.pretty());
260
+ }
261
+ buildFileFormat(label, mode, stripAnsi) {
262
+ const formats = mode === "pretty" ? this.formatter.pretty({ stripExtras: stripAnsi }) : this.formatter.json({ stripAnsi });
263
+ return winston.format.combine(this.withDefaultLabel(label), ...formats);
264
+ }
265
+ buildStreamFormat(label, mode, stripAnsi) {
266
+ const formats = mode === "pretty" ? this.formatter.pretty({ stripExtras: stripAnsi }) : this.formatter.json({ stripAnsi });
267
+ return winston.format.combine(this.withDefaultLabel(label), ...formats);
268
+ }
269
+ pad(value) {
270
+ return value.toString().padStart(2, "0");
271
+ }
272
+ buildTimestamp() {
273
+ if (this.cachedSessionTimestamp) return this.cachedSessionTimestamp;
274
+ const now = /* @__PURE__ */ new Date();
275
+ const yyyy = now.getFullYear();
276
+ const mm = this.pad(now.getMonth() + 1);
277
+ const dd = this.pad(now.getDate());
278
+ const hh = this.pad(now.getHours());
279
+ const min = this.pad(now.getMinutes());
280
+ const ss = this.pad(now.getSeconds());
281
+ const ms = now.getMilliseconds().toString().padStart(this.MILLISECOND_PAD, "0");
282
+ const date = `${yyyy}-${mm}-${dd}`;
283
+ const timestamp = `${date}-${hh}${min}${ss}-${ms}`;
284
+ this.cachedSessionTimestamp = {
285
+ date,
286
+ timestamp
287
+ };
288
+ return this.cachedSessionTimestamp;
289
+ }
290
+ resolveFilename(template, channel) {
291
+ const { date, timestamp } = this.buildTimestamp();
292
+ return template.replaceAll("{channel}", channel).replaceAll("{date}", date).replaceAll("{timestamp}", timestamp);
293
+ }
294
+ /**
295
+ * Builds a Winston transport from configuration.
296
+ *
297
+ * Creates console, file, or stream transport with proper formatting,
298
+ * level filtering, and file rotation settings.
299
+ *
300
+ * @param input - Transport configuration parameters
301
+ * @returns Configured Winston transport instance
302
+ */
303
+ build(input) {
304
+ const effectiveFormat = input.config.format ?? input.defaultFormat;
305
+ const shouldStripAnsi = input.config.stripAnsi ?? input.stripAnsi;
306
+ const level = input.config.level ?? input.level;
307
+ if (input.config.type === "console") return new winston.transports.Console({
308
+ level,
309
+ format: this.buildConsoleFormat(input.label)
310
+ });
311
+ if (input.config.type === "stream") return new winston.transports.Stream({
312
+ level,
313
+ stream: input.config.stream,
314
+ format: this.buildStreamFormat(input.label, effectiveFormat, shouldStripAnsi)
315
+ });
316
+ const filenameTemplate = input.config.filename ?? "logs/application-{timestamp}.log";
317
+ const resolvedFilename = this.resolveFilename(filenameTemplate, input.channel);
318
+ this.ensureDir(resolvedFilename);
319
+ return new winston.transports.File({
320
+ level,
321
+ filename: resolvedFilename,
322
+ ...input.config.maxSize !== void 0 ? { maxsize: input.config.maxSize } : {},
323
+ ...input.config.maxFiles !== void 0 ? { maxFiles: input.config.maxFiles } : {},
324
+ tailable: true,
325
+ format: this.buildFileFormat(input.label, effectiveFormat, shouldStripAnsi)
326
+ });
327
+ }
328
+ /**
329
+ * Builds a sink transport for routing logs to custom destinations.
330
+ *
331
+ * @param input - Transport configuration with channel and level info
332
+ * @param sink - Custom sink to receive log entries
333
+ * @returns Configured sink transport instance
334
+ */
335
+ buildSinkTransport(input, sink) {
336
+ const format = this.buildConsoleFormat(input.label);
337
+ return new SinkTransport({
338
+ level: input.level,
339
+ channel: input.channel,
340
+ sink,
341
+ format
342
+ });
343
+ }
344
+ };
345
345
 
346
- // src/Errors/ErrorMessages.ts
347
- var messages = {
348
- [SeedcordErrorCode.ConfigMissingDiscordToken]: () => "Missing DISCORD_BOT_TOKEN environment variable.",
349
- [SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing]: () => "Missing UNKNOWN_EXCEPTION_WEBHOOK_URL environment variable.",
350
- [SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid]: () => "Invalid UNKNOWN_EXCEPTION_WEBHOOK_URL value.",
351
- [SeedcordErrorCode.LifecycleAddAfterCompletion]: () => "Cannot add tasks after startup sequence has already completed.",
352
- [SeedcordErrorCode.LifecycleAddDuringRun]: () => "Cannot add tasks while startup sequence is in progress.",
353
- [SeedcordErrorCode.LifecycleRemoveDuringRun]: () => "Cannot remove tasks while startup sequence is in progress.",
354
- [SeedcordErrorCode.LifecycleUnknownPhase]: (phase) => `Unknown phase: ${String(phase)}.`,
355
- [SeedcordErrorCode.LifecyclePhaseFailures]: (phase, failures) => `Phase ${phase} completed with ${failures} failed task${failures === 1 ? "" : "s"}.`,
356
- [SeedcordErrorCode.LifecycleTaskTimeout]: (taskName, timeout) => `Task "${taskName}" timed out after ${timeout}ms.`,
357
- [SeedcordErrorCode.CoreSingletonViolation]: () => "Seedcord can only be instantiated once. Use the existing instance instead.",
358
- [SeedcordErrorCode.CorePluginAfterInit]: () => "Cannot attach a plugin after initialization.",
359
- [SeedcordErrorCode.CorePluginKeyExists]: (key) => `Plugin with key "${key}" already exists.`,
360
- [SeedcordErrorCode.CoreBotRoleMissing]: (guildId) => guildId ? `Bot role not found in guild ${guildId}.` : "Bot role not found in guild.",
361
- [SeedcordErrorCode.DecoratorInteractionEventFilter]: () => "Interaction middleware cannot specify event filters.",
362
- [SeedcordErrorCode.DecoratorMethodNotFound]: () => "Decorator could not locate the original method. Ensure the method exists before applying the decorator.",
363
- [SeedcordErrorCode.DecoratorCommandAlreadyRegistered]: (commandName, existingScope, requestedScope) => `Command "${commandName}" is already registered as a "${existingScope}" command and cannot be re-registered as a "${requestedScope}" command.`,
364
- [SeedcordErrorCode.DecoratorCommandGlobalWithGuilds]: () => 'RegisterCommand("global") cannot have guilds specified.',
365
- [SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds]: () => 'RegisterCommand("guild") requires a non-empty guilds array.',
366
- [SeedcordErrorCode.DecoratorInvalidMiddlewarePriority]: () => "Middleware priority must be a finite number.",
367
- [SeedcordErrorCode.UtilHexInputType]: () => "hexToNumber expects a string input.",
368
- [SeedcordErrorCode.UtilHexInvalid]: () => "Invalid hex string.",
369
- [SeedcordErrorCode.UtilInvalidSlashRouteArgument]: () => "Invalid argument passed to buildSlashRoute.",
370
- [SeedcordErrorCode.PluginMongoServiceDecoratorMissing]: (className) => `Missing @RegisterMongoService on ${className}.`,
371
- [SeedcordErrorCode.PluginMongoModelDecoratorMissing]: (className) => `Missing @RegisterMongoModel on ${className}.`,
372
- [SeedcordErrorCode.PluginMongoConnectionFailed]: (databaseName) => databaseName ? `Could not connect to MongoDB (${databaseName}).` : "Could not connect to MongoDB.",
373
- [SeedcordErrorCode.PluginKpgServiceDecoratorMissing]: (className) => `Missing @RegisterKpgService on ${className}.`,
374
- [SeedcordErrorCode.PluginKpgServiceTableMissing]: (className) => `Missing table metadata for ${className}. Provide a table via @RegisterKpgService().`,
375
- [SeedcordErrorCode.PluginKpgInvalidStepCount]: () => "Migration step count must be a non-negative integer.",
376
- [SeedcordErrorCode.PluginKpgUnknownDirection]: (direction) => `Unknown migration direction: ${String(direction)}.`,
377
- [SeedcordErrorCode.PluginKpgUnresolvedMigrationsPath]: (label) => `Unable to resolve migrations at path: ${label}.`,
378
- [SeedcordErrorCode.PluginKpgNoMigrationFiles]: () => "No migration files provided.",
379
- [SeedcordErrorCode.PluginKpgInvalidMigrationModule]: (filePath) => `Migration file ${filePath} must export async functions up and down.`,
380
- [SeedcordErrorCode.PluginKpgNonErrorFailure]: (message) => `Migration failure: ${message}.`
346
+ //#endregion
347
+ //#region src/Logger/LoggerChannelRegistry.ts
348
+ /**
349
+ * Manages Winston logger instances per channel with caching.
350
+ *
351
+ * Stores channel configuration and creates transports for each channel
352
+ * with environment-aware defaults.
353
+ */
354
+ var LoggerChannelRegistry = class LoggerChannelRegistry {
355
+ static _instance = null;
356
+ nextSinkId = 1;
357
+ sinks = /* @__PURE__ */ new Map();
358
+ DEFAULT_LEVEL = envapt.Envapter.isDevelopment ? "silly" : envapt.Envapter.isStaging ? "debug" : "info";
359
+ config = {
360
+ defaultChannel: "default",
361
+ channels: {},
362
+ files: {
363
+ maxSizeMB: 10,
364
+ maxFiles: 5,
365
+ patterns: {
366
+ dev: "logs/{channel}-{timestamp}.log",
367
+ staging: "logs/staging-{date}-{timestamp}.jsonl",
368
+ prod: "logs/production-{date}.jsonl"
369
+ }
370
+ }
371
+ };
372
+ FORMAT = envapt.Envapter.isDevelopment ? "pretty" : "json";
373
+ cache = /* @__PURE__ */ new Map();
374
+ transportFactory;
375
+ constructor() {
376
+ this.transportFactory = new TransportFactory();
377
+ }
378
+ /**
379
+ * Gets the singleton instance of the registry.
380
+ */
381
+ static get instance() {
382
+ return this._instance ??= new LoggerChannelRegistry();
383
+ }
384
+ getDefaultChannelConfig(name) {
385
+ return {
386
+ name,
387
+ level: this.DEFAULT_LEVEL,
388
+ transports: [{
389
+ type: "console",
390
+ level: this.DEFAULT_LEVEL,
391
+ format: this.FORMAT,
392
+ stripAnsi: !envapt.Envapter.isDevelopment
393
+ }, {
394
+ type: "file",
395
+ level: this.DEFAULT_LEVEL,
396
+ filename: envapt.Envapter.isDevelopment ? this.config.files.patterns.dev : envapt.Envapter.isStaging ? this.config.files.patterns.staging : this.config.files.patterns.prod,
397
+ format: this.FORMAT,
398
+ stripAnsi: true,
399
+ maxSize: this.config.files.maxSizeMB * 1024 * 1024,
400
+ maxFiles: this.config.files.maxFiles
401
+ }]
402
+ };
403
+ }
404
+ mergeChannelConfig(base, override) {
405
+ if (!override) return base;
406
+ const level = override.level ?? base.level;
407
+ const stripAnsi = override.stripAnsi ?? base.stripAnsi;
408
+ const format = override.format ?? base.format;
409
+ const transports = override.transports ?? base.transports;
410
+ return {
411
+ name: override.name || base.name,
412
+ ...level !== void 0 ? { level } : {},
413
+ ...stripAnsi !== void 0 ? { stripAnsi } : {},
414
+ ...format !== void 0 ? { format } : {},
415
+ ...transports !== void 0 ? { transports } : {}
416
+ };
417
+ }
418
+ /**
419
+ * Updates global logger configuration and clears cache.
420
+ *
421
+ * @param config - Partial configuration to merge with existing settings
422
+ */
423
+ configure(config) {
424
+ this.disposeCachedLoggers();
425
+ this.config = {
426
+ ...this.config,
427
+ ...config,
428
+ channels: {
429
+ ...this.config.channels,
430
+ ...config.channels ?? {}
431
+ }
432
+ };
433
+ this.cache.clear();
434
+ }
435
+ disposeCachedLoggers() {
436
+ for (const logger of this.cache.values()) for (const transport of [...logger.transports]) {
437
+ logger.remove(transport);
438
+ transport.close?.();
439
+ }
440
+ for (const record of this.sinks.values()) {
441
+ record.transportsByChannel.clear();
442
+ record.removedConsoleByChannel.clear();
443
+ }
444
+ }
445
+ /**
446
+ * Returns the name of the default channel.
447
+ */
448
+ getDefaultChannel() {
449
+ return this.config.defaultChannel;
450
+ }
451
+ /**
452
+ * Returns a list of all known channels (configured or cached).
453
+ */
454
+ getChannels() {
455
+ const configuredChannels = Object.keys(this.config.channels);
456
+ const cachedChannels = Array.from(this.cache.keys());
457
+ const allChannels = new Set([
458
+ ...configuredChannels,
459
+ ...cachedChannels,
460
+ this.config.defaultChannel
461
+ ]);
462
+ return Array.from(allChannels).sort();
463
+ }
464
+ /**
465
+ * Gets the log file path for a specific channel, if one exists.
466
+ *
467
+ * @param channel - Channel name to get log file path for
468
+ * @returns The log file path, or null if no file transport exists
469
+ */
470
+ getLogFilePath(channel) {
471
+ const logger = this.cache.get(channel);
472
+ if (!logger) return null;
473
+ for (const transport of logger.transports) if (transport instanceof winston.default.transports.File) return node_path.default.resolve(transport.dirname, transport.filename);
474
+ return null;
475
+ }
476
+ /**
477
+ * Gets or creates a Winston logger instance for the given channel.
478
+ *
479
+ * @param channel - Channel name to get logger for
480
+ * @returns Configured Winston logger instance
481
+ */
482
+ get(channel) {
483
+ const cached = this.cache.get(channel);
484
+ if (cached) return cached;
485
+ const channelConfig = this.mergeChannelConfig(this.getDefaultChannelConfig(channel), this.config.channels[channel]);
486
+ const effectiveLevel = channelConfig.level ?? this.DEFAULT_LEVEL;
487
+ const consoleMuted = this.isConsoleMuted();
488
+ const transports = (channelConfig.transports ?? []).filter((transportConfig) => !(consoleMuted && transportConfig.type === "console")).map((transportConfig) => this.transportFactory.build({
489
+ channel,
490
+ label: channel,
491
+ level: effectiveLevel,
492
+ config: transportConfig,
493
+ defaultFormat: channelConfig.format ?? "pretty",
494
+ stripAnsi: channelConfig.stripAnsi ?? true
495
+ }));
496
+ const logger = (0, winston.createLogger)({
497
+ level: effectiveLevel,
498
+ format: this.transportFactory.buildPreFormat(),
499
+ transports
500
+ });
501
+ for (const entry of this.sinks.values()) this.applySinkToCachedLogger(channel, logger, entry);
502
+ this.cache.set(channel, logger);
503
+ return logger;
504
+ }
505
+ isConsoleMuted() {
506
+ for (const entry of this.sinks.values()) if (entry.muteConsole) return true;
507
+ return false;
508
+ }
509
+ /**
510
+ * Installs a custom sink to capture log output across all channels.
511
+ *
512
+ * Useful for routing logs to custom destinations like TUIs or remote services.
513
+ *
514
+ * @param sink - Custom sink implementation to receive log entries
515
+ * @param options - Optional configuration for console muting behavior
516
+ * @returns Handle to dispose the sink when no longer needed
517
+ */
518
+ installSink(sink, options) {
519
+ const id = this.nextSinkId;
520
+ this.nextSinkId += 1;
521
+ const record = {
522
+ sink,
523
+ muteConsole: options?.muteConsole ?? true,
524
+ transportsByChannel: /* @__PURE__ */ new Map(),
525
+ removedConsoleByChannel: /* @__PURE__ */ new Map()
526
+ };
527
+ this.sinks.set(id, record);
528
+ for (const [channel, logger] of this.cache.entries()) this.applySinkToCachedLogger(channel, logger, record);
529
+ return new class {
530
+ registry;
531
+ key;
532
+ constructor(registry, key) {
533
+ this.registry = registry;
534
+ this.key = key;
535
+ }
536
+ dispose() {
537
+ this.registry.uninstallSink(this.key);
538
+ }
539
+ }(this, id);
540
+ }
541
+ uninstallSink(id) {
542
+ const record = this.sinks.get(id);
543
+ if (!record) return;
544
+ for (const [channel, logger] of this.cache.entries()) {
545
+ const sinkTransport = record.transportsByChannel.get(channel);
546
+ if (sinkTransport) logger.remove(sinkTransport);
547
+ const removed = record.removedConsoleByChannel.get(channel);
548
+ if (removed?.length) for (const t of removed) logger.add(t);
549
+ }
550
+ this.sinks.delete(id);
551
+ }
552
+ applySinkToCachedLogger(channel, logger, record) {
553
+ if (record.muteConsole) {
554
+ const removed = [];
555
+ for (const t of logger.transports) if (t instanceof winston.default.transports.Console) removed.push(t);
556
+ if (removed.length) {
557
+ for (const t of removed) logger.remove(t);
558
+ record.removedConsoleByChannel.set(channel, removed);
559
+ }
560
+ }
561
+ const sinkTransport = this.transportFactory.buildSinkTransport({
562
+ channel,
563
+ label: channel,
564
+ level: logger.level
565
+ }, record.sink);
566
+ logger.add(sinkTransport);
567
+ record.transportsByChannel.set(channel, sinkTransport);
568
+ }
381
569
  };
382
- function formatSeedcordErrorMessage(code, args) {
383
- const formatter = messages[code];
384
- const resolvedArgs = args ?? [];
385
- return formatter(...resolvedArgs);
386
- }
387
- __name(formatSeedcordErrorMessage, "formatSeedcordErrorMessage");
388
- function resolveIdentifier(code) {
389
- return SeedcordErrorCode[code];
390
- }
391
- __name(resolveIdentifier, "resolveIdentifier");
392
- function resolveMessage(code, args) {
393
- return formatSeedcordErrorMessage(code, args);
394
- }
395
- __name(resolveMessage, "resolveMessage");
396
- function formatErrorName(name, _identifier, code) {
397
- return `${chalk2__default.default.bold.red(name)}[${chalk2__default.default.gray(code)}]`;
398
- }
399
- __name(formatErrorName, "formatErrorName");
400
- var SeedcordError = class extends Error {
401
- static {
402
- __name(this, "SeedcordError");
403
- }
404
- code;
405
- identifier;
406
- constructor(code, args, options) {
407
- const message = resolveMessage(code, args);
408
- super(message, options);
409
- this.code = code;
410
- this.identifier = resolveIdentifier(code);
411
- this.name = formatErrorName(new.target.name, this.identifier, this.code);
412
- Object.setPrototypeOf(this, new.target.prototype);
413
- if (typeof Error.captureStackTrace === "function") {
414
- Error.captureStackTrace(this, new.target);
415
- }
416
- }
570
+
571
+ //#endregion
572
+ //#region src/Logger/LoggerUtilities.ts
573
+ /**
574
+ * Provides access to common logging utilities.
575
+ */
576
+ var LoggerUtilities = class {
577
+ logger;
578
+ constructor(logger) {
579
+ this.logger = logger;
580
+ }
581
+ arrow(text) {
582
+ return `${chalk.default.gray("→")} ${text}`;
583
+ }
584
+ /**
585
+ * Logs a single item with an arrow prefix.
586
+ * @param text - The text to log
587
+ */
588
+ item(text, level = "info") {
589
+ this.logger[level](this.arrow(text));
590
+ }
591
+ /**
592
+ * Logs a list of items with optional heading.
593
+ *
594
+ * @param items - Array of items to log as a list
595
+ * @param heading - Optional heading to display above the list
596
+ */
597
+ list(items, heading, level = "info") {
598
+ if (heading) this.logger[level](heading);
599
+ for (const item of items) this.logger[level](this.arrow(item));
600
+ }
601
+ /**
602
+ * Logs a summary with title and key-value pairs.
603
+ * Example: "Loaded: 5 handlers, 3 commands"
604
+ *
605
+ * @param title - The title of the summary
606
+ * @param items - Object with counts/values to display
607
+ */
608
+ summary(title, items, level = "info") {
609
+ const entries = Object.entries(items).map(([key, value]) => `${chalk.default.magenta.bold(String(value))} ${key}`);
610
+ this.logger[level](`${chalk.default.bold.green(title)}: ${entries.join(", ")}`);
611
+ }
612
+ /**
613
+ * Logs a component registration message.
614
+ *
615
+ * @param name - Name of the component being registered
616
+ * @param from - File path the component is from
617
+ * @param type - Optional type label (e.g., 'middleware', 'handler')
618
+ */
619
+ registration(name, from, type, level = "info") {
620
+ const scope = type ? `${type} ` : "";
621
+ this.logger[level](`${chalk.default.italic("Registered")} ${chalk.default.bold.yellow(scope)}${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(from))}`);
622
+ }
623
+ /**
624
+ * Logs component initialization start/end.
625
+ *
626
+ * @param component - Name of the component
627
+ * @param action - 'start' or 'end' to indicate initialization phase
628
+ */
629
+ initialization(component, action, level = "info") {
630
+ const verb = action === "start" ? "Initializing" : "Initialized";
631
+ this.logger[level](chalk.default.bold(`${verb} ${component}`));
632
+ }
633
+ /**
634
+ * Logs progress as "[current/total]" with optional item label.
635
+ *
636
+ * @param current - Current progress count
637
+ * @param total - Total count
638
+ * @param item - Optional item label to append
639
+ */
640
+ progress(current, total, item, level = "info") {
641
+ const base = `[${current}/${total}]`;
642
+ const suffix = item ? ` ${item}` : "";
643
+ this.logger[level](`${chalk.default.cyan(base)}${suffix}`);
644
+ }
645
+ /**
646
+ * Logs content in a decorative box.
647
+ *
648
+ * @param title - Title to display in the box
649
+ * @param content - Lines of content to display in the box
650
+ */
651
+ box(title, content, level = "info") {
652
+ const width = Math.max(title.length, ...content.map((line) => line.length)) + 2;
653
+ const horizontal = "─".repeat(width);
654
+ this.logger[level](`╭${horizontal}╮`);
655
+ this.logger[level](`│ ${title.padEnd(width - 1, " ")}│`);
656
+ for (const line of content) this.logger[level](`│ ${line.padEnd(width - 1, " ")}│`);
657
+ this.logger[level](`╰${horizontal}╯`);
658
+ }
417
659
  };
418
- var SeedcordTypeError = class extends TypeError {
419
- static {
420
- __name(this, "SeedcordTypeError");
421
- }
422
- code;
423
- identifier;
424
- constructor(code, args, options) {
425
- const message = resolveMessage(code, args);
426
- super(message, options);
427
- this.code = code;
428
- this.identifier = resolveIdentifier(code);
429
- this.name = formatErrorName(new.target.name, this.identifier, this.code);
430
- Object.setPrototypeOf(this, new.target.prototype);
431
- if (typeof Error.captureStackTrace === "function") {
432
- Error.captureStackTrace(this, new.target);
433
- }
434
- }
660
+
661
+ //#endregion
662
+ //#region src/Logger/Logger.ts
663
+ /**
664
+ * Public logging service with channel-aware transports and per-run file output.
665
+ *
666
+ * - Channel separation (e.g., bot, cli, hmr)
667
+ * - Production-safe JSON logs with ANSI stripping
668
+ * - Unique log files per run via filename templates
669
+ */
670
+ var Logger = class Logger {
671
+ label;
672
+ channel;
673
+ registry = LoggerChannelRegistry.instance;
674
+ utils;
675
+ static instances = /* @__PURE__ */ new Map();
676
+ static instance(prefix, channel) {
677
+ const key = channel ? `${channel}::${prefix}` : prefix;
678
+ let instance = this.instances.get(key);
679
+ if (!instance) {
680
+ instance = new Logger(prefix, channel ? { channel } : void 0);
681
+ this.instances.set(key, instance);
682
+ }
683
+ return instance;
684
+ }
685
+ /**
686
+ * Configures global logger settings.
687
+ *
688
+ * Applies configuration to all channels and clears instance cache.
689
+ *
690
+ * @param config - Partial configuration to merge with defaults
691
+ */
692
+ static configure(config) {
693
+ LoggerChannelRegistry.instance.configure(config);
694
+ this.instances.clear();
695
+ }
696
+ /**
697
+ * Creates a new Logger instance.
698
+ *
699
+ * @param label - Prefix/label for all log entries from this logger
700
+ * @param options - Optional configuration for channel, format, and ANSI handling
701
+ */
702
+ constructor(label, options) {
703
+ this.label = label;
704
+ this.channel = options?.channel ?? this.registry.getDefaultChannel();
705
+ this.logger = this.registry.get(this.channel).child({
706
+ label: this.label,
707
+ channel: this.channel
708
+ });
709
+ this.utils = new LoggerUtilities(this);
710
+ }
711
+ /**
712
+ * Switches this logger to a different channel.
713
+ *
714
+ * @param channel - Channel name to switch to
715
+ */
716
+ setChannel(channel) {
717
+ this.channel = channel;
718
+ this.logger = this.registry.get(channel).child({
719
+ label: this.label,
720
+ channel
721
+ });
722
+ }
723
+ /**
724
+ * Returns a new Logger instance configured for the specified channel. Loggers are cached per (label, channel) pair.
725
+ *
726
+ * @param channel - Channel name to use
727
+ */
728
+ inChannel(channel) {
729
+ return Logger.instance(this.label, channel);
730
+ }
731
+ /**
732
+ * Logs an error message with optional additional data.
733
+ *
734
+ * @param msg - The error message to log
735
+ * @param args - Additional data to include in the log entry
736
+ */
737
+ error(msg, ...args) {
738
+ this.logger.error(msg, ...args);
739
+ }
740
+ /**
741
+ * Logs a warning message with optional additional data.
742
+ *
743
+ * @param msg - The warning message to log
744
+ * @param args - Additional data to include in the log entry
745
+ */
746
+ warn(msg, ...args) {
747
+ this.logger.warn(msg, ...args);
748
+ }
749
+ /**
750
+ * Logs an informational message with optional additional data.
751
+ *
752
+ * @param msg - The informational message to log
753
+ * @param args - Additional data to include in the log entry
754
+ */
755
+ info(msg, ...args) {
756
+ this.logger.info(msg, ...args);
757
+ }
758
+ /**
759
+ * Logs an HTTP-related message with optional additional data.
760
+ *
761
+ * @param msg - The HTTP message to log
762
+ * @param args - Additional data to include in the log entry
763
+ */
764
+ http(msg, ...args) {
765
+ this.logger.http(msg, ...args);
766
+ }
767
+ /**
768
+ * Logs a verbose message with optional additional data.
769
+ *
770
+ * @param msg - The verbose message to log
771
+ * @param args - Additional data to include in the log entry
772
+ */
773
+ verbose(msg, ...args) {
774
+ this.logger.verbose(msg, ...args);
775
+ }
776
+ /**
777
+ * Logs a debug message with optional additional data.
778
+ *
779
+ * @param msg - The debug message to log
780
+ * @param args - Additional data to include in the log entry
781
+ */
782
+ debug(msg, ...args) {
783
+ this.logger.debug(msg, ...args);
784
+ }
785
+ /**
786
+ * Logs a silly/trace level message with optional additional data.
787
+ *
788
+ * @param msg - The silly message to log
789
+ * @param args - Additional data to include in the log entry
790
+ */
791
+ silly(msg, ...args) {
792
+ this.logger.silly(msg, ...args);
793
+ }
435
794
  };
436
- var SeedcordRangeError = class extends RangeError {
437
- static {
438
- __name(this, "SeedcordRangeError");
439
- }
440
- code;
441
- identifier;
442
- constructor(code, args, options) {
443
- const message = resolveMessage(code, args);
444
- super(message, options);
445
- this.code = code;
446
- this.identifier = resolveIdentifier(code);
447
- this.name = formatErrorName(new.target.name, this.identifier, this.code);
448
- Object.setPrototypeOf(this, new.target.prototype);
449
- if (typeof Error.captureStackTrace === "function") {
450
- Error.captureStackTrace(this, new.target);
451
- }
452
- }
795
+
796
+ //#endregion
797
+ //#region src/CooldownManager.ts
798
+ const logger = new Logger("CooldownManager");
799
+ /**
800
+ * Lightweight utility for per-key cooldowns.
801
+ *
802
+ * Manages time-based restrictions on operations by key,
803
+ * useful for rate limiting, command cooldowns, and spam prevention.
804
+ */
805
+ var CooldownManager = class {
806
+ window;
807
+ Err;
808
+ msg;
809
+ map = /* @__PURE__ */ new Map();
810
+ /**
811
+ * Creates a new CooldownManager instance.
812
+ *
813
+ * @param opts - Configuration options for the cooldown behavior
814
+ */
815
+ constructor(opts = {}) {
816
+ this.window = opts.cooldown ?? 1e3;
817
+ this.Err = opts.err ?? Error;
818
+ this.msg = opts.message ?? "Cooldown active";
819
+ }
820
+ /**
821
+ * Records usage timestamp for a key without any cooldown checks.
822
+ *
823
+ * @param key - The unique identifier for the cooldown entry
824
+ */
825
+ set(key) {
826
+ this.map.set(key, Date.now());
827
+ }
828
+ /**
829
+ * Verifies cooldown status for a key and updates timestamp if not active.
830
+ *
831
+ * If the cooldown is still active, throws the configured error.
832
+ * If not active, updates the timestamp and returns successfully.
833
+ *
834
+ * @param key - The unique identifier to check cooldown for
835
+ * @throws An {@link Err} When the cooldown is still active for the given key
836
+ */
837
+ check(key) {
838
+ const now = Date.now();
839
+ const last = this.map.get(key);
840
+ const remaining = this.window - (now - (last ?? 0));
841
+ if (envapt.Envapter.isDevelopment && remaining > 0) logger.debug(`${key} - ${remaining}ms remaining`);
842
+ if (last !== void 0 && remaining > 0) throw new this.Err(this.msg, remaining);
843
+ this.map.set(key, now);
844
+ }
845
+ /**
846
+ * Checks if a key is currently cooling down without updating timestamp.
847
+ *
848
+ * @param key - The unique identifier to check
849
+ * @returns True if the key is still cooling down, false otherwise
850
+ */
851
+ isActive(key) {
852
+ const last = this.map.get(key);
853
+ return last !== void 0 && Date.now() - last < this.window;
854
+ }
855
+ /**
856
+ * Removes a key from the cooldown map.
857
+ *
858
+ * @param key - The unique identifier to remove (useful for manual resets)
859
+ */
860
+ clear(key) {
861
+ this.map.delete(key);
862
+ }
453
863
  };
454
- var SeedcordErrors = {
455
- Error: SeedcordError,
456
- TypeError: SeedcordTypeError,
457
- RangeError: SeedcordRangeError
864
+
865
+ //#endregion
866
+ //#region src/StrictEventEmitter.ts
867
+ /**
868
+ * Typed wrapper around Node.js {@link EventEmitter} enforcing tuple payloads per event name.
869
+ *
870
+ * @typeParam TEvents - Map of event names to readonly tuple payloads
871
+ */
872
+ var StrictEventEmitter = class extends node_events.EventEmitter {
873
+ /**
874
+ * Registers a persistent listener with tuple-safe arguments for the given event.
875
+ *
876
+ * @param event - The event name to attach to
877
+ * @param listener - Callback operating on the typed argument tuple for the event
878
+ * @returns This emitter instance for chaining
879
+ */
880
+ on(event, listener) {
881
+ return super.on(event, listener);
882
+ }
883
+ /**
884
+ * Registers a one time listener that is removed after the first invocation.
885
+ *
886
+ * @param event - The event name to attach to
887
+ * @param listener - Callback operating on the typed argument tuple for the event
888
+ * @returns This emitter instance for chaining
889
+ */
890
+ once(event, listener) {
891
+ return super.once(event, listener);
892
+ }
893
+ /**
894
+ * Removes a previously registered listener for the given event.
895
+ *
896
+ * @param event - The event name whose listener should be removed
897
+ * @param listener - Callback originally registered for the event
898
+ * @returns This emitter instance for chaining
899
+ */
900
+ off(event, listener) {
901
+ return super.off(event, listener);
902
+ }
903
+ /**
904
+ * Alias of {@link StrictEventEmitter.on} for compatibility with Node.js EventEmitter APIs.
905
+ *
906
+ * @param event - The event name to attach to
907
+ * @param listener - Callback operating on the typed argument tuple for the event
908
+ * @returns This emitter instance for chaining
909
+ */
910
+ addListener(event, listener) {
911
+ return this.on(event, listener);
912
+ }
913
+ /**
914
+ * Alias of {@link StrictEventEmitter.off} for compatibility with Node.js EventEmitter APIs.
915
+ *
916
+ * @param event - The event name whose listener should be removed
917
+ * @param listener - Callback originally registered for the event
918
+ * @returns This emitter instance for chaining
919
+ */
920
+ removeListener(event, listener) {
921
+ return super.removeListener(event, listener);
922
+ }
923
+ /**
924
+ * Emits an event with the strictly typed argument tuple for the event name.
925
+ *
926
+ * @param event - The event name to emit
927
+ * @param args - Tuple payload for the event
928
+ * @returns True when the event had listeners, false otherwise
929
+ */
930
+ emit(event, ...args) {
931
+ return super.emit(event, ...args);
932
+ }
933
+ /**
934
+ * Retrieves the listener list for a given event with the correct tuple signature.
935
+ *
936
+ * @param event - The event name to inspect
937
+ * @returns Array of listeners registered for the event
938
+ */
939
+ listeners(event) {
940
+ return super.listeners(event);
941
+ }
942
+ /**
943
+ * Counts listeners for an event without widening the return type of {@link EventEmitter.listenerCount}.
944
+ *
945
+ * @param event - The event name to inspect
946
+ * @returns The total number of listeners registered for the event
947
+ */
948
+ listenerCountTyped(event) {
949
+ return super.listenerCount(event);
950
+ }
951
+ /**
952
+ * Returns the list of event names known to the emitter with the mapped key type.
953
+ *
954
+ * @returns Array of event keys supported by the emitter
955
+ */
956
+ eventNamesTyped() {
957
+ return super.eventNames();
958
+ }
959
+ /**
960
+ * Waits for an event to be emitted, resolving with the listener arguments tuple once triggered.
961
+ * Supports optional abort signals and timeouts for cancellation semantics.
962
+ *
963
+ * @param event - The event name to wait for
964
+ * @param opts - Optional abort signal or timeout in milliseconds
965
+ * @returns Promise resolving with the emitted argument tuple; rejects when aborted or timed out
966
+ */
967
+ waitFor(event, opts) {
968
+ return new Promise((resolve, reject) => {
969
+ const onEvent = (...args) => {
970
+ cleanup();
971
+ resolve(args);
972
+ };
973
+ const onAbort = () => {
974
+ cleanup();
975
+ reject(new require_SeedcordError.SeedcordError(1501));
976
+ };
977
+ let timeoutId = null;
978
+ const cleanup = () => {
979
+ this.off(event, onEvent);
980
+ opts?.signal?.removeEventListener("abort", onAbort);
981
+ if (timeoutId) clearTimeout(timeoutId);
982
+ };
983
+ this.once(event, onEvent);
984
+ if (opts?.signal) {
985
+ if (opts.signal.aborted) return onAbort();
986
+ opts.signal.addEventListener("abort", onAbort, { once: true });
987
+ }
988
+ if (opts?.timeoutMs !== void 0) {
989
+ const timeoutMs = opts.timeoutMs;
990
+ timeoutId = setTimeout(() => {
991
+ cleanup();
992
+ reject(new require_SeedcordError.SeedcordError(1502, [timeoutMs]));
993
+ }, timeoutMs);
994
+ }
995
+ });
996
+ }
458
997
  };
459
- function isSeedcordError(error) {
460
- return typeof error === "object" && error !== null && "code" in error && typeof error.code === "number" && "identifier" in error && typeof error.identifier === "string";
461
- }
462
- __name(isSeedcordError, "isSeedcordError");
463
- var CoordinatedLifecycle = class {
464
- static {
465
- __name(this, "CoordinatedLifecycle");
466
- }
467
- phaseOrder;
468
- phaseEnum;
469
- logger;
470
- events = new events.EventEmitter();
471
- tasksMap = /* @__PURE__ */ new Map();
472
- constructor(loggerName, phaseOrder, phaseEnum) {
473
- this.phaseOrder = phaseOrder;
474
- this.phaseEnum = phaseEnum;
475
- this.logger = new Logger(loggerName);
476
- this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));
477
- }
478
- /**
479
- * Adds a lifecycle task to a specific phase.
480
- *
481
- * Tasks are executed in phase order during lifecycle operations.
482
- * Each task has a timeout to prevent hanging operations.
483
- *
484
- * @param phase - The lifecycle phase to add the task to
485
- * @param taskName - Unique name for the task (used for logging and removal)
486
- * @param task - Async function to execute during the phase
487
- * @param timeoutMs - Maximum time allowed for task execution in milliseconds
488
- * @example
489
- * ```typescript
490
- * lifecycle.addTask(StartupPhase.Services, 'start-database', async () => {
491
- * await database.connect();
492
- * }, 10000);
493
- * ```
494
- */
495
- addTask(phase, taskName, task, timeoutMs) {
496
- if (!this.canAddTask()) return;
497
- const tasks = this.tasksMap.get(phase);
498
- if (!tasks) {
499
- throw new SeedcordError(SeedcordErrorCode.LifecycleUnknownPhase, [
500
- phase
501
- ]);
502
- }
503
- tasks.push({
504
- name: taskName,
505
- task,
506
- timeout: timeoutMs
507
- });
508
- this.logger.debug(`${chalk2__default.default.italic("Added")} ${this.getTaskType()} task ${chalk2__default.default.bold.cyan(taskName)} to phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])}`);
509
- }
510
- /**
511
- * Removes a lifecycle task from a specific phase.
512
- *
513
- * @param phase - The lifecycle phase to remove the task from
514
- * @param taskName - Name of the task to remove
515
- * @returns True if the task was found and removed, false otherwise
516
- */
517
- removeTask(phase, taskName) {
518
- if (!this.canRemoveTask()) return false;
519
- const tasks = this.tasksMap.get(phase);
520
- if (!tasks) return false;
521
- const initialLength = tasks.length;
522
- const filteredTasks = tasks.filter((task) => task.name !== taskName);
523
- this.tasksMap.set(phase, filteredTasks);
524
- const removed = initialLength !== filteredTasks.length;
525
- if (removed) {
526
- this.logger.debug(`${chalk2__default.default.italic("Removed")} ${this.getTaskType()} task ${chalk2__default.default.bold.cyan(taskName)} from phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])}`);
527
- }
528
- return removed;
529
- }
530
- /**
531
- * Run all tasks in a specific phase
532
- */
533
- async runPhase(phase) {
534
- const tasks = this.tasksMap.get(phase) ?? [];
535
- if (tasks.length === 0) {
536
- this.logger.warn(`No tasks to run in phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])}`);
537
- return;
538
- }
539
- this.logger.info(`${chalk2__default.default.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])} with ${chalk2__default.default.bold.cyan(tasks.length)} tasks`);
540
- this.emit(`phase:${phase}:start`);
541
- const results = await this.executeTasksInPhase(phase, tasks);
542
- const failures = results.filter((r) => r.status === "rejected").length;
543
- if (failures > 0) {
544
- throw new SeedcordError(SeedcordErrorCode.LifecyclePhaseFailures, [
545
- chalk2__default.default.bold.magenta(this.phaseEnum[phase]),
546
- failures
547
- ]);
548
- } else {
549
- this.logger.info(`Phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])} ${chalk2__default.default.bold.green("completed successfully")}`);
550
- }
551
- this.emit(`phase:${phase}:complete`);
552
- }
553
- /**
554
- * Run a single task with timeout
555
- */
556
- async runTaskWithTimeout(phase, task) {
557
- this.logger.info(`${chalk2__default.default.italic("Starting")} task ${chalk2__default.default.bold.cyan(task.name)} in phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])}`);
558
- try {
559
- await Promise.race([
560
- task.task(),
561
- new Promise((_, reject) => {
562
- setTimeout(() => {
563
- reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [
564
- task.name,
565
- task.timeout
566
- ]));
567
- }, task.timeout);
568
- })
569
- ]);
570
- this.logger.info(`${chalk2__default.default.italic("Completed")} task ${chalk2__default.default.bold.cyan(task.name)} in phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])}`);
571
- } catch (error) {
572
- this.logger.error(`${chalk2__default.default.italic("Failed")} task ${chalk2__default.default.bold.cyan(task.name)} in phase ${chalk2__default.default.bold.magenta(this.phaseEnum[phase])}:`, error);
573
- throw error;
574
- }
575
- }
576
- /**
577
- * Subscribe to lifecycle events
578
- */
579
- on(event, listener) {
580
- this.events.on(event, listener);
581
- }
582
- /**
583
- * Unsubscribe from lifecycle events
584
- */
585
- off(event, listener) {
586
- this.events.off(event, listener);
587
- }
588
- emit(event, ...args) {
589
- return this.events.emit(event, ...args);
590
- }
998
+
999
+ //#endregion
1000
+ //#region src/Lifecycle/CoordinatedLifecycle.ts
1001
+ /**
1002
+ * Abstract base class for coordinated lifecycle management (startup/shutdown)
1003
+ */
1004
+ var CoordinatedLifecycle = class extends StrictEventEmitter {
1005
+ phaseOrder;
1006
+ phaseEnum;
1007
+ logger;
1008
+ tasksMap = /* @__PURE__ */ new Map();
1009
+ constructor(loggerName, phaseOrder, phaseEnum) {
1010
+ super();
1011
+ this.phaseOrder = phaseOrder;
1012
+ this.phaseEnum = phaseEnum;
1013
+ this.logger = new Logger(loggerName);
1014
+ this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));
1015
+ }
1016
+ /**
1017
+ * Adds a lifecycle task to a specific phase.
1018
+ *
1019
+ * Tasks are executed in phase order during lifecycle operations.
1020
+ * Each task has a timeout to prevent hanging operations.
1021
+ *
1022
+ * @param phase - The lifecycle phase to add the task to
1023
+ * @param taskName - Unique name for the task (used for logging and removal)
1024
+ * @param task - Async function to execute during the phase
1025
+ * @param timeoutMs - Maximum time allowed for task execution in milliseconds
1026
+ * @example
1027
+ * ```typescript
1028
+ * lifecycle.addTask(StartupPhase.Services, 'start-database', async () => {
1029
+ * await database.connect();
1030
+ * }, 10000);
1031
+ * ```
1032
+ */
1033
+ addTask(phase, taskName, task, timeoutMs) {
1034
+ if (!this.canAddTask()) return;
1035
+ const tasks = this.tasksMap.get(phase);
1036
+ if (!tasks) throw new require_SeedcordError.SeedcordError(1104, [phase]);
1037
+ tasks.push({
1038
+ name: taskName,
1039
+ task,
1040
+ timeout: timeoutMs
1041
+ });
1042
+ this.logger.debug(`${chalk.default.italic("Added")} ${this.getTaskType()} task ${chalk.default.bold.cyan(taskName)} to phase ${chalk.default.bold.magenta(this.phaseEnum[phase])}`);
1043
+ }
1044
+ /**
1045
+ * Removes a lifecycle task from a specific phase.
1046
+ *
1047
+ * @param phase - The lifecycle phase to remove the task from
1048
+ * @param taskName - Name of the task to remove
1049
+ * @returns True if the task was found and removed, false otherwise
1050
+ */
1051
+ removeTask(phase, taskName) {
1052
+ if (!this.canRemoveTask()) return false;
1053
+ const tasks = this.tasksMap.get(phase);
1054
+ if (!tasks) return false;
1055
+ const initialLength = tasks.length;
1056
+ const filteredTasks = tasks.filter((task) => task.name !== taskName);
1057
+ this.tasksMap.set(phase, filteredTasks);
1058
+ const removed = initialLength !== filteredTasks.length;
1059
+ if (removed) this.logger.debug(`${chalk.default.italic("Removed")} ${this.getTaskType()} task ${chalk.default.bold.cyan(taskName)} from phase ${chalk.default.bold.magenta(this.phaseEnum[phase])}`);
1060
+ return removed;
1061
+ }
1062
+ /**
1063
+ * Run all tasks in a specific phase
1064
+ */
1065
+ async runPhase(phase) {
1066
+ const tasks = this.tasksMap.get(phase) ?? [];
1067
+ if (tasks.length === 0) {
1068
+ this.logger.warn(`No tasks to run in phase ${chalk.default.bold.magenta(this.phaseEnum[phase])}`);
1069
+ return;
1070
+ }
1071
+ this.logger.info(`${chalk.default.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk.default.bold.magenta(this.phaseEnum[phase])} with ${chalk.default.bold.cyan(tasks.length)} tasks`);
1072
+ this.emitPhase(phase, "start");
1073
+ const failures = (await this.executeTasksInPhase(phase, tasks)).filter((r) => r.status === "rejected").length;
1074
+ if (failures > 0) throw new require_SeedcordError.SeedcordError(1105, [this.phaseEnum[phase], failures]);
1075
+ else this.logger.info(`Phase ${chalk.default.bold.magenta(this.phaseEnum[phase])} ${chalk.default.bold.green("completed successfully")}`);
1076
+ this.emitPhase(phase, "complete");
1077
+ }
1078
+ /**
1079
+ * Run a single task with timeout
1080
+ */
1081
+ async runTaskWithTimeout(phase, task) {
1082
+ this.logger.info(`${chalk.default.italic("Starting")} task ${chalk.default.bold.cyan(task.name)} in phase ${chalk.default.bold.magenta(this.phaseEnum[phase])}`);
1083
+ let timeoutId;
1084
+ try {
1085
+ await Promise.race([task.task(), new Promise((_, reject) => {
1086
+ timeoutId = setTimeout(() => {
1087
+ reject(new require_SeedcordError.SeedcordError(1106, [task.name, task.timeout]));
1088
+ }, task.timeout);
1089
+ })]);
1090
+ this.logger.info(`${chalk.default.italic("Completed")} task ${chalk.default.bold.cyan(task.name)} in phase ${chalk.default.bold.magenta(this.phaseEnum[phase])}`);
1091
+ } catch (error) {
1092
+ this.logger.error(`${chalk.default.italic("Failed")} task ${chalk.default.bold.cyan(task.name)} in phase ${chalk.default.bold.magenta(this.phaseEnum[phase])}:`, error);
1093
+ throw error;
1094
+ } finally {
1095
+ if (timeoutId) clearTimeout(timeoutId);
1096
+ }
1097
+ }
1098
+ emitPhase(phase, action) {
1099
+ node_events.EventEmitter.prototype.emit.call(this, `phase:${phase}:${action}`);
1100
+ }
591
1101
  };
592
1102
 
593
- // src/Lifecycle/CoordinatedShutdown.ts
594
- function _ts_decorate(decorators, target, key, desc) {
595
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
596
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
597
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
598
- return c > 3 && r && Object.defineProperty(target, key, r), r;
599
- }
600
- __name(_ts_decorate, "_ts_decorate");
601
- function _ts_metadata(k, v) {
602
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
603
- }
604
- __name(_ts_metadata, "_ts_metadata");
605
- var ShutdownPhase = /* @__PURE__ */ (function(ShutdownPhase2) {
606
- ShutdownPhase2[ShutdownPhase2["StopAcceptingRequests"] = 1] = "StopAcceptingRequests";
607
- ShutdownPhase2[ShutdownPhase2["StopServices"] = 2] = "StopServices";
608
- ShutdownPhase2[ShutdownPhase2["ExternalResources"] = 3] = "ExternalResources";
609
- ShutdownPhase2[ShutdownPhase2["DiscordCleanup"] = 4] = "DiscordCleanup";
610
- ShutdownPhase2[ShutdownPhase2["FinalCleanup"] = 5] = "FinalCleanup";
611
- return ShutdownPhase2;
612
- })({});
613
- var PHASE_ORDER = [
614
- 1,
615
- 2,
616
- 3,
617
- 4,
618
- 5
1103
+ //#endregion
1104
+ //#region src/Lifecycle/CoordinatedShutdown.ts
1105
+ /**
1106
+ * Shutdown phases for coordinated application shutdown.
1107
+ */
1108
+ let ShutdownPhase = /* @__PURE__ */ function(ShutdownPhase) {
1109
+ /** Stop accepting new requests/interactions */
1110
+ ShutdownPhase[ShutdownPhase["StopAcceptingRequests"] = 1] = "StopAcceptingRequests";
1111
+ /** Stop background services (health checks, etc.) */
1112
+ ShutdownPhase[ShutdownPhase["StopServices"] = 2] = "StopServices";
1113
+ /** Disconnect from external resources (database, APIs) */
1114
+ ShutdownPhase[ShutdownPhase["ExternalResources"] = 3] = "ExternalResources";
1115
+ /** Disconnect from Discord */
1116
+ ShutdownPhase[ShutdownPhase["DiscordCleanup"] = 4] = "DiscordCleanup";
1117
+ /** Final cleanup tasks */
1118
+ ShutdownPhase[ShutdownPhase["FinalCleanup"] = 5] = "FinalCleanup";
1119
+ return ShutdownPhase;
1120
+ }({});
1121
+ /** Define the order of phases */
1122
+ const PHASE_ORDER$1 = [
1123
+ 1,
1124
+ 2,
1125
+ 3,
1126
+ 4,
1127
+ 5
619
1128
  ];
620
- var LOG_FLUSH_DELAY_MS = 500;
1129
+ const LOG_FLUSH_DELAY_MS = 500;
1130
+ /**
1131
+ * CoordinatedShutdown manages graceful application shutdown by executing registered tasks across defined phases.
1132
+ *
1133
+ * It listens for termination signals (SIGINT, SIGTERM) and runs tasks in parallel within each phase.
1134
+ * Tasks can be added or removed dynamically, and each task has an associated timeout.
1135
+ */
621
1136
  var CoordinatedShutdown = class extends CoordinatedLifecycle {
622
- static {
623
- __name(this, "CoordinatedShutdown");
624
- }
625
- isShuttingDown = false;
626
- exitCode = 0;
627
- constructor() {
628
- super("CoordinatedShutdown", PHASE_ORDER, ShutdownPhase);
629
- this.registerSignalHandlers();
630
- }
631
- canAddTask() {
632
- return this.isShutdownEnabled;
633
- }
634
- canRemoveTask() {
635
- return true;
636
- }
637
- getTaskType() {
638
- return "shutdown";
639
- }
640
- async executeTasksInPhase(phase, tasks) {
641
- const results = [];
642
- for (const task of tasks) {
643
- results.push(await Promise.resolve().then(() => this.runTaskWithTimeout(phase, task)).then(
644
- () => ({
645
- status: "fulfilled",
646
- value: void 0
647
- }),
648
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
649
- (reason) => ({
650
- status: "rejected",
651
- reason
652
- })
653
- ));
654
- }
655
- return results;
656
- }
657
- registerSignalHandlers() {
658
- if (!this.isShutdownEnabled) return;
659
- process.on("SIGTERM", () => {
660
- this.logger.info(`Received ${chalk2__default.default.yellow.bold("SIGTERM")} signal`);
661
- void this.run(0);
662
- });
663
- process.on("SIGINT", () => {
664
- this.logger.info(`Received ${chalk2__default.default.yellow.bold("SIGINT")} signal`);
665
- void this.run(0);
666
- });
667
- }
668
- /**
669
- * Adds a task to a specific shutdown phase with timeout.
670
- *
671
- * @param phase - The shutdown phase from {@link ShutdownPhase}
672
- * @param taskName - Unique identifier for the task
673
- * @param task - Async function to execute
674
- * @param timeoutMs - Task timeout in milliseconds (default: 5000)
675
- */
676
- addTask(phase, taskName, task, timeoutMs = 5e3) {
677
- super.addTask(phase, taskName, task, timeoutMs);
678
- }
679
- /**
680
- * Removes a task from a specific shutdown phase.
681
- *
682
- * @param phase - The shutdown phase to remove from
683
- * @param taskName - Name of the task to remove
684
- * @returns True if task was found and removed
685
- */
686
- removeTask(phase, taskName) {
687
- return super.removeTask(phase, taskName);
688
- }
689
- /**
690
- * Executes the coordinated shutdown sequence.
691
- *
692
- * Runs all registered tasks across shutdown phases in reverse order.
693
- * Tasks within each phase are executed in parallel for faster shutdown.
694
- * Process exits with the specified code when complete.
695
- *
696
- * @param exitCode - Process exit code (default: `0`)
697
- * @returns Promise that resolves when shutdown is complete
698
- * @example
699
- * ```typescript
700
- * shutdown.addTask(ShutdownPhase.Services, 'database', () => db.disconnect(), 5000);
701
- * await shutdown.run(0); // Graceful shutdown
702
- * ```
703
- */
704
- async run(exitCode = 0) {
705
- if (this.isShuttingDown) {
706
- this.logger.warn("Shutdown sequence already in progress");
707
- return;
708
- }
709
- this.isShuttingDown = true;
710
- this.exitCode = exitCode;
711
- this.logger.info(`${chalk2__default.default.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk2__default.default.bold.cyan(exitCode)}`);
712
- this.emit("shutdown:start");
713
- try {
714
- for (const phase of PHASE_ORDER) {
715
- await this.runPhase(phase);
716
- }
717
- this.logger.info(`${chalk2__default.default.bold.green("Coordinated shutdown completed")} successfully`);
718
- this.emit("shutdown:complete");
719
- } catch (error) {
720
- this.logger.error(`${chalk2__default.default.bold.red("Coordinated shutdown failed")}`);
721
- this.emit("shutdown:error", error);
722
- } finally {
723
- this.logger.info(`${chalk2__default.default.bold.red("Exiting")} process with code ${chalk2__default.default.bold.cyan(this.exitCode)}`);
724
- setTimeout(() => {
725
- process.exit(this.exitCode);
726
- }, LOG_FLUSH_DELAY_MS);
727
- }
728
- }
729
- /**
730
- * Subscribe to shutdown events
731
- */
732
- on(event, listener) {
733
- super.on(event, listener);
734
- }
735
- /**
736
- * Unsubscribe from shutdown events
737
- */
738
- off(event, listener) {
739
- super.off(event, listener);
740
- }
1137
+ isShutdownEnabled;
1138
+ isShuttingDown = false;
1139
+ exitCode = 0;
1140
+ onSigTerm = null;
1141
+ onSigInt = null;
1142
+ constructor(enabled = true) {
1143
+ super("CoordinatedShutdown", PHASE_ORDER$1, ShutdownPhase);
1144
+ this.isShutdownEnabled = enabled;
1145
+ this.registerSignalHandlers();
1146
+ }
1147
+ canAddTask() {
1148
+ return this.isShutdownEnabled;
1149
+ }
1150
+ canRemoveTask() {
1151
+ return true;
1152
+ }
1153
+ getTaskType() {
1154
+ return "shutdown";
1155
+ }
1156
+ async executeTasksInPhase(phase, tasks) {
1157
+ const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));
1158
+ return Promise.allSettled(promises);
1159
+ }
1160
+ registerSignalHandlers() {
1161
+ if (!this.isShutdownEnabled) return;
1162
+ this.onSigTerm = () => {
1163
+ this.logger.info(`Received ${chalk.default.yellow.bold("SIGTERM")} signal`);
1164
+ this.run(0);
1165
+ };
1166
+ this.onSigInt = () => {
1167
+ this.logger.info(`Received ${chalk.default.yellow.bold("SIGINT")} signal`);
1168
+ this.run(0);
1169
+ };
1170
+ process.on("SIGTERM", this.onSigTerm);
1171
+ process.on("SIGINT", this.onSigInt);
1172
+ }
1173
+ removeSignalHandlers() {
1174
+ if (this.onSigTerm) {
1175
+ process.off("SIGTERM", this.onSigTerm);
1176
+ this.onSigTerm = null;
1177
+ }
1178
+ if (this.onSigInt) {
1179
+ process.off("SIGINT", this.onSigInt);
1180
+ this.onSigInt = null;
1181
+ }
1182
+ }
1183
+ /**
1184
+ * Adds a task to a specific shutdown phase with timeout.
1185
+ *
1186
+ * @param phase - The shutdown phase from {@link ShutdownPhase}
1187
+ * @param taskName - Unique identifier for the task
1188
+ * @param task - Async function to execute
1189
+ * @param timeoutMs - Task timeout in milliseconds {@default 5000}
1190
+ */
1191
+ addTask(phase, taskName, task, timeoutMs = 5e3) {
1192
+ super.addTask(phase, taskName, task, timeoutMs);
1193
+ }
1194
+ /**
1195
+ * Removes a task from a specific shutdown phase.
1196
+ *
1197
+ * @param phase - The shutdown phase to remove from
1198
+ * @param taskName - Name of the task to remove
1199
+ * @returns True if task was found and removed
1200
+ */
1201
+ removeTask(phase, taskName) {
1202
+ return super.removeTask(phase, taskName);
1203
+ }
1204
+ /**
1205
+ * Executes the coordinated shutdown sequence.
1206
+ *
1207
+ * Runs all registered tasks across shutdown phases in reverse order.
1208
+ * Tasks within each phase are executed in parallel for faster shutdown.
1209
+ * Process exits with the specified code when complete.
1210
+ *
1211
+ * @param exitCode - Process exit code {@default 0}
1212
+ * @param exitProcess - Whether to exit the process after shutdown {@default true}
1213
+ * @returns Promise that resolves when shutdown is complete
1214
+ * @example
1215
+ * ```typescript
1216
+ * shutdown.addTask(ShutdownPhase.Services, 'database', () => db.disconnect(), 5000);
1217
+ * await shutdown.run(0); // Graceful shutdown
1218
+ * ```
1219
+ */
1220
+ async run(exitCode = 0, exitProcess = true) {
1221
+ this.removeSignalHandlers();
1222
+ if (this.isShuttingDown) {
1223
+ this.logger.warn("Shutdown sequence already in progress");
1224
+ return;
1225
+ }
1226
+ this.isShuttingDown = true;
1227
+ this.exitCode = exitCode;
1228
+ this.logger.info(`${chalk.default.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk.default.bold.cyan(exitCode)}`);
1229
+ this.emit("shutdown:start");
1230
+ try {
1231
+ for (const phase of PHASE_ORDER$1) await this.runPhase(phase);
1232
+ this.logger.info(`${chalk.default.bold.green("Coordinated shutdown completed")} successfully`);
1233
+ this.emit("shutdown:complete");
1234
+ } catch (error) {
1235
+ this.logger.error(`${chalk.default.bold.red("Coordinated shutdown failed")}`);
1236
+ this.emit("shutdown:error", error);
1237
+ } finally {
1238
+ if (exitProcess) {
1239
+ this.logger.info(`${chalk.default.bold.red("Exiting")} process with code ${chalk.default.bold.cyan(this.exitCode)}`);
1240
+ setTimeout(() => {
1241
+ process.exit(this.exitCode);
1242
+ }, LOG_FLUSH_DELAY_MS);
1243
+ } else {
1244
+ this.logger.info(`${chalk.default.bold.yellow("Skipping")} process exit (dev mode)`);
1245
+ this.isShuttingDown = false;
1246
+ }
1247
+ }
1248
+ }
741
1249
  };
742
- _ts_decorate([
743
- envapt.Envapt("SHUTDOWN_IS_ENABLED", {
744
- fallback: true
745
- }),
746
- _ts_metadata("design:type", Boolean)
747
- ], CoordinatedShutdown.prototype, "isShutdownEnabled", void 0);
748
1250
 
749
- // src/HealthCheck.ts
750
- function _ts_decorate2(decorators, target, key, desc) {
751
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
752
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
753
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
754
- return c > 3 && r && Object.defineProperty(target, key, r), r;
755
- }
756
- __name(_ts_decorate2, "_ts_decorate");
757
- function _ts_metadata2(k, v) {
758
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
759
- }
760
- __name(_ts_metadata2, "_ts_metadata");
761
- var HTTP_OK = 200;
762
- var HTTP_NOT_FOUND = 404;
1251
+ //#endregion
1252
+ //#region src/HealthCheck.ts
1253
+ const HTTP_OK = 200;
1254
+ const HTTP_NOT_FOUND = 404;
1255
+ const DEFAULT_HEALTH_CHECK_PORT = 6967;
1256
+ const DEFAULT_HEALTH_CHECK_PATH = "/healthcheck";
1257
+ /**
1258
+ * HTTP health check service for monitoring bot status.
1259
+ *
1260
+ * Provides a simple HTTP endpoint that responds with JSON status
1261
+ * information, useful for container orchestration and monitoring.
1262
+ */
763
1263
  var HealthCheck = class {
764
- static {
765
- __name(this, "HealthCheck");
766
- }
767
- logger = new Logger("HealthCheck");
768
- server;
769
- constructor(shutdown) {
770
- shutdown.addTask(ShutdownPhase.StopServices, "stop-healthcheck-server", async () => await this.stop());
771
- }
772
- /**
773
- * Starts the health check server.
774
- * @returns Promise that resolves when the server is listening
775
- */
776
- async init() {
777
- return new Promise((resolve, reject) => {
778
- this.server = http.createServer((req, res) => {
779
- if (req.method === "GET" && req.url === this.path) {
780
- res.writeHead(HTTP_OK, {
781
- "Content-Type": "application/json"
782
- });
783
- res.end(JSON.stringify({
784
- status: "ok",
785
- timestamp: Date.now()
786
- }));
787
- } else {
788
- res.writeHead(HTTP_NOT_FOUND, {
789
- "Content-Type": "application/json"
790
- });
791
- res.end(JSON.stringify({
792
- status: "not found"
793
- }));
794
- }
795
- });
796
- this.server.on("error", reject);
797
- this.server.once("listening", () => {
798
- const address = this.host ?? "localhost";
799
- this.logger.info(`${chalk2__default.default.green.bold("\u2713")} Health check server listening on ${chalk2__default.default.cyan(`http://${address}:${this.port}${this.path}`)}`);
800
- resolve();
801
- });
802
- if (this.host) {
803
- this.logger.debug(`Binding health check server to ${this.host}`);
804
- this.server.listen(this.port, this.host);
805
- } else {
806
- this.logger.debug("Binding health check server to all interfaces");
807
- this.server.listen(this.port);
808
- }
809
- });
810
- }
811
- /**
812
- * Stops the health check server.
813
- *
814
- * @returns Promise that resolves when the server is closed
815
- */
816
- stop() {
817
- if (this.server !== void 0) {
818
- const server = this.server;
819
- return new Promise((resolve) => {
820
- server.once("close", () => resolve());
821
- server.close(() => {
822
- this.logger.info(chalk2__default.default.bold.red("Health check server stopped"));
823
- });
824
- });
825
- }
826
- return Promise.resolve();
827
- }
1264
+ logger = new Logger("HealthCheck");
1265
+ port;
1266
+ path;
1267
+ host;
1268
+ server;
1269
+ constructor(shutdown, options) {
1270
+ this.port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;
1271
+ this.path = options?.path ?? DEFAULT_HEALTH_CHECK_PATH;
1272
+ this.host = options?.host;
1273
+ shutdown.addTask(2, "stop-healthcheck-server", async () => await this.stop());
1274
+ }
1275
+ /**
1276
+ * Starts the health check server.
1277
+ * @returns Promise that resolves when the server is listening
1278
+ */
1279
+ async init() {
1280
+ return new Promise((resolve, reject) => {
1281
+ const server = (0, http.createServer)((req, res) => {
1282
+ if (req.method === "GET" && req.url === this.path) {
1283
+ res.writeHead(HTTP_OK, { "Content-Type": "application/json" });
1284
+ res.end(JSON.stringify({
1285
+ status: "ok",
1286
+ timestamp: Date.now()
1287
+ }));
1288
+ } else {
1289
+ res.writeHead(HTTP_NOT_FOUND, { "Content-Type": "application/json" });
1290
+ res.end(JSON.stringify({ status: "not found" }));
1291
+ }
1292
+ });
1293
+ this.server = server;
1294
+ const onListenError = (err) => reject(err);
1295
+ server.on("error", onListenError);
1296
+ server.once("listening", () => {
1297
+ server.removeListener("error", onListenError);
1298
+ server.on("error", (err) => this.logger.error("Health check server error", err));
1299
+ const address = this.host ?? "localhost";
1300
+ this.logger.info(`${chalk.default.green.bold("✓")} Health check server listening on ${chalk.default.cyan(`http://${address}:${this.port}${this.path}`)}`);
1301
+ resolve();
1302
+ });
1303
+ if (this.host) {
1304
+ this.logger.debug(`Binding health check server to ${this.host}`);
1305
+ server.listen(this.port, this.host);
1306
+ } else {
1307
+ this.logger.debug("Binding health check server to all interfaces");
1308
+ server.listen(this.port);
1309
+ }
1310
+ });
1311
+ }
1312
+ /**
1313
+ * Stops the health check server.
1314
+ *
1315
+ * @returns Promise that resolves when the server is closed
1316
+ */
1317
+ stop() {
1318
+ const server = this.server;
1319
+ if (!server?.listening) return Promise.resolve();
1320
+ return new Promise((resolve, reject) => {
1321
+ server.once("close", () => resolve());
1322
+ server.close((err) => {
1323
+ if (err) {
1324
+ reject(err);
1325
+ return;
1326
+ }
1327
+ this.logger.info(chalk.default.bold.red("Health check server stopped"));
1328
+ });
1329
+ });
1330
+ }
828
1331
  };
829
- _ts_decorate2([
830
- envapt.Envapt("HEALTH_CHECK_PORT", {
831
- fallback: 6956
832
- }),
833
- _ts_metadata2("design:type", Number)
834
- ], HealthCheck.prototype, "port", void 0);
835
- _ts_decorate2([
836
- envapt.Envapt("HEALTH_CHECK_PATH", {
837
- fallback: "/healthcheck"
838
- }),
839
- _ts_metadata2("design:type", String)
840
- ], HealthCheck.prototype, "path", void 0);
841
- _ts_decorate2([
842
- envapt.Envapt("HEALTH_CHECK_HOST"),
843
- _ts_metadata2("design:type", Object)
844
- ], HealthCheck.prototype, "host", void 0);
845
- var StrictEventEmitter = class extends events.EventEmitter {
846
- static {
847
- __name(this, "StrictEventEmitter");
848
- }
849
- /**
850
- * Registers a persistent listener with tuple-safe arguments for the given event.
851
- *
852
- * @param event - The event name to attach to
853
- * @param listener - Callback operating on the typed argument tuple for the event
854
- * @returns This emitter instance for chaining
855
- */
856
- on(event, listener) {
857
- return super.on(event, listener);
858
- }
859
- /**
860
- * Registers a one time listener that is removed after the first invocation.
861
- *
862
- * @param event - The event name to attach to
863
- * @param listener - Callback operating on the typed argument tuple for the event
864
- * @returns This emitter instance for chaining
865
- */
866
- once(event, listener) {
867
- return super.once(event, listener);
868
- }
869
- /**
870
- * Removes a previously registered listener for the given event.
871
- *
872
- * @param event - The event name whose listener should be removed
873
- * @param listener - Callback originally registered for the event
874
- * @returns This emitter instance for chaining
875
- */
876
- off(event, listener) {
877
- return super.off(event, listener);
878
- }
879
- /**
880
- * Alias of {@link StrictEventEmitter.on} for compatibility with Node.js EventEmitter APIs.
881
- *
882
- * @param event - The event name to attach to
883
- * @param listener - Callback operating on the typed argument tuple for the event
884
- * @returns This emitter instance for chaining
885
- */
886
- addListener(event, listener) {
887
- return this.on(event, listener);
888
- }
889
- /**
890
- * Alias of {@link StrictEventEmitter.off} for compatibility with Node.js EventEmitter APIs.
891
- *
892
- * @param event - The event name whose listener should be removed
893
- * @param listener - Callback originally registered for the event
894
- * @returns This emitter instance for chaining
895
- */
896
- removeListener(event, listener) {
897
- return super.removeListener(event, listener);
898
- }
899
- /**
900
- * Emits an event with the strictly typed argument tuple for the event name.
901
- *
902
- * @param event - The event name to emit
903
- * @param args - Tuple payload for the event
904
- * @returns True when the event had listeners, false otherwise
905
- */
906
- emit(event, ...args) {
907
- return super.emit(event, ...args);
908
- }
909
- /**
910
- * Retrieves the listener list for a given event with the correct tuple signature.
911
- *
912
- * @param event - The event name to inspect
913
- * @returns Array of listeners registered for the event
914
- */
915
- listeners(event) {
916
- return super.listeners(event);
917
- }
918
- /**
919
- * Counts listeners for an event without widening the return type of {@link EventEmitter.listenerCount}.
920
- *
921
- * @param event - The event name to inspect
922
- * @returns The total number of listeners registered for the event
923
- */
924
- listenerCountTyped(event) {
925
- return super.listenerCount(event);
926
- }
927
- /**
928
- * Returns the list of event names known to the emitter with the mapped key type.
929
- *
930
- * @returns Array of event keys supported by the emitter
931
- */
932
- eventNamesTyped() {
933
- return super.eventNames();
934
- }
935
- /**
936
- * Waits for an event to be emitted, resolving with the listener arguments tuple once triggered.
937
- * Supports optional abort signals and timeouts for cancellation semantics.
938
- *
939
- * @param event - The event name to wait for
940
- * @param opts - Optional abort signal or timeout in milliseconds
941
- * @returns Promise resolving with the emitted argument tuple; rejects when aborted or timed out
942
- */
943
- waitFor(event, opts) {
944
- return new Promise((resolve, reject) => {
945
- const onEvent = /* @__PURE__ */ __name((...args) => {
946
- cleanup();
947
- resolve(args);
948
- }, "onEvent");
949
- const onAbort = /* @__PURE__ */ __name(() => {
950
- cleanup();
951
- reject(Object.assign(new Error("Aborted"), {
952
- name: "AbortError"
953
- }));
954
- }, "onAbort");
955
- let timeoutId = null;
956
- const cleanup = /* @__PURE__ */ __name(() => {
957
- this.off(event, onEvent);
958
- opts?.signal?.removeEventListener("abort", onAbort);
959
- if (timeoutId) clearTimeout(timeoutId);
960
- }, "cleanup");
961
- this.once(event, onEvent);
962
- if (opts?.signal) {
963
- if (opts.signal.aborted) return onAbort();
964
- opts.signal.addEventListener("abort", onAbort, {
965
- once: true
966
- });
967
- }
968
- if (opts?.timeoutMs !== void 0) {
969
- timeoutId = setTimeout(() => {
970
- cleanup();
971
- reject(new Error("Timed out"));
972
- }, opts.timeoutMs);
973
- }
974
- });
975
- }
976
- };
977
- var StartupPhase = /* @__PURE__ */ (function(StartupPhase2) {
978
- StartupPhase2[StartupPhase2["Validation"] = 1] = "Validation";
979
- StartupPhase2[StartupPhase2["Discovery"] = 2] = "Discovery";
980
- StartupPhase2[StartupPhase2["Registration"] = 3] = "Registration";
981
- StartupPhase2[StartupPhase2["Configuration"] = 4] = "Configuration";
982
- StartupPhase2[StartupPhase2["Instantiation"] = 5] = "Instantiation";
983
- StartupPhase2[StartupPhase2["Activation"] = 6] = "Activation";
984
- StartupPhase2[StartupPhase2["Ready"] = 7] = "Ready";
985
- return StartupPhase2;
986
- })({});
987
- var PHASE_ORDER2 = [
988
- 1,
989
- 2,
990
- 3,
991
- 4,
992
- 5,
993
- 6,
994
- 7
1332
+
1333
+ //#endregion
1334
+ //#region src/Lifecycle/CoordinatedStartup.ts
1335
+ /**
1336
+ * Startup phases for coordinated initialization
1337
+ *
1338
+ * Defines the order in which different components are initialized during bot startup.
1339
+ */
1340
+ let StartupPhase = /* @__PURE__ */ function(StartupPhase) {
1341
+ /** Validate environment variables and config files */
1342
+ StartupPhase[StartupPhase["Validation"] = 1] = "Validation";
1343
+ /** Discover plugin constructors via decorators or registry */
1344
+ StartupPhase[StartupPhase["Discovery"] = 2] = "Discovery";
1345
+ /** Register plugin metadata and declared dependencies */
1346
+ StartupPhase[StartupPhase["Registration"] = 3] = "Registration";
1347
+ /** Inject and validate plugin-specific configuration */
1348
+ StartupPhase[StartupPhase["Configuration"] = 4] = "Configuration";
1349
+ /** Instantiate plugin classes with Core and arguments */
1350
+ StartupPhase[StartupPhase["Instantiation"] = 5] = "Instantiation";
1351
+ /** Activate plugins by calling their init/setup methods */
1352
+ StartupPhase[StartupPhase["Activation"] = 6] = "Activation";
1353
+ /** Mark seedcord as ready and start handling interactions */
1354
+ StartupPhase[StartupPhase["Ready"] = 7] = "Ready";
1355
+ return StartupPhase;
1356
+ }({});
1357
+ /** Define the order of phases */
1358
+ const PHASE_ORDER = [
1359
+ 1,
1360
+ 2,
1361
+ 3,
1362
+ 4,
1363
+ 5,
1364
+ 6,
1365
+ 7
995
1366
  ];
1367
+ /**
1368
+ * Manages bot startup lifecycle with ordered phases
1369
+ *
1370
+ * Coordinates initialization of all bot components in a predictable sequence.
1371
+ * Tasks are executed within their designated phases to ensure proper dependency order.
1372
+ */
996
1373
  var CoordinatedStartup = class extends CoordinatedLifecycle {
997
- static {
998
- __name(this, "CoordinatedStartup");
999
- }
1000
- isStartingUp = false;
1001
- hasStarted = false;
1002
- constructor() {
1003
- super("CoordinatedStartup", PHASE_ORDER2, StartupPhase);
1004
- }
1005
- /**
1006
- * Adds a task to a specific startup phase with timeout.
1007
- *
1008
- * @param phase - The startup phase from {@link StartupPhase}
1009
- * @param taskName - Unique identifier for the task
1010
- * @param task - Async function to execute
1011
- * @param timeoutMs - Task timeout in milliseconds (default: 10000)
1012
- */
1013
- addTask(phase, taskName, task, timeoutMs = 1e4) {
1014
- super.addTask(phase, taskName, task, timeoutMs);
1015
- }
1016
- canAddTask() {
1017
- if (this.hasStarted) {
1018
- throw new SeedcordError(SeedcordErrorCode.LifecycleAddAfterCompletion);
1019
- }
1020
- if (this.isStartingUp) {
1021
- throw new SeedcordError(SeedcordErrorCode.LifecycleAddDuringRun);
1022
- }
1023
- return true;
1024
- }
1025
- canRemoveTask() {
1026
- if (this.isStartingUp) {
1027
- throw new SeedcordError(SeedcordErrorCode.LifecycleRemoveDuringRun);
1028
- }
1029
- return true;
1030
- }
1031
- getTaskType() {
1032
- return "startup";
1033
- }
1034
- async executeTasksInPhase(phase, tasks) {
1035
- const results = [];
1036
- for (const task of tasks) {
1037
- results.push(await Promise.resolve().then(() => this.runTaskWithTimeout(phase, task)).then(
1038
- () => ({
1039
- status: "fulfilled",
1040
- value: void 0
1041
- }),
1042
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1043
- (reason) => ({
1044
- status: "rejected",
1045
- reason
1046
- })
1047
- ));
1048
- }
1049
- return results;
1050
- }
1051
- /**
1052
- * Executes the coordinated startup sequence.
1053
- *
1054
- * Runs all registered tasks across startup phases in the correct order.
1055
- * Each phase completes before the next phase begins. Tasks within a phase
1056
- * are executed sequentially to maintain predictable initialization.
1057
- *
1058
- * @returns Promise that resolves when startup is complete
1059
- * @throws An {@link Error} If startup fails or is called multiple times
1060
- * @example
1061
- * ```typescript
1062
- * const startup = new CoordinatedStartup();
1063
- * startup.addTask(StartupPhase.Services, 'database', () => db.connect(), 10000);
1064
- * await startup.run();
1065
- * ```
1066
- */
1067
- async run() {
1068
- if (this.hasStarted) {
1069
- this.logger.warn("Startup sequence has already completed");
1070
- return;
1071
- }
1072
- if (this.isStartingUp) {
1073
- this.logger.warn("Startup sequence already in progress");
1074
- return;
1075
- }
1076
- this.isStartingUp = true;
1077
- this.logger.info(`${chalk2__default.default.bold.green("Starting")} coordinated startup sequence`);
1078
- this.emit("startup:start");
1079
- try {
1080
- for (const phase of PHASE_ORDER2) await this.runPhase(phase);
1081
- this.hasStarted = true;
1082
- this.logger.info(`${chalk2__default.default.bold.green("Coordinated startup completed")} successfully`);
1083
- this.emit("startup:complete");
1084
- } catch (error) {
1085
- this.logger.error(`${chalk2__default.default.bold.red("Coordinated startup failed")}`);
1086
- this.emit("startup:error", error);
1087
- throw error;
1088
- } finally {
1089
- this.isStartingUp = false;
1090
- }
1091
- }
1092
- /**
1093
- * Subscribe to startup events
1094
- */
1095
- on(event, listener) {
1096
- super.on(event, listener);
1097
- }
1098
- /**
1099
- * Unsubscribe from startup events
1100
- */
1101
- off(event, listener) {
1102
- super.off(event, listener);
1103
- }
1104
- /**
1105
- * Check if startup has completed
1106
- */
1107
- get isReady() {
1108
- return this.hasStarted;
1109
- }
1110
- /**
1111
- * Check if startup is currently running
1112
- */
1113
- get isRunning() {
1114
- return this.isStartingUp;
1115
- }
1374
+ isStartingUp = false;
1375
+ hasStarted = false;
1376
+ constructor() {
1377
+ super("CoordinatedStartup", PHASE_ORDER, StartupPhase);
1378
+ }
1379
+ /**
1380
+ * Adds a task to a specific startup phase with timeout.
1381
+ *
1382
+ * @param phase - The startup phase from {@link StartupPhase}
1383
+ * @param taskName - Unique identifier for the task
1384
+ * @param task - Async function to execute
1385
+ * @param timeoutMs - Task timeout in milliseconds {@default 10000}
1386
+ */
1387
+ addTask(phase, taskName, task, timeoutMs = 1e4) {
1388
+ super.addTask(phase, taskName, task, timeoutMs);
1389
+ }
1390
+ canAddTask() {
1391
+ if (this.hasStarted) throw new require_SeedcordError.SeedcordError(1101);
1392
+ if (this.isStartingUp) throw new require_SeedcordError.SeedcordError(1102);
1393
+ return true;
1394
+ }
1395
+ canRemoveTask() {
1396
+ if (this.isStartingUp) throw new require_SeedcordError.SeedcordError(1103);
1397
+ return true;
1398
+ }
1399
+ getTaskType() {
1400
+ return "startup";
1401
+ }
1402
+ async executeTasksInPhase(phase, tasks) {
1403
+ const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));
1404
+ return Promise.allSettled(promises);
1405
+ }
1406
+ /**
1407
+ * Executes the coordinated startup sequence.
1408
+ *
1409
+ * Runs all registered tasks across startup phases in the correct order.
1410
+ * Each phase completes before the next phase begins. Tasks within a phase
1411
+ * are executed sequentially to maintain predictable initialization.
1412
+ *
1413
+ * @returns Promise that resolves when startup is complete
1414
+ * @throws An {@link Error} If startup fails or is called multiple times
1415
+ * @example
1416
+ * ```typescript
1417
+ * const startup = new CoordinatedStartup();
1418
+ * startup.addTask(StartupPhase.Services, 'database', () => db.connect(), 10000);
1419
+ * await startup.run();
1420
+ * ```
1421
+ */
1422
+ async run() {
1423
+ if (this.hasStarted) {
1424
+ this.logger.warn("Startup sequence has already completed");
1425
+ return;
1426
+ }
1427
+ if (this.isStartingUp) {
1428
+ this.logger.warn("Startup sequence already in progress");
1429
+ return;
1430
+ }
1431
+ this.isStartingUp = true;
1432
+ this.logger.info(`${chalk.default.bold.green("Starting")} coordinated startup sequence`);
1433
+ this.emit("startup:start");
1434
+ try {
1435
+ for (const phase of PHASE_ORDER) {
1436
+ if (!this.isStartingUp) {
1437
+ this.logger.warn("Startup sequence aborted");
1438
+ return;
1439
+ }
1440
+ await this.runPhase(phase);
1441
+ }
1442
+ this.hasStarted = true;
1443
+ this.logger.info(`${chalk.default.bold.green("Coordinated startup completed")} successfully`);
1444
+ this.emit("startup:complete");
1445
+ } catch (error) {
1446
+ if (!this.isStartingUp) {
1447
+ this.logger.warn("Startup sequence aborted during error handling");
1448
+ return;
1449
+ }
1450
+ this.logger.error(`${chalk.default.bold.red("Coordinated startup failed")}`);
1451
+ this.emit("startup:error", error);
1452
+ throw error;
1453
+ } finally {
1454
+ this.isStartingUp = false;
1455
+ }
1456
+ }
1457
+ async runTaskWithTimeout(phase, task) {
1458
+ this.logger.info(`${chalk.default.italic("Starting")} task ${chalk.default.bold.cyan(task.name)} in phase ${chalk.default.bold.magenta(StartupPhase[phase])}`);
1459
+ let timeoutId;
1460
+ try {
1461
+ await Promise.race([task.task(), new Promise((_, reject) => {
1462
+ timeoutId = setTimeout(() => {
1463
+ reject(new require_SeedcordError.SeedcordError(1106, [task.name, task.timeout]));
1464
+ }, task.timeout);
1465
+ })]);
1466
+ this.logger.info(`${chalk.default.italic("Completed")} task ${chalk.default.bold.cyan(task.name)} in phase ${chalk.default.bold.magenta(StartupPhase[phase])}`);
1467
+ } catch (error) {
1468
+ if (!this.isStartingUp) return;
1469
+ this.logger.error(`${chalk.default.italic("Failed")} task ${chalk.default.bold.cyan(task.name)} in phase ${chalk.default.bold.magenta(StartupPhase[phase])}:`, error);
1470
+ throw error;
1471
+ } finally {
1472
+ if (timeoutId) clearTimeout(timeoutId);
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Aborts the startup sequence if it is currently running.
1477
+ */
1478
+ abort() {
1479
+ if (this.isStartingUp) {
1480
+ this.isStartingUp = false;
1481
+ this.logger.warn("Aborting coordinated startup sequence");
1482
+ }
1483
+ }
1484
+ /**
1485
+ * Check if startup has completed
1486
+ */
1487
+ get isReady() {
1488
+ return this.hasStarted;
1489
+ }
1490
+ /**
1491
+ * Check if startup is currently running
1492
+ */
1493
+ get isRunning() {
1494
+ return this.isStartingUp;
1495
+ }
1116
1496
  };
1117
1497
 
1498
+ //#endregion
1499
+ //#region src/index.ts
1500
+ /** Package version */
1501
+ const version = "0.7.0";
1502
+
1503
+ //#endregion
1118
1504
  exports.CooldownManager = CooldownManager;
1119
1505
  exports.CoordinatedLifecycle = CoordinatedLifecycle;
1120
1506
  exports.CoordinatedShutdown = CoordinatedShutdown;
1121
1507
  exports.CoordinatedStartup = CoordinatedStartup;
1122
1508
  exports.HealthCheck = HealthCheck;
1123
1509
  exports.Logger = Logger;
1124
- exports.SeedcordError = SeedcordError;
1125
- exports.SeedcordErrorCode = SeedcordErrorCode;
1126
- exports.SeedcordErrors = SeedcordErrors;
1127
- exports.SeedcordRangeError = SeedcordRangeError;
1128
- exports.SeedcordTypeError = SeedcordTypeError;
1510
+ exports.LoggerChannelRegistry = LoggerChannelRegistry;
1511
+ exports.LoggerUtilities = LoggerUtilities;
1512
+ exports.SeedcordErrorCode = require_SeedcordError.SeedcordErrorCode;
1129
1513
  exports.ShutdownPhase = ShutdownPhase;
1130
1514
  exports.StartupPhase = StartupPhase;
1131
1515
  exports.StrictEventEmitter = StrictEventEmitter;
1132
- exports.formatSeedcordErrorMessage = formatSeedcordErrorMessage;
1133
- exports.isSeedcordError = isSeedcordError;
1134
- exports.seedcordErrorMessages = messages;
1135
- //# sourceMappingURL=index.cjs.map
1516
+ exports.isSeedcordError = require_SeedcordError.isSeedcordError;
1517
+ exports.version = version;
1136
1518
  //# sourceMappingURL=index.cjs.map