@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/README.md +2 -2
- package/dist/SeedcordError-BuWVIe6m.d.mts +302 -0
- package/dist/SeedcordError-wt7_KePP.cjs +376 -0
- package/dist/SeedcordError-wt7_KePP.cjs.map +1 -0
- package/dist/SeedcordError-xd-Ib_-z.mjs +313 -0
- package/dist/SeedcordError-xd-Ib_-z.mjs.map +1 -0
- package/dist/index.cjs +1485 -1103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -656
- package/dist/index.d.mts +2930 -0
- package/dist/index.mjs +1474 -1090
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.cjs +6 -0
- package/dist/internal.index.d.cts +1 -0
- package/dist/internal.index.d.mts +2 -0
- package/dist/internal.index.mjs +3 -0
- package/package.json +44 -25
- package/dist/index.d.ts +0 -656
package/dist/index.cjs
CHANGED
|
@@ -1,1136 +1,1518 @@
|
|
|
1
|
-
'
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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.
|
|
1125
|
-
exports.
|
|
1126
|
-
exports.
|
|
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.
|
|
1133
|
-
exports.
|
|
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
|