@seedcord/http 0.1.0-next.1 → 0.1.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,31 +1,35 @@
1
1
  import { n as __reExport, t as __exportAll } from "./rolldown-runtime-BUUfjzRC.mjs";
2
- import { a as ButtonHandler, c as SlashHandler, d as buildRouteMaps, f as InteractionHandler, i as SelectMenuHandler, l as createSeedcord, m as RepliableHandler, n as Gated, o as ComponentHandler, p as AutocompleteHandler, r as ModalHandler, s as ContextMenuHandler, t as version, u as buildEngine } from "./edge.index-BAgNb6TV.mjs";
2
+ import { S as RepliableHandler, _ as createSeedcord, a as Paginator, b as InteractionHandler, c as ModalHandler, d as ComponentHandler, f as ContextMenuHandler, g as fetchApplicationId, h as Emojis, i as CursorSource, l as SelectMenuHandler, m as EmojiInjector, n as Gated, o as Subscriber, p as SlashHandler, r as ArraySource, s as WebhookLog, t as version, u as ButtonHandler, v as buildEngine, x as AutocompleteHandler, y as buildRouteMaps } from "./edge.index-Cv4plNWY.mjs";
3
3
  import { SeedcordError, validateDiscordToken } from "@seedcord/errors/internal";
4
4
  import { Envapter } from "envapt";
5
5
  import { REST } from "@discordjs/rest";
6
- import { HmrManager, InteractionMetadataKey, InteractionRoutes, interactionRoutesOf, setBotColor } from "@seedcord/core/internal";
6
+ import { Bus } from "@seedcord/core";
7
+ import { CommandInjector, HmrManager, InteractionMetadataKey, InteractionRoutes, interactionRoutesOf, setBotColor } from "@seedcord/core/internal";
7
8
  import { Logger, LoggerChannelRegistry, paint } from "@seedcord/logger";
8
9
  import { MemoryRateLimiter } from "@seedcord/rate-limiter";
9
10
  import { Routes } from "discord-api-types/v10";
10
11
  import { SeedcordErrorCode } from "@seedcord/errors";
11
12
  import { once } from "node:events";
12
13
  import { createServer } from "node:http";
13
- import { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase } from "@seedcord/core/node/internal";
14
+ import { CommandRegistry, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader } from "@seedcord/core/node/internal";
14
15
  import { installNodeDefaults } from "@seedcord/logger/node";
15
16
  import { SeedcordBrand } from "@seedcord/types/internal";
16
17
  import { HmrModuleHandler } from "@seedcord/core/hmr";
17
18
  import { formatFilePath } from "@seedcord/utils";
18
19
  import { traverseDirectory } from "@seedcord/utils/node";
20
+ import chalk from "chalk";
21
+ import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
19
22
 
20
23
  export * from "@seedcord/core"
21
24
 
22
25
  //#region src/manifest/RouteManifest.ts
23
26
  /** @internal */
24
27
  const EMPTY_MANIFEST = {
25
- commands: [],
26
- components: [],
27
- autocomplete: [],
28
- subscribers: []
28
+ commandRoutes: [],
29
+ componentRoutes: [],
30
+ autocompleteRoutes: [],
31
+ subscriberRoutes: [],
32
+ middlewareRoutes: []
29
33
  };
30
34
 
31
35
  //#endregion
@@ -41,7 +45,7 @@ var InteractionDispatcher = class {
41
45
  maps;
42
46
  targets;
43
47
  /** @internal */
44
- logger = new Logger("Interactions");
48
+ logger = new Logger("Interactions", { channel: "interactions" });
45
49
  isInitialized = false;
46
50
  hmrHandler;
47
51
  rowOwners = /* @__PURE__ */ new Map();
@@ -74,7 +78,7 @@ var InteractionDispatcher = class {
74
78
  this.registerHandler(value, relativePath);
75
79
  this.hmrHandler?.trackHandler(fullPath, value);
76
80
  }
77
- }, this.logger);
81
+ });
78
82
  } finally {
79
83
  this.loading = false;
80
84
  }
@@ -84,6 +88,19 @@ var InteractionDispatcher = class {
84
88
  async onHmr(event) {
85
89
  await this.hmrHandler?.handle(event);
86
90
  }
91
+ warnUnhandledRoutes(commandLeaves) {
92
+ this.warnMissing(commandLeaves, this.maps.slash, "Slash route", "@SlashRoute");
93
+ }
94
+ warnUnhandledContextMenuRoutes(leaves) {
95
+ this.warnMissing(leaves.user, this.maps.userContextMenu, "User context menu", "@ContextMenuRoute");
96
+ this.warnMissing(leaves.message, this.maps.messageContextMenu, "Message context menu", "@ContextMenuRoute");
97
+ }
98
+ warnMissing(names, map, label, decorator) {
99
+ for (const name of names) {
100
+ if (map.has(name)) continue;
101
+ this.logger.warn(`${label} ${paint.sky.bold(name)} has no registered ${chalk.bold(decorator)} handler.`);
102
+ }
103
+ }
87
104
  isHandler(value) {
88
105
  if (typeof value !== "function") return false;
89
106
  return (value.prototype instanceof InteractionHandler || value.prototype instanceof AutocompleteHandler) && Reflect.hasMetadata(InteractionMetadataKey, value);
@@ -145,15 +162,17 @@ var InteractionDispatcher = class {
145
162
  };
146
163
  }
147
164
  registerHandler(ctor, relativePath) {
165
+ const from = formatFilePath(relativePath);
148
166
  const writes = [];
149
167
  for (const [route, keys] of interactionRoutesOf(ctor)) {
150
168
  const target = this.targetFor(route);
151
169
  for (const key of keys) {
152
- const existing = this.rowOwners.get(`${target.kind}:${key}`);
170
+ const routeId = `${target.kind}:${key}`;
171
+ const existing = this.rowOwners.get(routeId);
153
172
  if (existing && existing.ctor !== ctor) throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [
154
- key,
155
- existing.ctor.name,
156
- ctor.name
173
+ routeId,
174
+ `${existing.ctor.name} (${existing.from})`,
175
+ `${ctor.name} (${from})`
157
176
  ]);
158
177
  writes.push([target, key]);
159
178
  }
@@ -164,15 +183,15 @@ var InteractionDispatcher = class {
164
183
  target.map.set(key, {
165
184
  kind: target.kind,
166
185
  routeId,
167
- load: () => Promise.resolve({ [ctor.name]: ctor })
186
+ load: () => Promise.resolve(ctor)
168
187
  });
169
188
  this.rowOwners.set(routeId, {
170
189
  ctor,
171
190
  target,
172
- key
191
+ key,
192
+ from
173
193
  });
174
194
  }
175
- const from = formatFilePath(relativePath);
176
195
  if (this.loading) this.loadedHandlers.push({
177
196
  name: ctor.name,
178
197
  from
@@ -244,31 +263,45 @@ var Seedcord = class Seedcord extends Pluggable {
244
263
  rest = new REST();
245
264
  /** @see {@link IRateLimiter} */
246
265
  rateLimiter;
247
- /** @see {@link CoordinatedShutdown} */
248
- shutdown;
249
- /** @see {@link CoordinatedStartup} */
250
- startup;
266
+ /** @see {@link Bus} */
267
+ bus;
268
+ subscribers;
251
269
  interactions;
270
+ commandRegistry;
271
+ appId;
272
+ appIdPromise;
273
+ emojiInjector = new EmojiInjector(this, () => this.applicationId());
252
274
  healthCheck;
253
275
  hmrManager;
254
- logger = new Logger("Server");
276
+ logger = new Logger("Server", { channel: "bot" });
255
277
  server;
256
278
  boundPort;
257
279
  requestedPort = DEFAULT_PORT;
258
280
  fetchedUsername;
259
281
  constructor(config) {
260
- const shutdown = new CoordinatedShutdown();
261
- const startup = new CoordinatedStartup();
262
- super(shutdown, startup);
282
+ super(new CoordinatedShutdown(), new CoordinatedStartup());
263
283
  this.config = config;
264
284
  installNodeDefaults(config.logger);
265
285
  setBotColor(config.botColor);
266
- this.shutdown = shutdown;
267
- this.startup = startup;
268
286
  this.hmrManager = new HmrManager();
269
287
  this.hmrManager.init();
270
288
  if (this.config.bot.interactions.path) this.interactions = new InteractionDispatcher(this.config.bot.interactions.path);
289
+ const commandsDir = this.config.bot.commands.path;
290
+ if (commandsDir) {
291
+ const injector = new CommandInjector();
292
+ const registry = new CommandRegistry({
293
+ dir: commandsDir,
294
+ rest: this.rest,
295
+ applicationId: () => this.requireApplicationId(),
296
+ onDeployed: (result) => {
297
+ injector.inject(result, registry.allCommands());
298
+ }
299
+ });
300
+ this.commandRegistry = registry;
301
+ }
271
302
  this.rateLimiter = config.store ?? new MemoryRateLimiter();
303
+ this.bus = new Bus(this);
304
+ this.subscribers = new SubscriberLoader(this.bus, config.subscribers.path);
272
305
  this.healthCheck = config.runtime === "edge" ? void 0 : HealthCheck.fromOption(this.shutdown, config.healthCheck);
273
306
  this.registerStartupTasks();
274
307
  }
@@ -280,17 +313,10 @@ var Seedcord = class Seedcord extends Pluggable {
280
313
  get port() {
281
314
  return this.boundPort;
282
315
  }
283
- pluginCapabilities() {
284
- const token = Envapter.get("DISCORD_BOT_TOKEN");
285
- return {
286
- rest: this.rest,
287
- ...token !== void 0 && { token }
288
- };
289
- }
290
316
  /**
291
317
  * Starts the host and runs the startup tasks.
292
318
  *
293
- * @param port - The port the interaction server binds. {@default `3000`}
319
+ * @param port - The port the interaction server binds. {@default `3000` }
294
320
  */
295
321
  async start(port = DEFAULT_PORT) {
296
322
  this.requestedPort = port;
@@ -309,31 +335,60 @@ var Seedcord = class Seedcord extends Pluggable {
309
335
  }
310
336
  registerStartupTasks() {
311
337
  if (Envapter.isDevelopment || Envapter.isTest) this.registerHmrAwareModules();
338
+ this.startup.addTask(StartupPhase.Configuration, "bus-initialization", async () => {
339
+ this.bus.logger.utils.initialization("Subscribers", "start");
340
+ await this.subscribers.init();
341
+ this.bus.logger.utils.initialization("Subscribers", "end");
342
+ });
312
343
  const { interactions } = this;
313
- if (interactions) this.startup.addTask(StartupPhase.Configuration, "Interactions Initialization", async () => {
344
+ if (interactions) this.startup.addTask(StartupPhase.Configuration, "interactions-initialization", async () => {
314
345
  interactions.logger.utils.initialization("Interactions", "start");
315
346
  await interactions.init();
316
347
  interactions.logger.utils.initialization("Interactions", "end");
317
348
  });
318
- this.startup.addTask(StartupPhase.Ready, "Http Server", () => this.listen());
319
- if (!Envapter.isTest) this.startup.addTask(StartupPhase.Ready, "Identity", () => this.fetchUsername());
349
+ this.startup.addTask(StartupPhase.Configuration, "authenticate", () => {
350
+ this.authenticate();
351
+ return Promise.resolve();
352
+ });
353
+ this.startup.addTask(StartupPhase.Login, "emoji-injection", () => this.emojiInjector.init());
354
+ const { commandRegistry } = this;
355
+ if (commandRegistry) this.startup.addTask(StartupPhase.Login, "command-deploy", async () => {
356
+ await commandRegistry.init();
357
+ this.appId = await this.applicationId();
358
+ await commandRegistry.setCommands();
359
+ interactions?.warnUnhandledRoutes(commandRegistry.routeLeaves());
360
+ interactions?.warnUnhandledContextMenuRoutes(commandRegistry.contextMenuLeaves());
361
+ });
362
+ this.startup.addTask(StartupPhase.Ready, "http-server", () => this.listen());
363
+ if (!Envapter.isTest) this.startup.addTask(StartupPhase.Ready, "identity", () => this.fetchUsername());
320
364
  const { healthCheck } = this;
321
- if (healthCheck) this.startup.addTask(StartupPhase.Ready, "Health Check", async () => {
365
+ if (healthCheck) this.startup.addTask(StartupPhase.Ready, "health-check", async () => {
322
366
  healthCheck.logger.utils.initialization("HealthCheck", "start");
323
367
  await healthCheck.init();
324
368
  healthCheck.logger.utils.initialization("HealthCheck", "end");
325
369
  });
326
370
  }
327
371
  registerHmrAwareModules() {
328
- this.startup.addTask(StartupPhase.Configuration, "HMR Registration", async () => {
372
+ this.startup.addTask(StartupPhase.Configuration, "hmr-registration", async () => {
329
373
  if (this.interactions) this.hmrManager.register(this.interactions);
374
+ if (this.commandRegistry) this.hmrManager.register(this.commandRegistry);
375
+ this.hmrManager.register(this.subscribers);
330
376
  for (const plugin of this.plugins) this.hmrManager.register(plugin);
331
377
  await Promise.resolve();
332
378
  });
333
379
  }
380
+ authenticate() {
381
+ this.rest.setToken(validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN")));
382
+ }
383
+ applicationId() {
384
+ this.appIdPromise ??= fetchApplicationId(this.rest);
385
+ return this.appIdPromise;
386
+ }
387
+ requireApplicationId() {
388
+ if (!this.appId) throw new SeedcordError(SeedcordErrorCode.CoreApplicationUnavailable);
389
+ return this.appId;
390
+ }
334
391
  async listen() {
335
- const token = validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN"));
336
- this.rest.setToken(token);
337
392
  const maps = this.interactions?.maps ?? buildRouteMaps(EMPTY_MANIFEST);
338
393
  const { handle, inFlight } = buildEngine(this, maps);
339
394
  const server = createServer((incoming, outgoing) => {
@@ -378,24 +433,43 @@ var Seedcord = class Seedcord extends Pluggable {
378
433
  }
379
434
  };
380
435
 
436
+ //#endregion
437
+ //#region src/plugin.ts
438
+ /**
439
+ * Base class for an HTTP plugin, binding `this.core` to the HTTP {@link Core}.
440
+ *
441
+ * A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
442
+ * `this.core` is typed to the shared members only.
443
+ *
444
+ * @typeParam Opts - The transport and runtime the plugin declares.
445
+ */
446
+ var Plugin = class extends Plugin$1 {};
447
+
381
448
  //#endregion
382
449
  //#region src/index.ts
383
450
  var src_exports = /* @__PURE__ */ __exportAll({
451
+ ArraySource: () => ArraySource,
384
452
  AutocompleteHandler: () => AutocompleteHandler,
385
453
  ButtonHandler: () => ButtonHandler,
386
454
  ComponentHandler: () => ComponentHandler,
387
455
  ContextMenuHandler: () => ContextMenuHandler,
456
+ CursorSource: () => CursorSource,
457
+ Emojis: () => Emojis,
388
458
  Gated: () => Gated,
389
459
  InteractionHandler: () => InteractionHandler,
390
460
  ModalHandler: () => ModalHandler,
461
+ Paginator: () => Paginator,
462
+ Plugin: () => Plugin,
391
463
  RepliableHandler: () => RepliableHandler,
392
464
  Seedcord: () => Seedcord,
393
465
  SelectMenuHandler: () => SelectMenuHandler,
394
466
  SlashHandler: () => SlashHandler,
467
+ Subscriber: () => Subscriber,
468
+ WebhookLog: () => WebhookLog,
395
469
  createSeedcord: () => createSeedcord,
396
470
  version: () => version
397
471
  });
398
472
 
399
473
  //#endregion
400
- export { AutocompleteHandler, ButtonHandler, ComponentHandler, ContextMenuHandler, Gated, InteractionHandler, ModalHandler, RepliableHandler, Seedcord, SelectMenuHandler, SlashHandler, createSeedcord, version };
474
+ export { ArraySource, AutocompleteHandler, ButtonHandler, ComponentHandler, ContextMenuHandler, CursorSource, Emojis, Gated, InteractionHandler, ModalHandler, Paginator, Plugin, RepliableHandler, Seedcord, SelectMenuHandler, SlashHandler, Subscriber, WebhookLog, createSeedcord, version };
401
475
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["packageVersion"],"sources":["../src/manifest/RouteManifest.ts","../src/node/InteractionDispatcher.ts","../src/node/webBridge.ts","../src/node/Seedcord.ts","../src/index.ts"],"sourcesContent":["import type { SelectMenuKind } from '@seedcord/core';\nimport type { ApplicationCommandType } from 'discord-api-types/v10';\n\n/** The command kinds a manifest row registers, the wire enum's other members have no handler base. */\ntype RegistrableCommandType =\n | ApplicationCommandType.ChatInput\n | ApplicationCommandType.User\n | ApplicationCommandType.Message;\n\n/**\n * A slash or context-menu command row. `type` picks the per-kind map, `name` is the lookup key. For a\n * chat-input command with subcommands, `name` is the full route path (`'config/set'`), the same key\n * `@SlashRoute` registers.\n */\ninterface CommandRoute {\n readonly name: string;\n readonly type: RegistrableCommandType;\n /** Lazy import of the handler module, evaluated on first hit. */\n readonly load: () => Promise<unknown>;\n}\n\n/**\n * A button, select, or modal row. `kind` picks the per-kind map, `prefix` is the stable customId prefix\n * the receiver routes by (the routeKey minus its layout hash).\n */\nexport interface ComponentRoute {\n readonly kind: 'button' | `${SelectMenuKind}Select` | 'modal';\n readonly prefix: string;\n /** Lazy import of the handler module, evaluated on first hit. */\n readonly load: () => Promise<unknown>;\n}\n\n/**\n * An autocomplete row, its own route keyed by the command's route path, separate from the slash row.\n */\ninterface AutocompleteRoute {\n readonly name: string;\n /** Lazy import of the handler module, evaluated on first hit. */\n readonly load: () => Promise<unknown>;\n}\n\n/**\n * A bus subscriber row. The Bus routes off it without importing the subscriber module. The HTTP engine\n * ignores these rows until the Bus ships on this transport.\n */\ninterface SubscriberRoute {\n /** The subscription keys this subscriber registers for. */\n readonly keys: readonly string[];\n /** Mirrors the `@Subscribe` frequency metadata. */\n readonly frequency: 'on' | 'once';\n /** Lazy import of the subscriber module. */\n readonly load: () => Promise<unknown>;\n}\n\n/**\n * The static route table the HTTP engine dispatches through, replacing the filesystem handler scan that\n * cannot run in a bundled isolate. `seedcord build` emits it, the mount path authors it by hand.\n */\nexport interface RouteManifest {\n readonly commands: readonly CommandRoute[];\n readonly components: readonly ComponentRoute[];\n readonly autocomplete: readonly AutocompleteRoute[];\n readonly subscribers: readonly SubscriberRoute[];\n}\n\n/** @internal */\nexport const EMPTY_MANIFEST: RouteManifest = { commands: [], components: [], autocomplete: [], subscribers: [] };\n","import { HmrModuleHandler } from '@seedcord/core/hmr';\nimport { interactionRoutesOf, InteractionMetadataKey, InteractionRoutes } from '@seedcord/core/internal';\nimport { SeedcordErrorCode } 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 { Envapter } from 'envapt';\n\nimport { AutocompleteHandler } from '@handlers/interaction/AutocompleteHandler';\nimport { InteractionHandler } from '@handlers/interaction/InteractionHandler';\nimport { buildRouteMaps } from '@src/dispatch/resolve';\nimport { EMPTY_MANIFEST } from '@src/manifest/RouteManifest';\n\nimport type { HandlerConstructor } from '@handlers/constructors';\nimport type { Initializeable } from '@seedcord/core';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { ResolvedRoute, RouteMap, RouteMaps } from '@src/dispatch/resolve';\n\ninterface RouteTarget {\n readonly map: RouteMap;\n readonly kind: ResolvedRoute['kind'];\n}\n\n/**\n * Discovers interaction handlers from the filesystem into the engine's live route maps.\n *\n * The node host runs it at startup and dev HMR swaps map entries in place. The engine's `resolve`\n * reads `maps` per request. The edge path builds its maps from the generated manifest.\n */\nexport class InteractionDispatcher implements Initializeable, HmrAware {\n public readonly maps: RouteMaps;\n private readonly targets: Record<InteractionRoutes, RouteTarget>;\n\n /** @internal */\n public readonly logger = new Logger('Interactions');\n\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<HandlerConstructor, void, string[]>;\n // routeId -> owner row, the duplicate guard and the hmr unregister index\n private readonly rowOwners = new Map<string, { ctor: HandlerConstructor; target: RouteTarget; key: string }>();\n\n private loading = false;\n private readonly loadedHandlers: { name: string; from: string }[] = [];\n\n constructor(private readonly handlersDir: string) {\n this.maps = buildRouteMaps(EMPTY_MANIFEST);\n this.targets = this.buildTargets();\n\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n this.hmrHandler = new HmrModuleHandler({\n handlersDir,\n isHandler: this.isHandler.bind(this),\n registerHandler: this.registerHandler.bind(this),\n unregisterHandler: this.unregisterHandler.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: this.logger\n });\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n this.loading = true;\n this.loadedHandlers.length = 0;\n try {\n await traverseDirectory(\n this.handlersDir,\n (fullPath, relativePath, imported) => {\n for (const value of Object.values(imported)) {\n if (!this.isHandler(value)) continue;\n this.registerHandler(value, relativePath);\n this.hmrHandler?.trackHandler(fullPath, value);\n }\n },\n this.logger\n );\n } finally {\n this.loading = false;\n }\n\n this.logger.utils.block('Loaded handlers', [\n ...this.logger.utils.entries(this.loadedHandlers),\n ...this.logger.utils.counts({ routes: this.rowOwners.size })\n ]);\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n await this.hmrHandler?.handle(event);\n }\n\n private isHandler(value: unknown): value is HandlerConstructor {\n if (typeof value !== 'function') return false;\n // this package's own family bases. A gateway handler in the same dir stays unregistered\n return (\n (value.prototype instanceof InteractionHandler || value.prototype instanceof AutocompleteHandler) &&\n Reflect.hasMetadata(InteractionMetadataKey, value)\n );\n }\n\n private getArtifacts(ctor: HandlerConstructor): string[] {\n const routeIds: string[] = [];\n for (const [routeId, owner] of this.rowOwners) {\n if (owner.ctor === ctor) routeIds.push(routeId);\n }\n return routeIds;\n }\n\n private targetFor(route: InteractionRoutes): RouteTarget {\n return this.targets[route];\n }\n\n private buildTargets(): Record<InteractionRoutes, RouteTarget> {\n return {\n [InteractionRoutes.Slash]: { map: this.maps.slash, kind: 'slash' },\n [InteractionRoutes.UserContextMenu]: { map: this.maps.userContextMenu, kind: 'userContextMenu' },\n [InteractionRoutes.MessageContextMenu]: { map: this.maps.messageContextMenu, kind: 'messageContextMenu' },\n [InteractionRoutes.Autocomplete]: { map: this.maps.autocomplete, kind: 'autocomplete' },\n [InteractionRoutes.Button]: { map: this.maps.components.button, kind: 'button' },\n [InteractionRoutes.StringMenu]: { map: this.maps.components.stringSelect, kind: 'stringMenu' },\n [InteractionRoutes.UserMenu]: { map: this.maps.components.userSelect, kind: 'userMenu' },\n [InteractionRoutes.RoleMenu]: { map: this.maps.components.roleSelect, kind: 'roleMenu' },\n [InteractionRoutes.ChannelMenu]: { map: this.maps.components.channelSelect, kind: 'channelMenu' },\n [InteractionRoutes.MentionableMenu]: {\n map: this.maps.components.mentionableSelect,\n kind: 'mentionableMenu'\n },\n [InteractionRoutes.Modal]: { map: this.maps.components.modal, kind: 'modal' }\n };\n }\n\n private registerHandler(ctor: HandlerConstructor, relativePath: string): void {\n // a partial registration would orphan routes and break hmr rollback\n const writes: [RouteTarget, string][] = [];\n\n for (const [route, keys] of interactionRoutesOf(ctor)) {\n const target = this.targetFor(route);\n for (const key of keys) {\n const existing = this.rowOwners.get(`${target.kind}:${key}`);\n // a different class on the same route would silently shadow (last write wins), so this throws\n if (existing && existing.ctor !== ctor) {\n throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [\n key,\n existing.ctor.name,\n ctor.name\n ]);\n }\n writes.push([target, key]);\n }\n }\n\n if (writes.length === 0) return;\n for (const [target, key] of writes) {\n const routeId = `${target.kind}:${key}`;\n // a one-export module, the dispatch first-handler scan resolves exactly this row's class\n target.map.set(key, { kind: target.kind, routeId, load: () => Promise.resolve({ [ctor.name]: ctor }) });\n this.rowOwners.set(routeId, { ctor, target, key });\n }\n\n const from = formatFilePath(relativePath);\n if (this.loading) this.loadedHandlers.push({ name: ctor.name, from });\n else this.logger.utils.registration(ctor.name, from);\n }\n\n private unregisterHandler(ctor: HandlerConstructor, artifacts?: string[]): void {\n const routeIds = artifacts ?? this.getArtifacts(ctor);\n for (const routeId of routeIds) {\n const owner = this.rowOwners.get(routeId);\n if (owner?.ctor !== ctor) continue;\n this.rowOwners.delete(routeId);\n owner.target.map.delete(owner.key);\n }\n }\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';\n\nfunction toHeaders(raw: IncomingHttpHeaders): Headers {\n const headers = new Headers();\n for (const [name, value] of Object.entries(raw)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) headers.append(name, item);\n } else {\n headers.set(name, value);\n }\n }\n return headers;\n}\n\n// buffers the raw body before any parse so ed25519 verifies the exact signed bytes\nexport async function toWebRequest(request: IncomingMessage): Promise<Request> {\n const chunks: Buffer[] = [];\n // justified: the async iterator is typed any and IncomingMessage yields Buffer chunks\n for await (const chunk of request) chunks.push(chunk as Buffer);\n\n const method = request.method ?? 'GET';\n const body = method === 'GET' || method === 'HEAD' ? undefined : new Uint8Array(Buffer.concat(chunks));\n\n return new Request(`http://${request.headers.host ?? 'localhost'}${request.url ?? '/'}`, {\n method,\n headers: toHeaders(request.headers),\n ...(body !== undefined && { body })\n });\n}\n\nexport async function writeWebResponse(response: Response, out: ServerResponse): Promise<void> {\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers);\n // iterating Headers comma-joins same-name values, which corrupts Set-Cookie (a per-cookie header)\n const cookies = response.headers.getSetCookie();\n if (cookies.length > 0) headers['set-cookie'] = cookies;\n\n out.writeHead(response.status, headers);\n out.end(response.body === null ? undefined : Buffer.from(await response.arrayBuffer()));\n}\n","import { once } from 'node:events';\nimport { createServer } from 'node:http';\n\nimport { REST } from '@discordjs/rest';\nimport { HmrManager, setBotColor } from '@seedcord/core/internal';\nimport {\n CoordinatedShutdown,\n CoordinatedStartup,\n HealthCheck,\n Pluggable,\n ShutdownPhase,\n StartupPhase\n} from '@seedcord/core/node/internal';\nimport { validateDiscordToken } from '@seedcord/errors/internal';\nimport { Logger, LoggerChannelRegistry, paint } from '@seedcord/logger';\nimport { installNodeDefaults } from '@seedcord/logger/node';\nimport { MemoryRateLimiter } from '@seedcord/rate-limiter';\nimport { SeedcordBrand } from '@seedcord/types/internal';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { buildRouteMaps } from '@src/dispatch/resolve';\nimport { buildEngine } from '@src/engine';\nimport { EMPTY_MANIFEST } from '@src/manifest/RouteManifest';\n\nimport { InteractionDispatcher } from './InteractionDispatcher';\nimport { toWebRequest, writeWebResponse } from './webBridge';\nimport { version as packageVersion } from '../version';\n\nimport type { HttpConfig } from '@interfaces/Config';\nimport type { Core } from '@interfaces/Core';\nimport type { PluginCapabilities } from '@seedcord/core/node/internal';\nimport type { IRateLimiter } from '@seedcord/types';\nimport type { Server } from 'node:http';\nimport type { AddressInfo } from 'node:net';\n\nconst DEFAULT_PORT = 3000;\nconst SERVER_SHUTDOWN_TIMEOUT_MS = 5000;\nconst DRAIN_TIMEOUT_MS = 10_000;\n\n/**\n * The HTTP-interactions bot host, a long-running node server around the engine.\n *\n * Discovers handlers from `config.bot.interactions.path`, verifies and dispatches interactions on\n * `start(port)`, and runs coordinated shutdown with an in-flight drain. The edge deploy path calls\n * `createSeedcord` from a generated entry.\n */\nexport class Seedcord extends Pluggable implements Core {\n // the CLI reads these to detect and augment the instance\n /** @internal */\n public readonly [SeedcordBrand] = true;\n /** @internal */\n public readonly augmentTarget = '@seedcord/http';\n /** @internal */\n public readonly version: string = packageVersion;\n\n /** Workerd-compatible Discord REST client. `start()` sets the token. */\n public readonly rest = new REST();\n\n /** @see {@link IRateLimiter} */\n public readonly rateLimiter: IRateLimiter;\n\n /** @see {@link CoordinatedShutdown} */\n public override readonly shutdown: CoordinatedShutdown;\n\n /** @see {@link CoordinatedStartup} */\n public override readonly startup: CoordinatedStartup;\n\n private readonly interactions?: InteractionDispatcher;\n private readonly healthCheck?: HealthCheck | undefined;\n private readonly hmrManager: HmrManager;\n private readonly logger = new Logger('Server');\n\n private server?: Server;\n private boundPort?: number;\n private requestedPort = DEFAULT_PORT;\n private fetchedUsername?: string | undefined;\n\n constructor(public readonly config: HttpConfig) {\n const shutdown = new CoordinatedShutdown();\n const startup = new CoordinatedStartup();\n\n super(shutdown, startup);\n\n installNodeDefaults(config.logger);\n setBotColor(config.botColor);\n\n this.shutdown = shutdown;\n this.startup = startup;\n\n this.hmrManager = new HmrManager();\n this.hmrManager.init();\n\n if (this.config.bot.interactions.path) {\n this.interactions = new InteractionDispatcher(this.config.bot.interactions.path);\n }\n\n this.rateLimiter = config.store ?? new MemoryRateLimiter();\n // the edge arm types healthCheck never, and a dev run of an edge config needs no health server\n this.healthCheck =\n config.runtime === 'edge' ? undefined : HealthCheck.fromOption(this.shutdown, config.healthCheck);\n\n this.registerStartupTasks();\n }\n\n /** The bot's discord username, populated by the ready fetch. */\n public get username(): string | undefined {\n return this.fetchedUsername;\n }\n\n /** The bound server port, populated once `start()` is listening. */\n public get port(): number | undefined {\n return this.boundPort;\n }\n\n protected override pluginCapabilities(): PluginCapabilities {\n const token = Envapter.get('DISCORD_BOT_TOKEN');\n return { rest: this.rest, ...(token !== undefined && { token }) };\n }\n\n /**\n * Starts the host and runs the startup tasks.\n *\n * @param port - The port the interaction server binds. {@default `3000`}\n */\n public async start(port = DEFAULT_PORT): Promise<this> {\n this.requestedPort = port;\n try {\n await super.init();\n } catch (caught) {\n // shutdown releases any resource opened before the failure, then rethrow\n await this.shutdown.run(1, false);\n Seedcord.reset();\n throw caught;\n }\n return this;\n }\n\n protected static override reset(): void {\n super.reset();\n LoggerChannelRegistry.instance.reset();\n }\n\n private registerStartupTasks(): void {\n if (Envapter.isDevelopment || Envapter.isTest) this.registerHmrAwareModules();\n\n const { interactions } = this;\n if (interactions) {\n this.startup.addTask(StartupPhase.Configuration, 'Interactions Initialization', async () => {\n interactions.logger.utils.initialization('Interactions', 'start');\n await interactions.init();\n interactions.logger.utils.initialization('Interactions', 'end');\n });\n }\n\n this.startup.addTask(StartupPhase.Ready, 'Http Server', () => this.listen());\n\n if (!Envapter.isTest) {\n this.startup.addTask(StartupPhase.Ready, 'Identity', () => this.fetchUsername());\n }\n\n const { healthCheck } = this;\n if (healthCheck) {\n this.startup.addTask(StartupPhase.Ready, 'Health Check', async () => {\n healthCheck.logger.utils.initialization('HealthCheck', 'start');\n await healthCheck.init();\n healthCheck.logger.utils.initialization('HealthCheck', 'end');\n });\n }\n }\n\n private registerHmrAwareModules(): void {\n this.startup.addTask(StartupPhase.Configuration, 'HMR Registration', async () => {\n if (this.interactions) this.hmrManager.register(this.interactions);\n for (const plugin of this.plugins) {\n this.hmrManager.register(plugin);\n }\n await Promise.resolve();\n });\n }\n\n private async listen(): Promise<void> {\n const token = validateDiscordToken(Envapter.get('DISCORD_BOT_TOKEN'));\n this.rest.setToken(token);\n\n const maps = this.interactions?.maps ?? buildRouteMaps(EMPTY_MANIFEST);\n const { handle, inFlight } = buildEngine(this, maps);\n\n const server = createServer((incoming, outgoing) => {\n void (async () => {\n const response = await handle(await toWebRequest(incoming));\n await writeWebResponse(response, outgoing);\n })().catch((error: unknown) => {\n // a swallowed throw would hang the client request with no cause\n outgoing.destroy(Error.isError(error) ? error : new Error(String(error)));\n });\n });\n this.server = server;\n\n server.listen(this.requestedPort);\n await once(server, 'listening');\n // justified: address() is AddressInfo once a TCP server is listening\n this.boundPort = (server.address() as AddressInfo).port;\n this.logger.info(`Interactions server listening on port ${paint.sky.bold(String(this.boundPort))}`);\n\n this.shutdown.addTask(\n ShutdownPhase.Unbind,\n 'stop-http-server',\n () => this.stopServer(),\n SERVER_SHUTDOWN_TIMEOUT_MS\n );\n // Unbind already ran, so every accepted request is in the in-flight set here\n this.shutdown.addTask(\n ShutdownPhase.Drain,\n 'drain-inflight',\n async () => {\n await Promise.allSettled(inFlight);\n },\n DRAIN_TIMEOUT_MS\n );\n }\n\n private async fetchUsername(): Promise<void> {\n try {\n // justified: the @me payload carries username per the discord api contract\n const me = (await this.rest.get(Routes.user('@me'))) as { username?: string };\n this.fetchedUsername = me.username;\n } catch (caught) {\n // a bad token errors on the first real send. The identity fetch stays non-fatal\n this.logger.warn('could not fetch the bot identity', caught);\n }\n }\n\n private stopServer(): Promise<void> {\n const server = this.server;\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolveClose, rejectClose) => {\n server.close((err) => {\n if (err) {\n rejectClose(err);\n return;\n }\n this.logger.info(paint.coral.bold('Interactions server stopped'));\n resolveClose();\n });\n // idle keep-alive sockets would stall close() past the task timeout. Active responses\n // still flush, and the task timeout bounds a hung one\n server.closeIdleConnections();\n });\n }\n}\n","export * from './edge.index';\nexport { Seedcord } from '@src/node/Seedcord';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["packageVersion","CorePlugin"],"sources":["../src/manifest/RouteManifest.ts","../src/node/InteractionDispatcher.ts","../src/node/webBridge.ts","../src/node/Seedcord.ts","../src/plugin.ts","../src/index.ts"],"sourcesContent":["import type { SelectMenuKind, SubscriptionKey } from '@seedcord/core';\nimport type { EventFrequency } from '@seedcord/types';\nimport type { ApplicationCommandType } from 'discord-api-types/v10';\n\n/** The command kinds a manifest row registers, the wire enum's other members have no handler base. */\ntype RegistrableCommandType =\n | ApplicationCommandType.ChatInput\n | ApplicationCommandType.User\n | ApplicationCommandType.Message;\n\n/** One file may export several handlers, so the row states which export it registers. */\nexport interface RouteModule {\n /** The exported binding inside the loaded module. */\n readonly exportName: string;\n /** The source file the row came from, reported when the export is missing. */\n readonly from: string;\n /** Lazy import of the module, evaluated on first hit. */\n readonly load: () => Promise<Record<string, unknown>>;\n}\n\n/**\n * A slash or context-menu command row. `type` picks the per-kind map, `name` is the lookup key. For a\n * chat-input command with subcommands, `name` is the full route path (`'config/set'`), the same key\n * `@SlashRoute` registers.\n */\ninterface CommandRoute extends RouteModule {\n readonly name: string;\n readonly type: RegistrableCommandType;\n}\n\n/**\n * A button, select, or modal row. `kind` picks the per-kind map, `prefix` is the stable customId prefix\n * the receiver routes by (the routeKey minus its layout hash).\n */\nexport interface ComponentRoute extends RouteModule {\n readonly kind: 'button' | `${SelectMenuKind}Select` | 'modal';\n readonly prefix: string;\n}\n\n/**\n * An autocomplete row, its own route keyed by the command's route path, separate from the slash row.\n */\ninterface AutocompleteRoute extends RouteModule {\n readonly name: string;\n}\n\n/**\n * A bus subscriber row. The Bus registers it without importing the subscriber module, and resolves the\n * class on the first publish of one of its keys.\n */\ninterface SubscriberRoute extends RouteModule {\n /** The subscription keys this subscriber registers for. */\n readonly keys: readonly SubscriptionKey[];\n /** Mirrors the `@Subscribe` frequency metadata. */\n readonly frequency: EventFrequency;\n}\n\n/** A middleware row. Middleware runs on every dispatch, so it carries no lookup key. */\ntype MiddlewareRoute = RouteModule;\n\n/**\n * The static route table the HTTP engine dispatches through, replacing the filesystem handler scan that\n * cannot run in a bundled isolate. `seedcord build` emits it, the mount path authors it by hand.\n */\nexport interface RouteManifest {\n readonly commandRoutes: readonly CommandRoute[];\n readonly componentRoutes: readonly ComponentRoute[];\n readonly autocompleteRoutes: readonly AutocompleteRoute[];\n readonly subscriberRoutes: readonly SubscriberRoute[];\n readonly middlewareRoutes: readonly MiddlewareRoute[];\n}\n\n/** @internal */\nexport const EMPTY_MANIFEST: RouteManifest = {\n commandRoutes: [],\n componentRoutes: [],\n autocompleteRoutes: [],\n subscriberRoutes: [],\n middlewareRoutes: []\n};\n","import { HmrModuleHandler } from '@seedcord/core/hmr';\nimport { interactionRoutesOf, InteractionMetadataKey, InteractionRoutes } from '@seedcord/core/internal';\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { Logger, paint } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport chalk from 'chalk';\nimport { Envapter } from 'envapt';\n\nimport { AutocompleteHandler } from '@handlers/interaction/AutocompleteHandler';\nimport { InteractionHandler } from '@handlers/interaction/InteractionHandler';\nimport { buildRouteMaps } from '@src/dispatch/resolve';\nimport { EMPTY_MANIFEST } from '@src/manifest/RouteManifest';\n\nimport type { HandlerConstructor } from '@handlers/constructors';\nimport type { Initializeable } from '@seedcord/core';\nimport type { ContextMenuLeaves } from '@seedcord/core/internal';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\nimport type { ResolvedRoute, RouteMap, RouteMaps } from '@src/dispatch/resolve';\n\ninterface RouteTarget {\n readonly map: RouteMap;\n readonly kind: ResolvedRoute['kind'];\n}\n\n/**\n * Discovers interaction handlers from the filesystem into the engine's live route maps.\n *\n * The node host runs it at startup and dev HMR swaps map entries in place. The engine's `resolve`\n * reads `maps` per request. The edge path builds its maps from the generated manifest.\n */\nexport class InteractionDispatcher implements Initializeable, HmrAware {\n public readonly maps: RouteMaps;\n private readonly targets: Record<InteractionRoutes, RouteTarget>;\n\n /** @internal */\n public readonly logger = new Logger('Interactions', { channel: 'interactions' });\n\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<HandlerConstructor, void, string[]>;\n // routeId -> owner row, the duplicate guard and the hmr unregister index\n private readonly rowOwners = new Map<\n string,\n { ctor: HandlerConstructor; target: RouteTarget; key: string; from: string }\n >();\n\n private loading = false;\n private readonly loadedHandlers: { name: string; from: string }[] = [];\n\n constructor(private readonly handlersDir: string) {\n this.maps = buildRouteMaps(EMPTY_MANIFEST);\n this.targets = this.buildTargets();\n\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n this.hmrHandler = new HmrModuleHandler({\n handlersDir,\n isHandler: this.isHandler.bind(this),\n registerHandler: this.registerHandler.bind(this),\n unregisterHandler: this.unregisterHandler.bind(this),\n getArtifacts: this.getArtifacts.bind(this),\n logger: this.logger\n });\n }\n\n /** @internal */\n public async init(): Promise<void> {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n this.loading = true;\n this.loadedHandlers.length = 0;\n try {\n await traverseDirectory(this.handlersDir, (fullPath, relativePath, imported) => {\n for (const value of Object.values(imported)) {\n if (!this.isHandler(value)) continue;\n this.registerHandler(value, relativePath);\n this.hmrHandler?.trackHandler(fullPath, value);\n }\n });\n } finally {\n this.loading = false;\n }\n\n this.logger.utils.block('Loaded handlers', [\n ...this.logger.utils.entries(this.loadedHandlers),\n ...this.logger.utils.counts({ routes: this.rowOwners.size })\n ]);\n }\n\n /** @internal */\n public async onHmr(event: HmrUpdateEvent): Promise<void> {\n await this.hmrHandler?.handle(event);\n }\n\n public warnUnhandledRoutes(commandLeaves: Iterable<string>): void {\n this.warnMissing(commandLeaves, this.maps.slash, 'Slash route', '@SlashRoute');\n }\n\n public warnUnhandledContextMenuRoutes(leaves: ContextMenuLeaves): void {\n this.warnMissing(leaves.user, this.maps.userContextMenu, 'User context menu', '@ContextMenuRoute');\n this.warnMissing(leaves.message, this.maps.messageContextMenu, 'Message context menu', '@ContextMenuRoute');\n }\n\n private warnMissing(names: Iterable<string>, map: RouteMap, label: string, decorator: string): void {\n for (const name of names) {\n if (map.has(name)) continue;\n this.logger.warn(`${label} ${paint.sky.bold(name)} has no registered ${chalk.bold(decorator)} handler.`);\n }\n }\n\n private isHandler(value: unknown): value is HandlerConstructor {\n if (typeof value !== 'function') return false;\n // this package's own family bases. A gateway handler in the same dir stays unregistered\n return (\n (value.prototype instanceof InteractionHandler || value.prototype instanceof AutocompleteHandler) &&\n Reflect.hasMetadata(InteractionMetadataKey, value)\n );\n }\n\n private getArtifacts(ctor: HandlerConstructor): string[] {\n const routeIds: string[] = [];\n for (const [routeId, owner] of this.rowOwners) {\n if (owner.ctor === ctor) routeIds.push(routeId);\n }\n return routeIds;\n }\n\n private targetFor(route: InteractionRoutes): RouteTarget {\n return this.targets[route];\n }\n\n private buildTargets(): Record<InteractionRoutes, RouteTarget> {\n return {\n [InteractionRoutes.Slash]: { map: this.maps.slash, kind: 'slash' },\n [InteractionRoutes.UserContextMenu]: { map: this.maps.userContextMenu, kind: 'userContextMenu' },\n [InteractionRoutes.MessageContextMenu]: { map: this.maps.messageContextMenu, kind: 'messageContextMenu' },\n [InteractionRoutes.Autocomplete]: { map: this.maps.autocomplete, kind: 'autocomplete' },\n [InteractionRoutes.Button]: { map: this.maps.components.button, kind: 'button' },\n [InteractionRoutes.StringMenu]: { map: this.maps.components.stringSelect, kind: 'stringMenu' },\n [InteractionRoutes.UserMenu]: { map: this.maps.components.userSelect, kind: 'userMenu' },\n [InteractionRoutes.RoleMenu]: { map: this.maps.components.roleSelect, kind: 'roleMenu' },\n [InteractionRoutes.ChannelMenu]: { map: this.maps.components.channelSelect, kind: 'channelMenu' },\n [InteractionRoutes.MentionableMenu]: {\n map: this.maps.components.mentionableSelect,\n kind: 'mentionableMenu'\n },\n [InteractionRoutes.Modal]: { map: this.maps.components.modal, kind: 'modal' }\n };\n }\n\n private registerHandler(ctor: HandlerConstructor, relativePath: string): void {\n const from = formatFilePath(relativePath);\n // a partial registration would orphan routes and break hmr rollback\n const writes: [RouteTarget, string][] = [];\n\n for (const [route, keys] of interactionRoutesOf(ctor)) {\n const target = this.targetFor(route);\n for (const key of keys) {\n const routeId = `${target.kind}:${key}`;\n const existing = this.rowOwners.get(routeId);\n // a different class on the same route would silently shadow (last write wins), so this throws\n if (existing && existing.ctor !== ctor) {\n throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [\n routeId,\n `${existing.ctor.name} (${existing.from})`,\n `${ctor.name} (${from})`\n ]);\n }\n writes.push([target, key]);\n }\n }\n\n if (writes.length === 0) return;\n for (const [target, key] of writes) {\n const routeId = `${target.kind}:${key}`;\n target.map.set(key, { kind: target.kind, routeId, load: () => Promise.resolve(ctor) });\n this.rowOwners.set(routeId, { ctor, target, key, from });\n }\n\n if (this.loading) this.loadedHandlers.push({ name: ctor.name, from });\n else this.logger.utils.registration(ctor.name, from);\n }\n\n private unregisterHandler(ctor: HandlerConstructor, artifacts?: string[]): void {\n const routeIds = artifacts ?? this.getArtifacts(ctor);\n for (const routeId of routeIds) {\n const owner = this.rowOwners.get(routeId);\n if (owner?.ctor !== ctor) continue;\n this.rowOwners.delete(routeId);\n owner.target.map.delete(owner.key);\n }\n }\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';\n\nfunction toHeaders(raw: IncomingHttpHeaders): Headers {\n const headers = new Headers();\n for (const [name, value] of Object.entries(raw)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) headers.append(name, item);\n } else {\n headers.set(name, value);\n }\n }\n return headers;\n}\n\n// buffers the raw body before any parse so ed25519 verifies the exact signed bytes\nexport async function toWebRequest(request: IncomingMessage): Promise<Request> {\n const chunks: Buffer[] = [];\n // justified: the async iterator is typed any and IncomingMessage yields Buffer chunks\n for await (const chunk of request) chunks.push(chunk as Buffer);\n\n const method = request.method ?? 'GET';\n const body = method === 'GET' || method === 'HEAD' ? undefined : new Uint8Array(Buffer.concat(chunks));\n\n return new Request(`http://${request.headers.host ?? 'localhost'}${request.url ?? '/'}`, {\n method,\n headers: toHeaders(request.headers),\n ...(body !== undefined && { body })\n });\n}\n\nexport async function writeWebResponse(response: Response, out: ServerResponse): Promise<void> {\n const headers: Record<string, string | string[]> = Object.fromEntries(response.headers);\n // iterating Headers comma-joins same-name values, which corrupts Set-Cookie (a per-cookie header)\n const cookies = response.headers.getSetCookie();\n if (cookies.length > 0) headers['set-cookie'] = cookies;\n\n out.writeHead(response.status, headers);\n out.end(response.body === null ? undefined : Buffer.from(await response.arrayBuffer()));\n}\n","import { once } from 'node:events';\nimport { createServer } from 'node:http';\n\nimport { REST } from '@discordjs/rest';\nimport { Bus } from '@seedcord/core';\nimport { CommandInjector, HmrManager, setBotColor } from '@seedcord/core/internal';\nimport {\n CommandRegistry,\n CoordinatedShutdown,\n CoordinatedStartup,\n HealthCheck,\n Pluggable,\n ShutdownPhase,\n StartupPhase,\n SubscriberLoader\n} from '@seedcord/core/node/internal';\nimport { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError, validateDiscordToken } from '@seedcord/errors/internal';\nimport { Logger, LoggerChannelRegistry, paint } from '@seedcord/logger';\nimport { installNodeDefaults } from '@seedcord/logger/node';\nimport { MemoryRateLimiter } from '@seedcord/rate-limiter';\nimport { SeedcordBrand } from '@seedcord/types/internal';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { fetchApplicationId } from '@src/applicationId';\nimport { buildRouteMaps } from '@src/dispatch/resolve';\nimport { EmojiInjector } from '@src/emojis/EmojiInjector';\nimport { buildEngine } from '@src/engine';\nimport { EMPTY_MANIFEST } from '@src/manifest/RouteManifest';\n\nimport { InteractionDispatcher } from './InteractionDispatcher';\nimport { toWebRequest, writeWebResponse } from './webBridge';\nimport { version as packageVersion } from '../version';\n\nimport type { HttpConfig } from '@interfaces/Config';\nimport type { Core } from '@interfaces/Core';\nimport type { IRateLimiter } from '@seedcord/types';\nimport type { SeedcordInstance } from '@seedcord/types/internal';\nimport type { Server } from 'node:http';\nimport type { AddressInfo } from 'node:net';\n\nconst DEFAULT_PORT = 3000;\nconst SERVER_SHUTDOWN_TIMEOUT_MS = 5000;\nconst DRAIN_TIMEOUT_MS = 10_000;\n\ntype RuntimeOfConfig<Cfg extends HttpConfig> = Cfg extends { runtime: 'edge' } ? 'edge' : 'server';\n\n/**\n * The HTTP-interactions bot host, a long-running node server around the engine.\n *\n * Discovers handlers from `config.bot.interactions.path`, verifies and dispatches interactions on\n * `start(port)`, and runs coordinated shutdown with an in-flight drain. The edge deploy path calls\n * `createSeedcord` from a generated entry.\n */\nexport class Seedcord<Cfg extends HttpConfig = HttpConfig>\n extends Pluggable<'http', RuntimeOfConfig<Cfg>>\n implements Core, SeedcordInstance\n{\n // the CLI reads these to detect and augment the instance\n /** @internal */\n public readonly [SeedcordBrand] = true;\n /** @internal */\n public readonly augmentTarget = '@seedcord/http';\n /** @internal */\n public readonly version: string = packageVersion;\n\n /** Workerd-compatible Discord REST client. `start()` sets the token. */\n public readonly rest = new REST();\n\n /** @see {@link IRateLimiter} */\n public readonly rateLimiter: IRateLimiter;\n\n /** @see {@link Bus} */\n public readonly bus: Bus;\n\n private readonly subscribers: SubscriberLoader;\n\n private readonly interactions?: InteractionDispatcher;\n private readonly commandRegistry?: CommandRegistry;\n private appId?: string;\n private appIdPromise?: Promise<string>;\n private readonly emojiInjector = new EmojiInjector(this, () => this.applicationId());\n private readonly healthCheck?: HealthCheck | undefined;\n private readonly hmrManager: HmrManager;\n private readonly logger = new Logger('Server', { channel: 'bot' });\n\n private server?: Server;\n private boundPort?: number;\n private requestedPort = DEFAULT_PORT;\n private fetchedUsername?: string | undefined;\n\n constructor(public readonly config: Cfg) {\n super(new CoordinatedShutdown(), new CoordinatedStartup());\n\n installNodeDefaults(config.logger);\n setBotColor(config.botColor);\n\n this.hmrManager = new HmrManager();\n this.hmrManager.init();\n\n if (this.config.bot.interactions.path) {\n this.interactions = new InteractionDispatcher(this.config.bot.interactions.path);\n }\n\n const commandsDir = this.config.bot.commands.path;\n if (commandsDir) {\n const injector = new CommandInjector();\n // onDeployed only fires at deploy time, so registry is assigned by then\n const registry: CommandRegistry = new CommandRegistry({\n dir: commandsDir,\n rest: this.rest,\n applicationId: () => this.requireApplicationId(),\n onDeployed: (result) => {\n injector.inject(result, registry.allCommands());\n }\n });\n this.commandRegistry = registry;\n }\n\n this.rateLimiter = config.store ?? new MemoryRateLimiter();\n this.bus = new Bus(this);\n this.subscribers = new SubscriberLoader(this.bus, config.subscribers.path);\n // the edge arm types healthCheck never, and a dev run of an edge config needs no health server\n this.healthCheck =\n config.runtime === 'edge' ? undefined : HealthCheck.fromOption(this.shutdown, config.healthCheck);\n\n this.registerStartupTasks();\n }\n\n /** The bot's discord username, populated by the ready fetch. */\n public get username(): string | undefined {\n return this.fetchedUsername;\n }\n\n /** The bound server port, populated once `start()` is listening. */\n public get port(): number | undefined {\n return this.boundPort;\n }\n\n /**\n * Starts the host and runs the startup tasks.\n *\n * @param port - The port the interaction server binds. {@default `3000` }\n */\n public async start(port = DEFAULT_PORT): Promise<this> {\n this.requestedPort = port;\n try {\n await super.init();\n } catch (caught) {\n // shutdown releases any resource opened before the failure, then rethrow\n await this.shutdown.run(1, false);\n Seedcord.reset();\n throw caught;\n }\n return this;\n }\n\n protected static override reset(): void {\n super.reset();\n LoggerChannelRegistry.instance.reset();\n }\n\n private registerStartupTasks(): void {\n if (Envapter.isDevelopment || Envapter.isTest) this.registerHmrAwareModules();\n\n this.startup.addTask(StartupPhase.Configuration, 'bus-initialization', async () => {\n this.bus.logger.utils.initialization('Subscribers', 'start');\n await this.subscribers.init();\n this.bus.logger.utils.initialization('Subscribers', 'end');\n });\n\n const { interactions } = this;\n if (interactions) {\n this.startup.addTask(StartupPhase.Configuration, 'interactions-initialization', async () => {\n interactions.logger.utils.initialization('Interactions', 'start');\n await interactions.init();\n interactions.logger.utils.initialization('Interactions', 'end');\n });\n }\n\n this.startup.addTask(StartupPhase.Configuration, 'authenticate', () => {\n this.authenticate();\n return Promise.resolve();\n });\n\n // needs the token from Configuration, and must finish before Ready opens the server to interactions\n this.startup.addTask(StartupPhase.Login, 'emoji-injection', () => this.emojiInjector.init());\n\n const { commandRegistry } = this;\n if (commandRegistry) {\n // one task, because tasks within a phase run concurrently and the deploy reads the id\n this.startup.addTask(StartupPhase.Login, 'command-deploy', async () => {\n await commandRegistry.init();\n this.appId = await this.applicationId();\n await commandRegistry.setCommands();\n interactions?.warnUnhandledRoutes(commandRegistry.routeLeaves());\n interactions?.warnUnhandledContextMenuRoutes(commandRegistry.contextMenuLeaves());\n });\n }\n\n this.startup.addTask(StartupPhase.Ready, 'http-server', () => this.listen());\n\n if (!Envapter.isTest) {\n this.startup.addTask(StartupPhase.Ready, 'identity', () => this.fetchUsername());\n }\n\n const { healthCheck } = this;\n if (healthCheck) {\n this.startup.addTask(StartupPhase.Ready, 'health-check', async () => {\n healthCheck.logger.utils.initialization('HealthCheck', 'start');\n await healthCheck.init();\n healthCheck.logger.utils.initialization('HealthCheck', 'end');\n });\n }\n }\n\n private registerHmrAwareModules(): void {\n this.startup.addTask(StartupPhase.Configuration, 'hmr-registration', async () => {\n if (this.interactions) this.hmrManager.register(this.interactions);\n if (this.commandRegistry) this.hmrManager.register(this.commandRegistry);\n this.hmrManager.register(this.subscribers);\n for (const plugin of this.plugins) {\n this.hmrManager.register(plugin);\n }\n await Promise.resolve();\n });\n }\n\n private authenticate(): void {\n this.rest.setToken(validateDiscordToken(Envapter.get('DISCORD_BOT_TOKEN')));\n }\n\n // the promise is stored, so two concurrent Login tasks share one fetch\n private applicationId(): Promise<string> {\n this.appIdPromise ??= fetchApplicationId(this.rest);\n return this.appIdPromise;\n }\n\n // the registry deploys on a hot reload too, after the startup task resolved this\n private requireApplicationId(): string {\n if (!this.appId) throw new SeedcordError(SeedcordErrorCode.CoreApplicationUnavailable);\n return this.appId;\n }\n\n private async listen(): Promise<void> {\n const maps = this.interactions?.maps ?? buildRouteMaps(EMPTY_MANIFEST);\n const { handle, inFlight } = buildEngine(this, maps);\n\n const server = createServer((incoming, outgoing) => {\n void (async () => {\n const response = await handle(await toWebRequest(incoming));\n await writeWebResponse(response, outgoing);\n })().catch((error: unknown) => {\n // a swallowed throw would hang the client request with no cause\n outgoing.destroy(Error.isError(error) ? error : new Error(String(error)));\n });\n });\n this.server = server;\n\n server.listen(this.requestedPort);\n await once(server, 'listening');\n // justified: address() is AddressInfo once a TCP server is listening\n this.boundPort = (server.address() as AddressInfo).port;\n this.logger.info(`Interactions server listening on port ${paint.sky.bold(String(this.boundPort))}`);\n\n this.shutdown.addTask(\n ShutdownPhase.Unbind,\n 'stop-http-server',\n () => this.stopServer(),\n SERVER_SHUTDOWN_TIMEOUT_MS\n );\n // Unbind already ran, so every accepted request is in the in-flight set here\n this.shutdown.addTask(\n ShutdownPhase.Drain,\n 'drain-inflight',\n async () => {\n await Promise.allSettled(inFlight);\n },\n DRAIN_TIMEOUT_MS\n );\n }\n\n private async fetchUsername(): Promise<void> {\n try {\n // justified: the @me payload carries username per the discord api contract\n const me = (await this.rest.get(Routes.user('@me'))) as { username?: string };\n this.fetchedUsername = me.username;\n } catch (caught) {\n // a bad token errors on the first real send. The identity fetch stays non-fatal\n this.logger.warn('could not fetch the bot identity', caught);\n }\n }\n\n private stopServer(): Promise<void> {\n const server = this.server;\n if (!server?.listening) return Promise.resolve();\n\n return new Promise((resolveClose, rejectClose) => {\n server.close((err) => {\n if (err) {\n rejectClose(err);\n return;\n }\n this.logger.info(paint.coral.bold('Interactions server stopped'));\n resolveClose();\n });\n // idle keep-alive sockets would stall close() past the task timeout. Active responses\n // still flush, and the task timeout bounds a hung one\n server.closeIdleConnections();\n });\n }\n}\n","import { Plugin as CorePlugin } from '@seedcord/core/plugin';\n\nimport type { Core } from '@interfaces/Core';\nimport type { PluginOptions } from '@seedcord/core/plugin';\nimport type { TypedOmit } from '@seedcord/types';\n\n/** The options an HTTP plugin declares, narrowed to the transports this base serves. */\nexport type HttpPluginOptions = TypedOmit<PluginOptions, 'transport'> & { transport?: 'http' | 'any' };\n\n/**\n * Base class for an HTTP plugin, binding `this.core` to the HTTP {@link Core}.\n *\n * A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose\n * `this.core` is typed to the shared members only.\n *\n * @typeParam Opts - The transport and runtime the plugin declares.\n */\nexport abstract class Plugin<Opts extends HttpPluginOptions = {}> extends CorePlugin<Opts, Core> {}\n\nexport type { PluginLifecycleSpec, PluginOptions } from '@seedcord/core/plugin';\n","export * from './edge.index';\nexport { Seedcord } from '@src/node/Seedcord';\n\nexport { Plugin } from './plugin';\nexport type { HttpPluginOptions, PluginLifecycleSpec, PluginOptions } from './plugin';\n"],"mappings":"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"}
@@ -3,13 +3,30 @@ let _seedcord_errors_internal = require("@seedcord/errors/internal");
3
3
  let _seedcord_errors = require("@seedcord/errors");
4
4
 
5
5
  //#region src/manifest.index.ts
6
+ const notGenerated = () => {
7
+ throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigManifestNotGenerated);
8
+ };
6
9
  /**
7
- * The generated route table. `seedcord build` aliases this module to the emitted file. An un-built
8
- * import throws.
10
+ * The generated route table. `seedcord build` aliases this module to the emitted file. Reading a route
11
+ * list off the un-built stub will throw.
9
12
  */
10
- const manifest = (() => {
11
- throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigManifestNotGenerated);
12
- })();
13
+ const manifest = {
14
+ get commandRoutes() {
15
+ return notGenerated();
16
+ },
17
+ get componentRoutes() {
18
+ return notGenerated();
19
+ },
20
+ get autocompleteRoutes() {
21
+ return notGenerated();
22
+ },
23
+ get subscriberRoutes() {
24
+ return notGenerated();
25
+ },
26
+ get middlewareRoutes() {
27
+ return notGenerated();
28
+ }
29
+ };
13
30
 
14
31
  //#endregion
15
32
  exports.manifest = manifest;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.index.cjs","names":["SeedcordError","SeedcordErrorCode"],"sources":["../src/manifest.index.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { RouteManifest } from '@src/manifest/RouteManifest';\n\nexport type { RouteManifest } from '@src/manifest/RouteManifest';\n\n/**\n * The generated route table. `seedcord build` aliases this module to the emitted file. An un-built\n * import throws.\n */\nexport const manifest: RouteManifest = (() => {\n throw new SeedcordError(SeedcordErrorCode.ConfigManifestNotGenerated);\n})();\n"],"mappings":";;;;;;;;;AAWA,MAAa,kBAAiC;CAC1C,MAAM,IAAIA,wCAAcC,mCAAkB,0BAA0B;AACxE,EAAC,CAAE"}
1
+ {"version":3,"file":"manifest.index.cjs","names":["SeedcordError","SeedcordErrorCode"],"sources":["../src/manifest.index.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { RouteManifest } from '@src/manifest/RouteManifest';\n\nexport type { RouteManifest } from '@src/manifest/RouteManifest';\n\nconst notGenerated = (): never => {\n throw new SeedcordError(SeedcordErrorCode.ConfigManifestNotGenerated);\n};\n\n/**\n * The generated route table. `seedcord build` aliases this module to the emitted file. Reading a route\n * list off the un-built stub will throw.\n */\nexport const manifest: RouteManifest = {\n get commandRoutes(): never {\n return notGenerated();\n },\n get componentRoutes(): never {\n return notGenerated();\n },\n get autocompleteRoutes(): never {\n return notGenerated();\n },\n get subscriberRoutes(): never {\n return notGenerated();\n },\n get middlewareRoutes(): never {\n return notGenerated();\n }\n};\n"],"mappings":";;;;;AAOA,MAAM,qBAA4B;CAC9B,MAAM,IAAIA,wCAAcC,mCAAkB,0BAA0B;AACxE;;;;;AAMA,MAAa,WAA0B;CACnC,IAAI,gBAAuB;EACvB,OAAO,aAAa;CACxB;CACA,IAAI,kBAAyB;EACzB,OAAO,aAAa;CACxB;CACA,IAAI,qBAA4B;EAC5B,OAAO,aAAa;CACxB;CACA,IAAI,mBAA0B;EAC1B,OAAO,aAAa;CACxB;CACA,IAAI,mBAA0B;EAC1B,OAAO,aAAa;CACxB;AACJ"}
@@ -1,9 +1,9 @@
1
- import { t as RouteManifest } from "./RouteManifest-BHdAeyLG.mjs";
1
+ import { t as RouteManifest } from "./RouteManifest-DE-HCMwd.mjs";
2
2
 
3
3
  //#region src/manifest.index.d.ts
4
4
  /**
5
- * The generated route table. `seedcord build` aliases this module to the emitted file. An un-built
6
- * import throws.
5
+ * The generated route table. `seedcord build` aliases this module to the emitted file. Reading a route
6
+ * list off the un-built stub will throw.
7
7
  */
8
8
  declare const manifest: RouteManifest;
9
9
  //#endregion
@@ -2,13 +2,30 @@ import { SeedcordError } from "@seedcord/errors/internal";
2
2
  import { SeedcordErrorCode } from "@seedcord/errors";
3
3
 
4
4
  //#region src/manifest.index.ts
5
+ const notGenerated = () => {
6
+ throw new SeedcordError(SeedcordErrorCode.ConfigManifestNotGenerated);
7
+ };
5
8
  /**
6
- * The generated route table. `seedcord build` aliases this module to the emitted file. An un-built
7
- * import throws.
9
+ * The generated route table. `seedcord build` aliases this module to the emitted file. Reading a route
10
+ * list off the un-built stub will throw.
8
11
  */
9
- const manifest = (() => {
10
- throw new SeedcordError(SeedcordErrorCode.ConfigManifestNotGenerated);
11
- })();
12
+ const manifest = {
13
+ get commandRoutes() {
14
+ return notGenerated();
15
+ },
16
+ get componentRoutes() {
17
+ return notGenerated();
18
+ },
19
+ get autocompleteRoutes() {
20
+ return notGenerated();
21
+ },
22
+ get subscriberRoutes() {
23
+ return notGenerated();
24
+ },
25
+ get middlewareRoutes() {
26
+ return notGenerated();
27
+ }
28
+ };
12
29
 
13
30
  //#endregion
14
31
  export { manifest };
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.index.mjs","names":[],"sources":["../src/manifest.index.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { RouteManifest } from '@src/manifest/RouteManifest';\n\nexport type { RouteManifest } from '@src/manifest/RouteManifest';\n\n/**\n * The generated route table. `seedcord build` aliases this module to the emitted file. An un-built\n * import throws.\n */\nexport const manifest: RouteManifest = (() => {\n throw new SeedcordError(SeedcordErrorCode.ConfigManifestNotGenerated);\n})();\n"],"mappings":";;;;;;;;AAWA,MAAa,kBAAiC;CAC1C,MAAM,IAAI,cAAc,kBAAkB,0BAA0B;AACxE,EAAC,CAAE"}
1
+ {"version":3,"file":"manifest.index.mjs","names":[],"sources":["../src/manifest.index.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { RouteManifest } from '@src/manifest/RouteManifest';\n\nexport type { RouteManifest } from '@src/manifest/RouteManifest';\n\nconst notGenerated = (): never => {\n throw new SeedcordError(SeedcordErrorCode.ConfigManifestNotGenerated);\n};\n\n/**\n * The generated route table. `seedcord build` aliases this module to the emitted file. Reading a route\n * list off the un-built stub will throw.\n */\nexport const manifest: RouteManifest = {\n get commandRoutes(): never {\n return notGenerated();\n },\n get componentRoutes(): never {\n return notGenerated();\n },\n get autocompleteRoutes(): never {\n return notGenerated();\n },\n get subscriberRoutes(): never {\n return notGenerated();\n },\n get middlewareRoutes(): never {\n return notGenerated();\n }\n};\n"],"mappings":";;;;AAOA,MAAM,qBAA4B;CAC9B,MAAM,IAAI,cAAc,kBAAkB,0BAA0B;AACxE;;;;;AAMA,MAAa,WAA0B;CACnC,IAAI,gBAAuB;EACvB,OAAO,aAAa;CACxB;CACA,IAAI,kBAAyB;EACzB,OAAO,aAAa;CACxB;CACA,IAAI,qBAA4B;EAC5B,OAAO,aAAa;CACxB;CACA,IAAI,mBAA0B;EAC1B,OAAO,aAAa;CACxB;CACA,IAAI,mBAA0B;EAC1B,OAAO,aAAa;CACxB;AACJ"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@seedcord/http",
3
3
  "type": "module",
4
- "version": "0.1.0-next.1",
4
+ "version": "0.1.0-next.3",
5
5
  "description": "HTTP-interactions transport for Seedcord bots",
6
6
  "engines": {
7
7
  "node": ">=24.3"
@@ -53,15 +53,16 @@
53
53
  "dependencies": {
54
54
  "@discordjs/core": "^2.6.0",
55
55
  "@discordjs/rest": "^2.6.2",
56
+ "chalk": "^5.6.2",
56
57
  "discord-api-types": "^0.38.51",
57
58
  "envapt": "^8.1.0",
58
59
  "type-fest": "^5.7.0",
59
- "@seedcord/core": "0.1.0-next.6",
60
- "@seedcord/errors": "0.3.0-next.5",
61
- "@seedcord/logger": "0.1.0-next.2",
62
- "@seedcord/rate-limiter": "0.1.0-next.4",
63
- "@seedcord/types": "0.8.0-next.7",
64
- "@seedcord/utils": "0.8.0-next.7"
60
+ "@seedcord/core": "0.1.0-next.8",
61
+ "@seedcord/errors": "0.3.0-next.7",
62
+ "@seedcord/logger": "0.1.0-next.4",
63
+ "@seedcord/rate-limiter": "0.1.0-next.5",
64
+ "@seedcord/types": "0.8.0-next.8",
65
+ "@seedcord/utils": "0.8.0-next.9"
65
66
  },
66
67
  "peerDependencies": {
67
68
  "reflect-metadata": "^0.2.2",