@seedcord/core 0.10.0 → 0.11.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.
Files changed (63) hide show
  1. package/README.md +27 -9
  2. package/dist/CoordinatedShutdown-H5YjZn8A.d.mts +78 -0
  3. package/dist/CoordinatedStartup-CGnJrqcl.mjs +168 -0
  4. package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +1 -0
  5. package/dist/{HmrModuleHandler-DOAtah3j.mjs → HmrModuleHandler-8ZC-UuZh.mjs} +9 -22
  6. package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +1 -0
  7. package/dist/PluginHost-B8kxLOeb.mjs +245 -0
  8. package/dist/PluginHost-B8kxLOeb.mjs.map +1 -0
  9. package/dist/{Plugin-Dhx_ivqj.d.mts → PluginHost-DB4j8unu.d.mts} +110 -17
  10. package/dist/PluginLifecycle-AhQ46NIr.mjs +158 -0
  11. package/dist/PluginLifecycle-AhQ46NIr.mjs.map +1 -0
  12. package/dist/ServerHost-CtmAhEXn.mjs +168 -0
  13. package/dist/ServerHost-CtmAhEXn.mjs.map +1 -0
  14. package/dist/ServerHost-DYeh0KTk.d.mts +40 -0
  15. package/dist/bindViteEnv-BgFewlZ0.d.mts +11 -0
  16. package/dist/bindViteEnv-DWq5rftJ.mjs +88 -0
  17. package/dist/bindViteEnv-DWq5rftJ.mjs.map +1 -0
  18. package/dist/env-internal.index.d.mts +2 -0
  19. package/dist/env-internal.index.mjs +3 -0
  20. package/dist/hmr.index.d.mts +0 -1
  21. package/dist/hmr.index.mjs +1 -1
  22. package/dist/{cursor-BTU5bGoo.d.mts → index-Du71Xx69.d.mts} +253 -6
  23. package/dist/index.d.mts +19 -84
  24. package/dist/index.mjs +17 -37
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/internal.index.d.mts +186 -22
  27. package/dist/internal.index.mjs +714 -19
  28. package/dist/internal.index.mjs.map +1 -1
  29. package/dist/node-internal.index.d.mts +25 -70
  30. package/dist/node-internal.index.mjs +83 -297
  31. package/dist/node-internal.index.mjs.map +1 -1
  32. package/dist/node.index.d.mts +3 -2
  33. package/dist/node.index.mjs +53 -200
  34. package/dist/node.index.mjs.map +1 -1
  35. package/dist/{phases-D_zQBXga.d.mts → phases-FTM69J3a.d.mts} +4 -4
  36. package/dist/plugin.index.d.mts +2 -2
  37. package/dist/plugin.index.mjs +2 -2
  38. package/dist/publishDefault-D3dAyzZ9.mjs +7 -0
  39. package/dist/publishDefault-D3dAyzZ9.mjs.map +1 -0
  40. package/dist/subscribers-Ce3SWy23.mjs +1913 -0
  41. package/dist/subscribers-Ce3SWy23.mjs.map +1 -0
  42. package/package.json +24 -17
  43. package/dist/Bus-BxPa-Uvp.mjs +0 -866
  44. package/dist/Bus-BxPa-Uvp.mjs.map +0 -1
  45. package/dist/HmrModuleHandler-DOAtah3j.mjs.map +0 -1
  46. package/dist/Pluggable-CwKbLui3.d.mts +0 -162
  47. package/dist/Pluggable-afy2H0Dr.mjs +0 -339
  48. package/dist/Pluggable-afy2H0Dr.mjs.map +0 -1
  49. package/dist/Plugin-CmNW5n6a.mjs +0 -122
  50. package/dist/Plugin-CmNW5n6a.mjs.map +0 -1
  51. package/dist/contextMenuLeaves-COzDYa6c.d.mts +0 -17
  52. package/dist/cursor-CgNBnJfu.mjs +0 -802
  53. package/dist/cursor-CgNBnJfu.mjs.map +0 -1
  54. package/dist/devChannel-CQU7hq7Q.mjs +0 -14
  55. package/dist/devChannel-CQU7hq7Q.mjs.map +0 -1
  56. package/dist/index-B_-EjyvS.d.mts +0 -135
  57. package/dist/publishDefault-BuSZPbjT.mjs +0 -14
  58. package/dist/publishDefault-BuSZPbjT.mjs.map +0 -1
  59. package/dist/slashRouteLeaves-k1hJnXDO.mjs +0 -31
  60. package/dist/slashRouteLeaves-k1hJnXDO.mjs.map +0 -1
  61. package/dist/subscribers-xGy7a7xR.mjs +0 -167
  62. package/dist/subscribers-xGy7a7xR.mjs.map +0 -1
  63. package/dist/types-BG0BimVr.d.mts +0 -13
@@ -1,47 +1,91 @@
1
- import { a as RegisterSubscriber, c as VerifyWebhooks, f as Subscriber, g as CommandMetadataKey, i as RegisterDefaults, k as contextMenuLeaves, n as busLoggerOf, o as RegisteredCount, r as registrationFor, s as UnregisterSubscriber, w as SubscribeMetadataKey } from "./Bus-BxPa-Uvp.mjs";
2
- import { r as CommandInjector, t as ConfigGuilds } from "./subscribers-xGy7a7xR.mjs";
3
- import { t as PublishDefault } from "./publishDefault-BuSZPbjT.mjs";
4
1
  import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
5
- import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
6
- import { n as isCommandClass, t as slashRouteLeaves } from "./slashRouteLeaves-k1hJnXDO.mjs";
7
- import { t as HmrModuleHandler } from "./HmrModuleHandler-DOAtah3j.mjs";
8
- import { i as settleWithin, n as shutdownOf, r as assertDeclaredRuntime } from "./Pluggable-afy2H0Dr.mjs";
9
- import { SlashCommandBuilder } from "@discordjs/builders";
10
- import { Routes } from "discord-api-types/v10";
2
+ import { n as settleWithin } from "./PluginLifecycle-AhQ46NIr.mjs";
3
+ import { n as shutdownOf, r as assertDeclaredRuntime } from "./ServerHost-CtmAhEXn.mjs";
11
4
  import { SeedcordErrorCode, paint } from "@seedcord/errors";
12
5
  import { SeedcordError } from "@seedcord/errors/internal";
13
6
  import { Logger } from "@seedcord/logger";
14
- import { formatFilePath } from "@seedcord/utils";
7
+ import { botConfigDir, isPortInUse, setBuiltFilesHook } from "@seedcord/utils/internal";
15
8
  import { Envapter } from "envapt";
16
- import { resolve } from "node:path";
17
9
  import { createServer } from "node:http";
18
- import { traverseDirectory } from "@seedcord/utils/node";
19
10
 
20
11
  //#region src/node/Lifecycle/drainWindow.ts
21
12
  const DRAIN_WINDOW_MS = 5e3;
22
13
  const DRAIN_TASK_TIMEOUT_MS = 6e3;
23
- async function drainInFlight(inFlight, timeoutMs, logger, label) {
24
- await settleWithin(Promise.allSettled(inFlight), timeoutMs);
25
- if (inFlight.size > 0) logger.warn(`${label}: ${String(inFlight.size)} still running when the drain window closed`);
26
- }
14
+
15
+ //#endregion
16
+ //#region src/node/Lifecycle/InFlight.ts
17
+ var InFlight = class {
18
+ logger;
19
+ label;
20
+ running = /* @__PURE__ */ new Set();
21
+ isClosed = false;
22
+ constructor(logger, label) {
23
+ this.logger = logger;
24
+ this.label = label;
25
+ }
26
+ get closed() {
27
+ return this.isClosed;
28
+ }
29
+ close() {
30
+ this.isClosed = true;
31
+ }
32
+ track(work) {
33
+ this.running.add(work);
34
+ const forget = () => {
35
+ this.running.delete(work);
36
+ };
37
+ work.then(forget, forget);
38
+ }
39
+ async drain(timeoutMs) {
40
+ await settleWithin(this.settleAll(), timeoutMs);
41
+ if (this.running.size > 0) this.logger.warn(`${this.label}: ${String(this.running.size)} still running when the drain window closed`);
42
+ }
43
+ async settleAll() {
44
+ while (this.running.size > 0) await Promise.allSettled(this.running);
45
+ }
46
+ };
27
47
 
28
48
  //#endregion
29
49
  //#region src/node/HealthResponder.ts
30
50
  const HTTP_OK = 200;
51
+ const HTTP_SERVICE_UNAVAILABLE = 503;
31
52
  const DEFAULT_HEALTH_CHECK_PATH = "/health";
32
- var HealthResponder = class {
53
+ const JSON_HEADERS = { "Content-Type": "application/json" };
54
+ function healthCheckConfig(option) {
55
+ if (option === false) return void 0;
56
+ return typeof option === "object" ? option : {};
57
+ }
58
+ var HealthResponder = class HealthResponder {
33
59
  path;
34
60
  constructor(path) {
35
61
  this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;
36
62
  }
63
+ static fromOption(option) {
64
+ const config = healthCheckConfig(option);
65
+ return config && new HealthResponder(config.path);
66
+ }
37
67
  tryRespond(req, res) {
38
- if (req.method !== "GET" || req.url !== this.path) return false;
39
- res.writeHead(HTTP_OK, { "Content-Type": "application/json" });
40
- res.end(JSON.stringify({
68
+ if (!this.matches(req.method, req.url)) return false;
69
+ res.writeHead(HTTP_OK, JSON_HEADERS);
70
+ res.end(this.body());
71
+ return true;
72
+ }
73
+ answer(request, ready) {
74
+ if (!this.matches(request.method, new URL(request.url).pathname)) return void 0;
75
+ if (!ready) return new Response(null, { status: HTTP_SERVICE_UNAVAILABLE });
76
+ return new Response(this.body(), {
77
+ status: HTTP_OK,
78
+ headers: JSON_HEADERS
79
+ });
80
+ }
81
+ matches(method, path) {
82
+ return method === "GET" && path === this.path;
83
+ }
84
+ body() {
85
+ return JSON.stringify({
41
86
  status: "ok",
42
87
  timestamp: Date.now()
43
- }));
44
- return true;
88
+ });
45
89
  }
46
90
  };
47
91
 
@@ -71,8 +115,8 @@ var HealthCheck = class HealthCheck {
71
115
  return this.responder.path;
72
116
  }
73
117
  static fromOption(shutdown, option) {
74
- if (option === false) return void 0;
75
- return new HealthCheck(shutdown, typeof option === "object" ? option : void 0);
118
+ const config = healthCheckConfig(option);
119
+ return config && new HealthCheck(shutdown, config);
76
120
  }
77
121
  async init() {
78
122
  return new Promise((resolve, reject) => {
@@ -82,13 +126,19 @@ var HealthCheck = class HealthCheck {
82
126
  res.end(JSON.stringify({ status: "not found" }));
83
127
  });
84
128
  this.server = server;
85
- const onListenError = (err) => reject(err);
129
+ const onListenError = (error) => {
130
+ if (isPortInUse(error)) {
131
+ reject(new SeedcordError(SeedcordErrorCode.PortInUse, [this._port, "healthCheck.port"], { cause: error }));
132
+ return;
133
+ }
134
+ reject(error);
135
+ };
86
136
  server.on("error", onListenError);
87
137
  server.once("listening", () => {
88
138
  server.removeListener("error", onListenError);
89
139
  server.on("error", (err) => this.logger.error("Health check server error", err));
90
140
  const address = this._host ?? "localhost";
91
- this.logger.info(`${paint.mint.bold("✔︎")} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`);
141
+ this.logger.info(`${paint.mint.bold(paint.check)} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`);
92
142
  resolve();
93
143
  });
94
144
  if (this._host) {
@@ -117,276 +167,12 @@ var HealthCheck = class HealthCheck {
117
167
  };
118
168
 
119
169
  //#endregion
120
- //#region src/node/subscribers/SubscriberLoader.ts
121
- var SubscriberLoader = class {
122
- bus;
123
- directory;
124
- isInitialized = false;
125
- hmrHandler;
126
- constructor(bus, directory) {
127
- this.bus = bus;
128
- this.directory = directory;
129
- if (!this.directory) return;
130
- if (!Envapter.isDevelopment && !Envapter.isTest) return;
131
- this.hmrHandler = new HmrModuleHandler({
132
- handlersDir: this.directory,
133
- isHandler: this.isSubscriber.bind(this),
134
- registerHandler: this.registerSubscriber.bind(this),
135
- unregisterHandler: this.unregisterSubscriber.bind(this),
136
- getArtifacts: this.getArtifacts.bind(this),
137
- logger: busLoggerOf(this.bus)
138
- });
139
- }
140
- /** @internal */
141
- async init() {
142
- if (this.isInitialized) return;
143
- this.isInitialized = true;
144
- this.bus[RegisterDefaults]();
145
- const { directory } = this;
146
- if (directory) {
147
- busLoggerOf(this.bus).debug(paint.mute(directory));
148
- await this.load(directory);
149
- busLoggerOf(this.bus).utils.list([`${paint.iris.bold(this.bus[RegisteredCount])} subscribers`], paint.mint.bold("Loaded"), "debug");
150
- }
151
- if (!Envapter.isTest) await this.bus[VerifyWebhooks]();
152
- }
153
- /** @internal */
154
- async onHmr(event) {
155
- if (this.hmrHandler) await this.hmrHandler.handle(event);
156
- }
157
- async load(dir) {
158
- await traverseDirectory(dir, (fullPath, relativePath, imported) => {
159
- for (const exportName of Object.keys(imported)) {
160
- const value = imported[exportName];
161
- if (!this.isSubscriber(value)) continue;
162
- this.registerSubscriber(value);
163
- this.hmrHandler?.trackHandler(fullPath, value);
164
- busLoggerOf(this.bus).utils.registration(value.name, relativePath, void 0, "trace");
165
- }
166
- });
167
- }
168
- registerSubscriber(ctor) {
169
- this.bus[RegisterSubscriber](registrationFor(ctor));
170
- }
171
- unregisterSubscriber(ctor, artifacts) {
172
- this.bus[UnregisterSubscriber](ctor, artifacts);
173
- }
174
- getArtifacts(ctor) {
175
- return [Reflect.getMetadata(SubscribeMetadataKey, ctor).subscriber];
176
- }
177
- isSubscriber(value) {
178
- if (typeof value !== "function") return false;
179
- return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);
180
- }
181
- };
182
-
183
- //#endregion
184
- //#region src/commands/deployTarget.ts
185
- function resolveDeployTarget(meta, configured, commandName) {
186
- if (meta.scope === "global") return { scope: "global" };
187
- if (meta.scope === "config") return configured.length > 0 ? guildTarget(configured) : { scope: "global" };
188
- if (configured.length === 0 && meta.guilds.includes("config")) throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildsEmpty, [commandName]);
189
- return guildTarget(meta.guilds.flatMap((target) => target === "config" ? configured : target));
190
- }
191
- function guildTarget(guilds) {
192
- return {
193
- scope: "guild",
194
- guilds: [...new Set(guilds)]
195
- };
196
- }
197
-
198
- //#endregion
199
- //#region src/node/commands/CommandRegistry.ts
200
- function indexById(commands) {
201
- return new Map(commands.map((command) => [command.id, command]));
202
- }
203
- /**
204
- * Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.
205
- *
206
- * @internal
207
- */
208
- var CommandRegistry = class {
209
- core;
210
- logger = new Logger("Commands", { channel: "commands" });
211
- isInitialised = false;
212
- globalCommands = [];
213
- guildCommands = /* @__PURE__ */ new Map();
214
- ctorToCommand = /* @__PURE__ */ new Map();
215
- loading = false;
216
- loadedCommands = [];
217
- hmrHandler;
218
- pendingEvents = /* @__PURE__ */ new Map();
219
- injector = new CommandInjector();
220
- dir;
221
- configuredGuilds;
222
- constructor(core) {
223
- this.core = core;
224
- const { commands } = core.config.bot;
225
- if (!commands.path) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
226
- this.dir = commands.path;
227
- this.configuredGuilds = commands.guilds ?? [];
228
- if (!Envapter.isDevelopment && !Envapter.isTest) return;
229
- this.hmrHandler = new HmrModuleHandler({
230
- handlersDir: this.dir,
231
- isHandler: isCommandClass,
232
- registerHandler: this.registerCommand.bind(this),
233
- unregisterHandler: this.unregisterCommand.bind(this),
234
- getArtifacts: this.getArtifacts.bind(this),
235
- logger: this.logger
236
- });
237
- }
238
- getArtifacts(ctor) {
239
- return this.ctorToCommand.get(ctor);
240
- }
241
- /** @internal */
242
- async init() {
243
- if (this.isInitialised) return;
244
- this.isInitialised = true;
245
- this.loading = true;
246
- this.loadedCommands.length = 0;
247
- try {
248
- await this.loadCommands(this.dir);
249
- } finally {
250
- this.loading = false;
251
- }
252
- this.reportLoad();
253
- getDevChannel()?.on("seedcord:refresh-commands", (data) => {
254
- this.refresh(data.shouldRefresh);
255
- });
256
- }
257
- reportLoad() {
258
- const { utils } = this.logger;
259
- utils.summary("Loaded commands", {
260
- global: this.globalCommands.length,
261
- "guild groups": this.guildCommands.size
262
- }, "debug");
263
- let slash = 0;
264
- let menu = 0;
265
- for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
266
- else menu++;
267
- utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
268
- "slash commands": slash,
269
- "context menus": menu
270
- })], "debug");
271
- }
272
- /** @internal */
273
- async refresh(shouldRefresh = true) {
274
- if (!shouldRefresh) {
275
- this.logger.debug(paint.italic("Command refresh cancelled."));
276
- this.pendingEvents.clear();
277
- return;
278
- }
279
- this.logger.debug(paint.italic("Refreshing commands..."));
280
- for (const event of this.pendingEvents.values()) await this.hmrHandler?.handle(event);
281
- this.pendingEvents.clear();
282
- await this.setCommands();
283
- }
284
- /** @internal */
285
- async onHmr(event) {
286
- if (event.file.startsWith(resolve(process.cwd(), this.dir))) {
287
- this.pendingEvents.delete(event.file);
288
- this.pendingEvents.set(event.file, event);
289
- getDevChannel()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f)) });
290
- } else await this.hmrHandler?.handle(event);
291
- }
292
- async loadCommands(dir) {
293
- await traverseDirectory(dir, (fullPath, rel, mod) => {
294
- for (const exported of Object.values(mod)) if (isCommandClass(exported)) {
295
- this.registerCommand(exported, rel);
296
- this.hmrHandler?.trackHandler(fullPath, exported);
297
- }
298
- });
299
- }
300
- registerCommand(Ctor, rel) {
301
- const meta = Reflect.getMetadata(CommandMetadataKey, Ctor);
302
- if (!meta) return;
303
- const comp = new Ctor().component;
304
- const kind = comp instanceof SlashCommandBuilder ? "slash command" : "context menu";
305
- const target = resolveDeployTarget(meta, this.configuredGuilds, Ctor.name);
306
- if (target.scope === "global") this.globalCommands.push(comp);
307
- else for (const g of target.guilds) {
308
- const arr = this.guildCommands.get(g) ?? [];
309
- arr.push(comp);
310
- this.guildCommands.set(g, arr);
311
- }
312
- this.ctorToCommand.set(Ctor, {
313
- name: comp.name,
314
- ...target
315
- });
316
- if (this.loading) this.loadedCommands.push({
317
- name: comp.name,
318
- from: formatFilePath(rel),
319
- kind
320
- });
321
- }
322
- unregisterCommand(ctor, artifacts) {
323
- const info = artifacts ?? this.ctorToCommand.get(ctor);
324
- if (!info) return;
325
- if (info.scope === "global") {
326
- const idx = this.globalCommands.findIndex((c) => c.name === info.name);
327
- if (idx !== -1) this.globalCommands.splice(idx, 1);
328
- } else for (const g of info.guilds) {
329
- const arr = this.guildCommands.get(g);
330
- if (!arr) continue;
331
- const idx = arr.findIndex((c) => c.name === info.name);
332
- if (idx !== -1) arr.splice(idx, 1);
333
- }
334
- this.ctorToCommand.delete(ctor);
335
- }
336
- /** @internal */
337
- async setCommands() {
338
- const result = {
339
- global: /* @__PURE__ */ new Map(),
340
- guilds: /* @__PURE__ */ new Map()
341
- };
342
- const appId = this.core.applicationId;
343
- if (this.globalCommands.length > 0) {
344
- result.global = indexById(await this.put(Routes.applicationCommands(appId), this.globalCommands));
345
- const tag = this.globalCommands.length === 1 ? "command" : "commands";
346
- this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
347
- }
348
- for (const [guildId, commands] of this.guildCommands.entries()) {
349
- const deployed = await this.putGuild(appId, guildId, commands);
350
- result.guilds.set(guildId, indexById(deployed));
351
- const tag = commands.length === 1 ? "command" : "commands";
352
- this.logger.utils.block(`Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
353
- }
354
- this.injector.inject(result, this.allCommands());
355
- this.core.bus[PublishDefault]("commandsDeployed", deployedPayload(result));
356
- return result;
357
- }
358
- async putGuild(appId, guildId, commands) {
359
- try {
360
- return await this.put(Routes.applicationGuildCommands(appId, guildId), commands);
361
- } catch (error) {
362
- throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildDeployFailed, [guildId], { cause: error });
363
- }
364
- }
365
- async put(route, commands) {
366
- const body = commands.map((command) => command.toJSON());
367
- const { rest } = this.core;
368
- return await rest.put(route, { body });
369
- }
370
- /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */
371
- allCommands() {
372
- return [...this.globalCommands, ...this.guildCommands.values()].flat();
373
- }
374
- /** @internal */
375
- routeLeaves() {
376
- return slashRouteLeaves(this.allCommands());
377
- }
378
- /** @internal */
379
- contextMenuLeaves() {
380
- return contextMenuLeaves(this.allCommands());
381
- }
382
- };
383
- function deployedPayload(result) {
384
- const guilds = {};
385
- for (const [guildId, deployed] of result.guilds) guilds[guildId] = [...deployed.values()];
386
- return {
387
- global: [...result.global.values()],
388
- guilds
389
- };
170
+ //#region src/node/envaptBaseDir.ts
171
+ function setEnvaptBaseDir() {
172
+ setBuiltFilesHook(() => {
173
+ const configDir = botConfigDir();
174
+ if (configDir !== void 0 && Envapter.source.supportsFiles) Envapter.baseDir = configDir;
175
+ });
390
176
  }
391
177
 
392
178
  //#endregion
@@ -394,5 +180,5 @@ function deployedPayload(result) {
394
180
  assertDeclaredRuntime();
395
181
 
396
182
  //#endregion
397
- export { CommandRegistry, DRAIN_TASK_TIMEOUT_MS, DRAIN_WINDOW_MS, HealthCheck, HealthResponder, ShutdownPhase, StartupPhase, SubscriberLoader, drainInFlight, settleWithin, shutdownOf };
183
+ export { DRAIN_TASK_TIMEOUT_MS, DRAIN_WINDOW_MS, HealthCheck, HealthResponder, InFlight, ShutdownPhase, StartupPhase, setEnvaptBaseDir, settleWithin, shutdownOf };
398
184
  //# sourceMappingURL=node-internal.index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/subscribers/SubscriberLoader.ts","../src/commands/deployTarget.ts","../src/node/commands/CommandRegistry.ts","../src/node-internal.index.ts"],"sourcesContent":["import { settleWithin } from './withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\n// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n\nexport async function drainInFlight(\n inFlight: ReadonlySet<Promise<unknown>>,\n timeoutMs: number,\n logger: Logger,\n label: string\n): Promise<void> {\n await settleWithin(Promise.allSettled(inFlight), timeoutMs);\n // nothing cancels these before the process exits\n if (inFlight.size > 0) logger.warn(`${label}: ${String(inFlight.size)} still running when the drain window closed`);\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (req.method !== 'GET' || req.url !== this.path) return false;\n\n res.writeHead(HTTP_OK, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));\n return true;\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n if (option === false) return undefined;\n return new HealthCheck(shutdown, typeof option === 'object' ? option : undefined);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (err: Error): void => reject(err);\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold('✔︎')} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { paint } from '@seedcord/errors';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Envapter } from 'envapt';\n\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { SubscribeMetadataKey } from '#src/metadataKeys';\nimport { busLoggerOf } from '#subscribers/Bus';\nimport { registrationFor, Subscriber } from '#subscribers/index';\nimport {\n RegisterDefaults,\n RegisteredCount,\n RegisterSubscriber,\n UnregisterSubscriber,\n VerifyWebhooks\n} from '#subscribers/slots';\n\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { SubscribeMetadataEntry } from '#subscribers/decorators/Subscribe';\nimport type { StoredSubscriberCtor, Bus } from '#subscribers/index';\nimport type { SubscriptionKey } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\ntype SubscriberArtifact = SubscriptionKey[];\n\n// loads subscribers off the filesystem into a Bus and stores their hot-reload wiring. node only, a bundled isolate registers from the manifest instead\nexport class SubscriberLoader implements Initializeable, HmrAware {\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<StoredSubscriberCtor, void, SubscriberArtifact>;\n\n constructor(\n private readonly bus: Bus,\n private readonly directory: string | null\n ) {\n if (!this.directory) return;\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n\n this.hmrHandler = new HmrModuleHandler({\n handlersDir: this.directory,\n isHandler: this.isSubscriber.bind(this),\n registerHandler: this.registerSubscriber.bind(this),\n unregisterHandler: this.unregisterSubscriber.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: busLoggerOf(this.bus)\n });\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n this.bus[RegisterDefaults]();\n\n const { directory } = this;\n if (directory) {\n busLoggerOf(this.bus).debug(paint.mute(directory));\n await this.load(directory);\n busLoggerOf(this.bus).utils.list(\n [`${paint.iris.bold(this.bus[RegisteredCount])} subscribers`],\n paint.mint.bold('Loaded'),\n 'debug'\n );\n }\n\n if (!Envapter.isTest) await this.bus[VerifyWebhooks]();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (this.hmrHandler) await this.hmrHandler.handle(event);\n }\n\n private async load(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, relativePath, imported) => {\n for (const exportName of Object.keys(imported)) {\n const value = imported[exportName];\n if (!this.isSubscriber(value)) continue;\n\n this.registerSubscriber(value);\n this.hmrHandler?.trackHandler(fullPath, value);\n busLoggerOf(this.bus).utils.registration(value.name, relativePath, undefined, 'trace');\n }\n });\n }\n\n private registerSubscriber(ctor: StoredSubscriberCtor): void {\n this.bus[RegisterSubscriber](registrationFor(ctor));\n }\n\n private unregisterSubscriber(ctor: StoredSubscriberCtor, artifacts?: SubscriberArtifact): void {\n this.bus[UnregisterSubscriber](ctor, artifacts);\n }\n\n private getArtifacts(ctor: StoredSubscriberCtor): SubscriberArtifact {\n const meta = Reflect.getMetadata(SubscribeMetadataKey, ctor) as SubscribeMetadataEntry;\n return [meta.subscriber];\n }\n\n private isSubscriber(value: unknown): value is StoredSubscriberCtor {\n if (typeof value !== 'function') return false;\n return value.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, value);\n }\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { ConfigGuilds } from '#decorators/Command';\n\nimport type { CommandMeta } from '#decorators/Command';\n\nexport type DeployTarget = Exclude<CommandMeta, { scope: 'config' }>;\n\nexport function resolveDeployTarget(\n meta: CommandMeta,\n configured: readonly string[],\n commandName: string\n): DeployTarget {\n if (meta.scope === 'global') return { scope: 'global' };\n\n if (meta.scope === 'config') {\n return configured.length > 0 ? guildTarget(configured) : { scope: 'global' };\n }\n\n if (configured.length === 0 && meta.guilds.includes(ConfigGuilds)) {\n throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildsEmpty, [commandName]);\n }\n\n return guildTarget(meta.guilds.flatMap((target) => (target === ConfigGuilds ? configured : target)));\n}\n\n// a repeated id would push the same builder into one guild twice, and discord rejects a duplicate name in the put\nfunction guildTarget(guilds: readonly string[]): DeployTarget {\n return { scope: 'guild', guilds: [...new Set(guilds)] };\n}\n","import { resolve } from 'node:path';\n\nimport { SlashCommandBuilder } from '@discordjs/builders';\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { getDevChannel } from '#hmr/devChannel';\nimport { HmrModuleHandler } from '#hmr/HmrModuleHandler';\nimport { CommandInjector } from '#src/commands/CommandInjector';\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { resolveDeployTarget } from '#src/commands/deployTarget';\nimport { isCommandClass } from '#src/commands/isCommandClass';\nimport { slashRouteLeaves } from '#src/commands/slashRouteLeaves';\nimport { CommandMetadataKey } from '#src/metadataKeys';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CommandMeta } from '#decorators/Command';\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ContextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport type { DeployTarget } from '#src/commands/deployTarget';\nimport type { CommandCtor } from '#src/commands/isCommandClass';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { Initializeable } from '#src/plugin/Plugin';\nimport type { AllSubscriptions } from '#subscribers/types/Subscriptions';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { APIApplicationCommand } from 'discord-api-types/v10';\n\ntype CommandArtifact = DeployTarget & { name: string };\n\nfunction indexById(commands: readonly APIApplicationCommand[]): Map<string, APIApplicationCommand> {\n return new Map(commands.map((command) => [command.id, command]));\n}\n\n/**\n * Scans the command directory, builds each command, and PUTs the global and guild scopes to Discord.\n *\n * @internal\n */\nexport class CommandRegistry implements Initializeable, HmrAware {\n private readonly logger = new Logger('Commands', { channel: 'commands' });\n private isInitialised = false;\n\n public readonly globalCommands: CommandBuilder[] = [];\n public readonly guildCommands = new Map<string, CommandBuilder[]>();\n\n private readonly ctorToCommand = new Map<CommandCtor, CommandArtifact>();\n\n // batched during bulk load. A reload reports on the hmr channel instead\n private loading = false;\n private readonly loadedCommands: { name: string; from: string; kind: 'slash command' | 'context menu' }[] = [];\n\n private readonly hmrHandler?: HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>;\n private readonly pendingEvents = new Map<string, HmrUpdateEvent>();\n private readonly injector = new CommandInjector();\n\n private readonly dir: string;\n private readonly configuredGuilds: readonly string[];\n\n public constructor(private readonly core: CoreBase) {\n const { commands } = core.config.bot;\n if (!commands.path) {\n throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ['CommandRegistry', 'commands']);\n }\n this.dir = commands.path;\n this.configuredGuilds = commands.guilds ?? [];\n\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n this.hmrHandler = new HmrModuleHandler<CommandCtor, void, CommandArtifact | undefined>({\n handlersDir: this.dir,\n isHandler: isCommandClass,\n registerHandler: this.registerCommand.bind(this),\n unregisterHandler: this.unregisterCommand.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: this.logger\n });\n }\n\n private getArtifacts(ctor: CommandCtor): CommandArtifact | undefined {\n return this.ctorToCommand.get(ctor);\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialised) return;\n this.isInitialised = true;\n\n this.loading = true;\n this.loadedCommands.length = 0;\n try {\n await this.loadCommands(this.dir);\n } finally {\n this.loading = false;\n }\n\n this.reportLoad();\n\n getDevChannel()?.on('seedcord:refresh-commands', (data) => {\n void this.refresh(data.shouldRefresh);\n });\n }\n\n private reportLoad(): void {\n const { utils } = this.logger;\n\n utils.summary(\n 'Loaded commands',\n { global: this.globalCommands.length, 'guild groups': this.guildCommands.size },\n 'debug'\n );\n\n let slash = 0;\n let menu = 0;\n for (const command of this.loadedCommands) {\n if (command.kind === 'slash command') slash++;\n else menu++;\n }\n\n utils.block(\n 'Loaded commands',\n [\n ...utils.entries(this.loadedCommands),\n ...utils.counts({ 'slash commands': slash, 'context menus': menu })\n ],\n 'debug'\n );\n }\n\n /** @internal */\n public async refresh(shouldRefresh = true): Promise<void> {\n if (!shouldRefresh) {\n this.logger.debug(paint.italic('Command refresh cancelled.'));\n this.pendingEvents.clear();\n return;\n }\n\n this.logger.debug(paint.italic('Refreshing commands...'));\n for (const event of this.pendingEvents.values()) {\n await this.hmrHandler?.handle(event);\n }\n this.pendingEvents.clear();\n await this.setCommands();\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n if (event.file.startsWith(resolve(process.cwd(), this.dir))) {\n this.pendingEvents.delete(event.file);\n this.pendingEvents.set(event.file, event);\n getDevChannel()?.send('seedcord:commands-update-prompt', {\n files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f))\n });\n } else {\n await this.hmrHandler?.handle(event);\n }\n }\n\n private async loadCommands(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, rel, mod) => {\n for (const exported of Object.values(mod))\n if (isCommandClass(exported)) {\n this.registerCommand(exported, rel);\n this.hmrHandler?.trackHandler(fullPath, exported);\n }\n });\n }\n\n private registerCommand(Ctor: CommandCtor, rel: string): void {\n const meta = Reflect.getMetadata(CommandMetadataKey, Ctor) as CommandMeta | undefined;\n\n if (!meta) return;\n\n const instance = new Ctor();\n const comp = instance.component;\n const kind = comp instanceof SlashCommandBuilder ? 'slash command' : 'context menu';\n\n const target = resolveDeployTarget(meta, this.configuredGuilds, Ctor.name);\n\n if (target.scope === 'global') {\n this.globalCommands.push(comp);\n } else {\n for (const g of target.guilds) {\n const arr = this.guildCommands.get(g) ?? [];\n arr.push(comp);\n this.guildCommands.set(g, arr);\n }\n }\n\n this.ctorToCommand.set(Ctor, { name: comp.name, ...target });\n\n if (this.loading) {\n this.loadedCommands.push({ name: comp.name, from: formatFilePath(rel), kind });\n }\n }\n\n private unregisterCommand(ctor: CommandCtor, artifacts?: CommandArtifact): void {\n const info = artifacts ?? this.ctorToCommand.get(ctor);\n if (!info) return;\n\n if (info.scope === 'global') {\n const idx = this.globalCommands.findIndex((c) => c.name === info.name);\n if (idx !== -1) this.globalCommands.splice(idx, 1);\n } else {\n for (const g of info.guilds) {\n const arr = this.guildCommands.get(g);\n if (!arr) continue;\n const idx = arr.findIndex((c) => c.name === info.name);\n if (idx !== -1) arr.splice(idx, 1);\n }\n }\n this.ctorToCommand.delete(ctor);\n }\n\n /** @internal */\n public async setCommands(): Promise<DeployResult> {\n const result: DeployResult = { global: new Map(), guilds: new Map() };\n const appId = this.core.applicationId;\n\n if (this.globalCommands.length > 0) {\n const deployed = await this.put(Routes.applicationCommands(appId), this.globalCommands);\n result.global = indexById(deployed);\n const tag = this.globalCommands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${this.globalCommands.length} global ${tag}`,\n this.logger.utils.wrap(this.globalCommands.map((command) => command.name)),\n 'info'\n );\n }\n\n for (const [guildId, commands] of this.guildCommands.entries()) {\n const deployed = await this.putGuild(appId, guildId, commands);\n result.guilds.set(guildId, indexById(deployed));\n const tag = commands.length === 1 ? 'command' : 'commands';\n this.logger.utils.block(\n `Deployed ${paint.iris.bold(commands.length)} ${tag} to ${paint.sky.bold(guildId)}`,\n this.logger.utils.wrap(commands.map((command) => command.name)),\n 'info'\n );\n }\n\n this.injector.inject(result, this.allCommands());\n this.core.bus[PublishDefault]('commandsDeployed', deployedPayload(result));\n return result;\n }\n\n private async putGuild(\n appId: string,\n guildId: string,\n commands: readonly CommandBuilder[]\n ): Promise<APIApplicationCommand[]> {\n try {\n return await this.put(Routes.applicationGuildCommands(appId, guildId), commands);\n } catch (error) {\n throw new SeedcordError(SeedcordErrorCode.CoreCommandGuildDeployFailed, [guildId], { cause: error });\n }\n }\n\n private async put(route: `/${string}`, commands: readonly CommandBuilder[]): Promise<APIApplicationCommand[]> {\n const body = commands.map((command) => command.toJSON());\n // read here because gateway assigns core.rest after this registry is constructed\n const { rest } = this.core;\n // justified: the bulk-overwrite routes return the deployed command array\n return (await rest.put(route, { body })) as APIApplicationCommand[];\n }\n\n /** Every registered builder, global first. A guild command in N guilds appears once per guild. @internal */\n public allCommands(): CommandBuilder[] {\n return [...this.globalCommands, ...this.guildCommands.values()].flat();\n }\n\n /** @internal */\n public routeLeaves(): Set<string> {\n return slashRouteLeaves(this.allCommands());\n }\n\n /** @internal */\n public contextMenuLeaves(): ContextMenuLeaves {\n return contextMenuLeaves(this.allCommands());\n }\n}\n\nfunction deployedPayload(result: DeployResult): AllSubscriptions['commandsDeployed'] {\n const guilds: Record<string, APIApplicationCommand[]> = {};\n for (const [guildId, deployed] of result.guilds) guilds[guildId] = [...deployed.values()];\n\n return { global: [...result.global.values()], guilds };\n}\n","import { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\n\n// the edge bundle never reaches this entry\nassertDeclaredRuntime();\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#node/Lifecycle/withTimeout';\nexport { SubscriberLoader } from '#node/subscribers/SubscriberLoader';\nexport { CommandRegistry } from '#node/commands/CommandRegistry';\nexport { shutdownOf } from '#node/Pluggable';\n"],"mappings":"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"}
1
+ {"version":3,"file":"node-internal.index.mjs","names":[],"sources":["../src/node/Lifecycle/drainWindow.ts","../src/node/Lifecycle/InFlight.ts","../src/node/HealthResponder.ts","../src/node/HealthCheck.ts","../src/node/envaptBaseDir.ts","../src/node-internal.index.ts"],"sourcesContent":["// gateway already used 5s. discord's 3s first-ack deadline is the floor.\nexport const DRAIN_WINDOW_MS = 5000;\n\nconst DRAIN_HEADROOM_MS = 1000;\nexport const DRAIN_TASK_TIMEOUT_MS = DRAIN_WINDOW_MS + DRAIN_HEADROOM_MS;\n","import { settleWithin } from '#src/lifecycle/withTimeout';\n\nimport type { Logger } from '@seedcord/logger';\n\nexport class InFlight {\n private readonly running = new Set<Promise<unknown>>();\n private isClosed = false;\n\n constructor(\n private readonly logger: Logger,\n private readonly label: string\n ) {}\n\n public get closed(): boolean {\n return this.isClosed;\n }\n\n public close(): void {\n this.isClosed = true;\n }\n\n public track(work: Promise<unknown>): void {\n this.running.add(work);\n const forget = (): void => {\n this.running.delete(work);\n };\n void work.then(forget, forget);\n }\n\n public async drain(timeoutMs: number): Promise<void> {\n await settleWithin(this.settleAll(), timeoutMs);\n // nothing cancels these before the process exits\n if (this.running.size > 0) {\n this.logger.warn(`${this.label}: ${String(this.running.size)} still running when the drain window closed`);\n }\n }\n\n // settling work can track more work\n private async settleAll(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n}\n","import type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nconst HTTP_OK = 200;\nconst HTTP_SERVICE_UNAVAILABLE = 503;\nconst DEFAULT_HEALTH_CHECK_PATH = '/health';\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nexport function healthCheckConfig(option?: HealthCheckOption): HealthCheckConfig | undefined {\n if (option === false) return undefined;\n return typeof option === 'object' ? option : {};\n}\n\nexport class HealthResponder {\n public readonly path: string;\n\n constructor(path?: string) {\n this.path = path ?? DEFAULT_HEALTH_CHECK_PATH;\n }\n\n public static fromOption(option?: HealthCheckOption): HealthResponder | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthResponder(config.path);\n }\n\n public tryRespond(req: IncomingMessage, res: ServerResponse): boolean {\n if (!this.matches(req.method, req.url)) return false;\n\n res.writeHead(HTTP_OK, JSON_HEADERS);\n res.end(this.body());\n return true;\n }\n\n public answer(request: Request, ready: boolean): Response | undefined {\n if (!this.matches(request.method, new URL(request.url).pathname)) return undefined;\n if (!ready) return new Response(null, { status: HTTP_SERVICE_UNAVAILABLE });\n return new Response(this.body(), { status: HTTP_OK, headers: JSON_HEADERS });\n }\n\n private matches(method: string | undefined, path: string | undefined): boolean {\n return method === 'GET' && path === this.path;\n }\n\n private body(): string {\n return JSON.stringify({ status: 'ok', timestamp: Date.now() });\n }\n}\n","import { createServer } from 'node:http';\n\nimport { paint, SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\nimport { isPortInUse } from '@seedcord/utils/internal';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { healthCheckConfig, HealthResponder } from './HealthResponder';\n\nimport type { CoordinatedShutdown } from './Lifecycle/CoordinatedShutdown';\nimport type { HealthCheckConfig, HealthCheckOption } from '@seedcord/types';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\n\nconst HTTP_NOT_FOUND = 404;\n\nconst DEFAULT_HEALTH_CHECK_PORT = 6967;\n\nexport class HealthCheck {\n public readonly logger = new Logger('HealthCheck', { channel: 'health' });\n\n private readonly _port: number;\n private readonly _host: string | undefined;\n\n private readonly responder: HealthResponder;\n private server?: Server;\n\n constructor(shutdown: CoordinatedShutdown, options?: HealthCheckConfig) {\n this._port = options?.port ?? DEFAULT_HEALTH_CHECK_PORT;\n this._host = options?.host;\n this.responder = new HealthResponder(options?.path);\n\n shutdown.addTask(ShutdownPhase.Drain, 'stop-healthcheck-server', () => this.stop());\n }\n\n public get port(): number {\n return this._port;\n }\n\n public get host(): string | undefined {\n return this._host;\n }\n\n public get path(): string {\n return this.responder.path;\n }\n\n public static fromOption(shutdown: CoordinatedShutdown, option?: HealthCheckOption): HealthCheck | undefined {\n const config = healthCheckConfig(option);\n return config && new HealthCheck(shutdown, config);\n }\n\n public async init(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n if (this.responder.tryRespond(req, res)) return;\n\n res.writeHead(HTTP_NOT_FOUND, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ status: 'not found' }));\n });\n this.server = server;\n\n const onListenError = (error: Error): void => {\n if (isPortInUse(error)) {\n reject(\n new SeedcordError(SeedcordErrorCode.PortInUse, [this._port, 'healthCheck.port'], {\n cause: error\n })\n );\n return;\n }\n reject(error);\n };\n server.on('error', onListenError);\n\n server.once('listening', () => {\n // a late error would reject an already-settled promise, and node throws process-wide without a listener here\n server.removeListener('error', onListenError);\n server.on('error', (err) => this.logger.error('Health check server error', err));\n\n // binds all interfaces, so log an address a browser can open\n const address = this._host ?? 'localhost';\n this.logger.info(\n `${paint.mint.bold(paint.check)} Health check server listening on ${paint.sky.bold(`http://${address}:${this._port}${this.path}`)}`\n );\n resolve();\n });\n\n if (this._host) {\n this.logger.debug(`Binding health check server to ${this._host}`);\n server.listen(this._port, this._host);\n } else {\n this.logger.debug('Binding health check server to all interfaces');\n server.listen(this._port);\n }\n });\n }\n\n public stop(): Promise<void> {\n const server = this.server;\n // close() on a non-listening server invokes its callback with ERR_SERVER_NOT_RUNNING and\n // never fires 'close', so without this guard the promise would hang the shutdown phase.\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolve, reject) => {\n // settle only in the callback, a 'close'-event resolve would swallow the callback's error\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n this.logger.info(paint.coral.bold('Health check server stopped'));\n resolve();\n });\n });\n }\n}\n","import { botConfigDir, setBuiltFilesHook } from '@seedcord/utils/internal';\nimport { Envapter } from 'envapt';\n\nexport function setEnvaptBaseDir(): void {\n setBuiltFilesHook(() => {\n const configDir = botConfigDir();\n if (configDir !== undefined && Envapter.source.supportsFiles) Envapter.baseDir = configDir;\n });\n}\n","import { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\n\n// the edge bundle never reaches this entry\nassertDeclaredRuntime();\n\nexport * from '#node/Lifecycle';\nexport * from '#node/HealthCheck';\nexport { HealthResponder } from '#node/HealthResponder';\nexport { settleWithin } from '#src/lifecycle/withTimeout';\nexport { shutdownOf } from '#node/ServerHost';\nexport { setEnvaptBaseDir } from '#node/envaptBaseDir';\n"],"mappings":"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"}
@@ -1,2 +1,3 @@
1
- import { i as CoordinatedShutdown, r as CoordinatedStartup, t as Pluggable } from "./Pluggable-CwKbLui3.mjs";
2
- export { CoordinatedShutdown, CoordinatedStartup, Pluggable };
1
+ import { n as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-H5YjZn8A.mjs";
2
+ import { t as ServerHost } from "./ServerHost-DYeh0KTk.mjs";
3
+ export { CoordinatedShutdown, CoordinatedStartup, ServerHost };