@seedcord/http 0.1.0-next.2 → 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,22 +1,23 @@
1
1
  import { n as __reExport, t as __exportAll } from "./rolldown-runtime-BUUfjzRC.mjs";
2
- import { a as ModalHandler, c as ComponentHandler, d as createSeedcord, f as buildEngine, g as RepliableHandler, h as AutocompleteHandler, i as WebhookLog, l as ContextMenuHandler, m as InteractionHandler, n as Gated, o as SelectMenuHandler, p as buildRouteMaps, r as Subscriber, s as ButtonHandler, t as version, u as SlashHandler } from "./edge.index-BBRnxxkS.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
6
  import { Bus } from "@seedcord/core";
7
- import { HmrManager, InteractionMetadataKey, InteractionRoutes, interactionRoutesOf, setBotColor } from "@seedcord/core/internal";
7
+ import { CommandInjector, HmrManager, InteractionMetadataKey, InteractionRoutes, interactionRoutesOf, setBotColor } from "@seedcord/core/internal";
8
8
  import { Logger, LoggerChannelRegistry, paint } from "@seedcord/logger";
9
9
  import { MemoryRateLimiter } from "@seedcord/rate-limiter";
10
10
  import { Routes } from "discord-api-types/v10";
11
11
  import { SeedcordErrorCode } from "@seedcord/errors";
12
12
  import { once } from "node:events";
13
13
  import { createServer } from "node:http";
14
- import { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader } from "@seedcord/core/node/internal";
14
+ import { CommandRegistry, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader } from "@seedcord/core/node/internal";
15
15
  import { installNodeDefaults } from "@seedcord/logger/node";
16
16
  import { SeedcordBrand } from "@seedcord/types/internal";
17
17
  import { HmrModuleHandler } from "@seedcord/core/hmr";
18
18
  import { formatFilePath } from "@seedcord/utils";
19
19
  import { traverseDirectory } from "@seedcord/utils/node";
20
+ import chalk from "chalk";
20
21
  import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
21
22
 
22
23
  export * from "@seedcord/core"
@@ -87,6 +88,19 @@ var InteractionDispatcher = class {
87
88
  async onHmr(event) {
88
89
  await this.hmrHandler?.handle(event);
89
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
+ }
90
104
  isHandler(value) {
91
105
  if (typeof value !== "function") return false;
92
106
  return (value.prototype instanceof InteractionHandler || value.prototype instanceof AutocompleteHandler) && Reflect.hasMetadata(InteractionMetadataKey, value);
@@ -253,6 +267,10 @@ var Seedcord = class Seedcord extends Pluggable {
253
267
  bus;
254
268
  subscribers;
255
269
  interactions;
270
+ commandRegistry;
271
+ appId;
272
+ appIdPromise;
273
+ emojiInjector = new EmojiInjector(this, () => this.applicationId());
256
274
  healthCheck;
257
275
  hmrManager;
258
276
  logger = new Logger("Server", { channel: "bot" });
@@ -268,6 +286,19 @@ var Seedcord = class Seedcord extends Pluggable {
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();
272
303
  this.bus = new Bus(this);
273
304
  this.subscribers = new SubscriberLoader(this.bus, config.subscribers.path);
@@ -282,13 +313,6 @@ var Seedcord = class Seedcord extends Pluggable {
282
313
  get port() {
283
314
  return this.boundPort;
284
315
  }
285
- pluginCapabilities() {
286
- const token = Envapter.get("DISCORD_BOT_TOKEN");
287
- return {
288
- rest: this.rest,
289
- ...token !== void 0 && { token }
290
- };
291
- }
292
316
  /**
293
317
  * Starts the host and runs the startup tasks.
294
318
  *
@@ -322,6 +346,19 @@ var Seedcord = class Seedcord extends Pluggable {
322
346
  await interactions.init();
323
347
  interactions.logger.utils.initialization("Interactions", "end");
324
348
  });
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
+ });
325
362
  this.startup.addTask(StartupPhase.Ready, "http-server", () => this.listen());
326
363
  if (!Envapter.isTest) this.startup.addTask(StartupPhase.Ready, "identity", () => this.fetchUsername());
327
364
  const { healthCheck } = this;
@@ -334,14 +371,24 @@ var Seedcord = class Seedcord extends Pluggable {
334
371
  registerHmrAwareModules() {
335
372
  this.startup.addTask(StartupPhase.Configuration, "hmr-registration", async () => {
336
373
  if (this.interactions) this.hmrManager.register(this.interactions);
374
+ if (this.commandRegistry) this.hmrManager.register(this.commandRegistry);
337
375
  this.hmrManager.register(this.subscribers);
338
376
  for (const plugin of this.plugins) this.hmrManager.register(plugin);
339
377
  await Promise.resolve();
340
378
  });
341
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
+ }
342
391
  async listen() {
343
- const token = validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN"));
344
- this.rest.setToken(token);
345
392
  const maps = this.interactions?.maps ?? buildRouteMaps(EMPTY_MANIFEST);
346
393
  const { handle, inFlight } = buildEngine(this, maps);
347
394
  const server = createServer((incoming, outgoing) => {
@@ -392,22 +439,26 @@ var Seedcord = class Seedcord extends Pluggable {
392
439
  * Base class for an HTTP plugin, binding `this.core` to the HTTP {@link Core}.
393
440
  *
394
441
  * A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
395
- * `this.core` carries the shared members only.
442
+ * `this.core` is typed to the shared members only.
396
443
  *
397
- * @typeParam Opts - The transport, runtime, and capabilities the plugin declares.
444
+ * @typeParam Opts - The transport and runtime the plugin declares.
398
445
  */
399
446
  var Plugin = class extends Plugin$1 {};
400
447
 
401
448
  //#endregion
402
449
  //#region src/index.ts
403
450
  var src_exports = /* @__PURE__ */ __exportAll({
451
+ ArraySource: () => ArraySource,
404
452
  AutocompleteHandler: () => AutocompleteHandler,
405
453
  ButtonHandler: () => ButtonHandler,
406
454
  ComponentHandler: () => ComponentHandler,
407
455
  ContextMenuHandler: () => ContextMenuHandler,
456
+ CursorSource: () => CursorSource,
457
+ Emojis: () => Emojis,
408
458
  Gated: () => Gated,
409
459
  InteractionHandler: () => InteractionHandler,
410
460
  ModalHandler: () => ModalHandler,
461
+ Paginator: () => Paginator,
411
462
  Plugin: () => Plugin,
412
463
  RepliableHandler: () => RepliableHandler,
413
464
  Seedcord: () => Seedcord,
@@ -420,5 +471,5 @@ var src_exports = /* @__PURE__ */ __exportAll({
420
471
  });
421
472
 
422
473
  //#endregion
423
- export { AutocompleteHandler, ButtonHandler, ComponentHandler, ContextMenuHandler, Gated, InteractionHandler, ModalHandler, Plugin, RepliableHandler, Seedcord, SelectMenuHandler, SlashHandler, Subscriber, WebhookLog, createSeedcord, version };
474
+ export { ArraySource, AutocompleteHandler, ButtonHandler, ComponentHandler, ContextMenuHandler, CursorSource, Emojis, Gated, InteractionHandler, ModalHandler, Paginator, Plugin, RepliableHandler, Seedcord, SelectMenuHandler, SlashHandler, Subscriber, WebhookLog, createSeedcord, version };
424
475
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
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 } 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', { 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 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 { HmrManager, setBotColor } from '@seedcord/core/internal';\nimport {\n CoordinatedShutdown,\n CoordinatedStartup,\n HealthCheck,\n Pluggable,\n ShutdownPhase,\n StartupPhase,\n SubscriberLoader\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 { 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 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 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 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 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.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 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 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","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` carries the shared members only.\n *\n * @typeParam Opts - The transport, runtime, and capabilities the plugin declares.\n */\nexport abstract class Plugin<Opts extends HttpPluginOptions = {}> extends CorePlugin<Opts, Core> {}\n\nexport type { PluginContext, PluginLifecycleSpec, PluginOptions } from '@seedcord/core/plugin';\n","export * from './edge.index';\nexport { Seedcord } from '@src/node/Seedcord';\n\n// the plugin authoring surface with http's Core bound to it\nexport { Plugin } from './plugin';\nexport type { HttpPluginOptions, PluginContext, PluginLifecycleSpec, PluginOptions } from './plugin';\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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAyEA,MAAa,iBAAgC;CACzC,eAAe,CAAC;CAChB,iBAAiB,CAAC;CAClB,oBAAoB,CAAC;CACrB,kBAAkB,CAAC;CACnB,kBAAkB,CAAC;AACvB;;;;;;;;;;AC/CA,IAAa,wBAAb,MAAuE;CAkBtC;CAjB7B,AAAgB;CAChB,AAAiB;;CAGjB,AAAgB,SAAS,IAAI,OAAO,gBAAgB,EAAE,SAAS,eAAe,CAAC;CAE/E,AAAQ,gBAAgB;CACxB,AAAiB;CAEjB,AAAiB,4BAAY,IAAI,IAG/B;CAEF,AAAQ,UAAU;CAClB,AAAiB,iBAAmD,CAAC;CAErE,YAAY,AAAiB,aAAqB;EAArB;EACzB,KAAK,OAAO,eAAe,cAAc;EACzC,KAAK,UAAU,KAAK,aAAa;EAEjC,IAAI,CAAC,SAAS,iBAAiB,CAAC,SAAS,QAAQ;EACjD,KAAK,aAAa,IAAI,iBAAiB;GACnC;GACA,WAAW,KAAK,UAAU,KAAK,IAAI;GACnC,iBAAiB,KAAK,gBAAgB,KAAK,IAAI;GAC/C,mBAAmB,KAAK,kBAAkB,KAAK,IAAI;GACnD,cAAc,KAAK,aAAa,KAAK,IAAI;GACzC,QAAQ,KAAK;EACjB,CAAC;CACL;;CAGA,MAAa,OAAsB;EAC/B,IAAI,KAAK,eAAe;EACxB,KAAK,gBAAgB;EAErB,KAAK,UAAU;EACf,KAAK,eAAe,SAAS;EAC7B,IAAI;GACA,MAAM,kBAAkB,KAAK,cAAc,UAAU,cAAc,aAAa;IAC5E,KAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,GAAG;KACzC,IAAI,CAAC,KAAK,UAAU,KAAK,GAAG;KAC5B,KAAK,gBAAgB,OAAO,YAAY;KACxC,KAAK,YAAY,aAAa,UAAU,KAAK;IACjD;GACJ,CAAC;EACL,UAAU;GACN,KAAK,UAAU;EACnB;EAEA,KAAK,OAAO,MAAM,MAAM,mBAAmB,CACvC,GAAG,KAAK,OAAO,MAAM,QAAQ,KAAK,cAAc,GAChD,GAAG,KAAK,OAAO,MAAM,OAAO,EAAE,QAAQ,KAAK,UAAU,KAAK,CAAC,CAC/D,CAAC;CACL;;CAGA,MAAa,MAAM,OAAsC;EACrD,MAAM,KAAK,YAAY,OAAO,KAAK;CACvC;CAEA,AAAO,oBAAoB,eAAuC;EAC9D,KAAK,YAAY,eAAe,KAAK,KAAK,OAAO,eAAe,aAAa;CACjF;CAEA,AAAO,+BAA+B,QAAiC;EACnE,KAAK,YAAY,OAAO,MAAM,KAAK,KAAK,iBAAiB,qBAAqB,mBAAmB;EACjG,KAAK,YAAY,OAAO,SAAS,KAAK,KAAK,oBAAoB,wBAAwB,mBAAmB;CAC9G;CAEA,AAAQ,YAAY,OAAyB,KAAe,OAAe,WAAyB;EAChG,KAAK,MAAM,QAAQ,OAAO;GACtB,IAAI,IAAI,IAAI,IAAI,GAAG;GACnB,KAAK,OAAO,KAAK,GAAG,MAAM,GAAG,MAAM,IAAI,KAAK,IAAI,EAAE,qBAAqB,MAAM,KAAK,SAAS,EAAE,UAAU;EAC3G;CACJ;CAEA,AAAQ,UAAU,OAA6C;EAC3D,IAAI,OAAO,UAAU,YAAY,OAAO;EAExC,QACK,MAAM,qBAAqB,sBAAsB,MAAM,qBAAqB,wBAC7E,QAAQ,YAAY,wBAAwB,KAAK;CAEzD;CAEA,AAAQ,aAAa,MAAoC;EACrD,MAAM,WAAqB,CAAC;EAC5B,KAAK,MAAM,CAAC,SAAS,UAAU,KAAK,WAChC,IAAI,MAAM,SAAS,MAAM,SAAS,KAAK,OAAO;EAElD,OAAO;CACX;CAEA,AAAQ,UAAU,OAAuC;EACrD,OAAO,KAAK,QAAQ;CACxB;CAEA,AAAQ,eAAuD;EAC3D,OAAO;IACF,kBAAkB,QAAQ;IAAE,KAAK,KAAK,KAAK;IAAO,MAAM;GAAQ;IAChE,kBAAkB,kBAAkB;IAAE,KAAK,KAAK,KAAK;IAAiB,MAAM;GAAkB;IAC9F,kBAAkB,qBAAqB;IAAE,KAAK,KAAK,KAAK;IAAoB,MAAM;GAAqB;IACvG,kBAAkB,eAAe;IAAE,KAAK,KAAK,KAAK;IAAc,MAAM;GAAe;IACrF,kBAAkB,SAAS;IAAE,KAAK,KAAK,KAAK,WAAW;IAAQ,MAAM;GAAS;IAC9E,kBAAkB,aAAa;IAAE,KAAK,KAAK,KAAK,WAAW;IAAc,MAAM;GAAa;IAC5F,kBAAkB,WAAW;IAAE,KAAK,KAAK,KAAK,WAAW;IAAY,MAAM;GAAW;IACtF,kBAAkB,WAAW;IAAE,KAAK,KAAK,KAAK,WAAW;IAAY,MAAM;GAAW;IACtF,kBAAkB,cAAc;IAAE,KAAK,KAAK,KAAK,WAAW;IAAe,MAAM;GAAc;IAC/F,kBAAkB,kBAAkB;IACjC,KAAK,KAAK,KAAK,WAAW;IAC1B,MAAM;GACV;IACC,kBAAkB,QAAQ;IAAE,KAAK,KAAK,KAAK,WAAW;IAAO,MAAM;GAAQ;EAChF;CACJ;CAEA,AAAQ,gBAAgB,MAA0B,cAA4B;EAC1E,MAAM,OAAO,eAAe,YAAY;EAExC,MAAM,SAAkC,CAAC;EAEzC,KAAK,MAAM,CAAC,OAAO,SAAS,oBAAoB,IAAI,GAAG;GACnD,MAAM,SAAS,KAAK,UAAU,KAAK;GACnC,KAAK,MAAM,OAAO,MAAM;IACpB,MAAM,UAAU,GAAG,OAAO,KAAK,GAAG;IAClC,MAAM,WAAW,KAAK,UAAU,IAAI,OAAO;IAE3C,IAAI,YAAY,SAAS,SAAS,MAC9B,MAAM,IAAI,cAAc,kBAAkB,2BAA2B;KACjE;KACA,GAAG,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK;KACxC,GAAG,KAAK,KAAK,IAAI,KAAK;IAC1B,CAAC;IAEL,OAAO,KAAK,CAAC,QAAQ,GAAG,CAAC;GAC7B;EACJ;EAEA,IAAI,OAAO,WAAW,GAAG;EACzB,KAAK,MAAM,CAAC,QAAQ,QAAQ,QAAQ;GAChC,MAAM,UAAU,GAAG,OAAO,KAAK,GAAG;GAClC,OAAO,IAAI,IAAI,KAAK;IAAE,MAAM,OAAO;IAAM;IAAS,YAAY,QAAQ,QAAQ,IAAI;GAAE,CAAC;GACrF,KAAK,UAAU,IAAI,SAAS;IAAE;IAAM;IAAQ;IAAK;GAAK,CAAC;EAC3D;EAEA,IAAI,KAAK,SAAS,KAAK,eAAe,KAAK;GAAE,MAAM,KAAK;GAAM;EAAK,CAAC;OAC/D,KAAK,OAAO,MAAM,aAAa,KAAK,MAAM,IAAI;CACvD;CAEA,AAAQ,kBAAkB,MAA0B,WAA4B;EAC5E,MAAM,WAAW,aAAa,KAAK,aAAa,IAAI;EACpD,KAAK,MAAM,WAAW,UAAU;GAC5B,MAAM,QAAQ,KAAK,UAAU,IAAI,OAAO;GACxC,IAAI,OAAO,SAAS,MAAM;GAC1B,KAAK,UAAU,OAAO,OAAO;GAC7B,MAAM,OAAO,IAAI,OAAO,MAAM,GAAG;EACrC;CACJ;AACJ;;;;AC/LA,SAAS,UAAU,KAAmC;CAClD,MAAM,UAAU,IAAI,QAAQ;CAC5B,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC7C,IAAI,UAAU,QAAW;EACzB,IAAI,MAAM,QAAQ,KAAK,GACnB,KAAK,MAAM,QAAQ,OAAO,QAAQ,OAAO,MAAM,IAAI;OAEnD,QAAQ,IAAI,MAAM,KAAK;CAE/B;CACA,OAAO;AACX;AAGA,eAAsB,aAAa,SAA4C;CAC3E,MAAM,SAAmB,CAAC;CAE1B,WAAW,MAAM,SAAS,SAAS,OAAO,KAAK,KAAe;CAE9D,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,OAAO,WAAW,SAAS,WAAW,SAAS,SAAY,IAAI,WAAW,OAAO,OAAO,MAAM,CAAC;CAErG,OAAO,IAAI,QAAQ,UAAU,QAAQ,QAAQ,QAAQ,cAAc,QAAQ,OAAO,OAAO;EACrF;EACA,SAAS,UAAU,QAAQ,OAAO;EAClC,GAAI,SAAS,UAAa,EAAE,KAAK;CACrC,CAAC;AACL;AAEA,eAAsB,iBAAiB,UAAoB,KAAoC;CAC3F,MAAM,UAA6C,OAAO,YAAY,SAAS,OAAO;CAEtF,MAAM,UAAU,SAAS,QAAQ,aAAa;CAC9C,IAAI,QAAQ,SAAS,GAAG,QAAQ,gBAAgB;CAEhD,IAAI,UAAU,SAAS,QAAQ,OAAO;CACtC,IAAI,IAAI,SAAS,SAAS,OAAO,SAAY,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC;AAC1F;;;;ACGA,MAAM,eAAe;AACrB,MAAM,6BAA6B;AACnC,MAAM,mBAAmB;;;;;;;;AAWzB,IAAa,WAAb,MAAa,iBACD,UAEZ;CAkCgC;;CA/B5B,CAAiB,iBAAiB;;CAElC,AAAgB,gBAAgB;;CAEhC,AAAgB,UAAkBA;;CAGlC,AAAgB,OAAO,IAAI,KAAK;;CAGhC,AAAgB;;CAGhB,AAAgB;CAEhB,AAAiB;CAEjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ;CACR,AAAQ;CACR,AAAiB,gBAAgB,IAAI,cAAc,YAAY,KAAK,cAAc,CAAC;CACnF,AAAiB;CACjB,AAAiB;CACjB,AAAiB,SAAS,IAAI,OAAO,UAAU,EAAE,SAAS,MAAM,CAAC;CAEjE,AAAQ;CACR,AAAQ;CACR,AAAQ,gBAAgB;CACxB,AAAQ;CAER,YAAY,AAAgB,QAAa;EACrC,MAAM,IAAI,oBAAoB,GAAG,IAAI,mBAAmB,CAAC;EADjC;EAGxB,oBAAoB,OAAO,MAAM;EACjC,YAAY,OAAO,QAAQ;EAE3B,KAAK,aAAa,IAAI,WAAW;EACjC,KAAK,WAAW,KAAK;EAErB,IAAI,KAAK,OAAO,IAAI,aAAa,MAC7B,KAAK,eAAe,IAAI,sBAAsB,KAAK,OAAO,IAAI,aAAa,IAAI;EAGnF,MAAM,cAAc,KAAK,OAAO,IAAI,SAAS;EAC7C,IAAI,aAAa;GACb,MAAM,WAAW,IAAI,gBAAgB;GAErC,MAAM,WAA4B,IAAI,gBAAgB;IAClD,KAAK;IACL,MAAM,KAAK;IACX,qBAAqB,KAAK,qBAAqB;IAC/C,aAAa,WAAW;KACpB,SAAS,OAAO,QAAQ,SAAS,YAAY,CAAC;IAClD;GACJ,CAAC;GACD,KAAK,kBAAkB;EAC3B;EAEA,KAAK,cAAc,OAAO,SAAS,IAAI,kBAAkB;EACzD,KAAK,MAAM,IAAI,IAAI,IAAI;EACvB,KAAK,cAAc,IAAI,iBAAiB,KAAK,KAAK,OAAO,YAAY,IAAI;EAEzE,KAAK,cACD,OAAO,YAAY,SAAS,SAAY,YAAY,WAAW,KAAK,UAAU,OAAO,WAAW;EAEpG,KAAK,qBAAqB;CAC9B;;CAGA,IAAW,WAA+B;EACtC,OAAO,KAAK;CAChB;;CAGA,IAAW,OAA2B;EAClC,OAAO,KAAK;CAChB;;;;;;CAOA,MAAa,MAAM,OAAO,cAA6B;EACnD,KAAK,gBAAgB;EACrB,IAAI;GACA,MAAM,MAAM,KAAK;EACrB,SAAS,QAAQ;GAEb,MAAM,KAAK,SAAS,IAAI,GAAG,KAAK;GAChC,SAAS,MAAM;GACf,MAAM;EACV;EACA,OAAO;CACX;CAEA,OAA0B,QAAc;EACpC,MAAM,MAAM;EACZ,sBAAsB,SAAS,MAAM;CACzC;CAEA,AAAQ,uBAA6B;EACjC,IAAI,SAAS,iBAAiB,SAAS,QAAQ,KAAK,wBAAwB;EAE5E,KAAK,QAAQ,QAAQ,aAAa,eAAe,sBAAsB,YAAY;GAC/E,KAAK,IAAI,OAAO,MAAM,eAAe,eAAe,OAAO;GAC3D,MAAM,KAAK,YAAY,KAAK;GAC5B,KAAK,IAAI,OAAO,MAAM,eAAe,eAAe,KAAK;EAC7D,CAAC;EAED,MAAM,EAAE,iBAAiB;EACzB,IAAI,cACA,KAAK,QAAQ,QAAQ,aAAa,eAAe,+BAA+B,YAAY;GACxF,aAAa,OAAO,MAAM,eAAe,gBAAgB,OAAO;GAChE,MAAM,aAAa,KAAK;GACxB,aAAa,OAAO,MAAM,eAAe,gBAAgB,KAAK;EAClE,CAAC;EAGL,KAAK,QAAQ,QAAQ,aAAa,eAAe,sBAAsB;GACnE,KAAK,aAAa;GAClB,OAAO,QAAQ,QAAQ;EAC3B,CAAC;EAGD,KAAK,QAAQ,QAAQ,aAAa,OAAO,yBAAyB,KAAK,cAAc,KAAK,CAAC;EAE3F,MAAM,EAAE,oBAAoB;EAC5B,IAAI,iBAEA,KAAK,QAAQ,QAAQ,aAAa,OAAO,kBAAkB,YAAY;GACnE,MAAM,gBAAgB,KAAK;GAC3B,KAAK,QAAQ,MAAM,KAAK,cAAc;GACtC,MAAM,gBAAgB,YAAY;GAClC,cAAc,oBAAoB,gBAAgB,YAAY,CAAC;GAC/D,cAAc,+BAA+B,gBAAgB,kBAAkB,CAAC;EACpF,CAAC;EAGL,KAAK,QAAQ,QAAQ,aAAa,OAAO,qBAAqB,KAAK,OAAO,CAAC;EAE3E,IAAI,CAAC,SAAS,QACV,KAAK,QAAQ,QAAQ,aAAa,OAAO,kBAAkB,KAAK,cAAc,CAAC;EAGnF,MAAM,EAAE,gBAAgB;EACxB,IAAI,aACA,KAAK,QAAQ,QAAQ,aAAa,OAAO,gBAAgB,YAAY;GACjE,YAAY,OAAO,MAAM,eAAe,eAAe,OAAO;GAC9D,MAAM,YAAY,KAAK;GACvB,YAAY,OAAO,MAAM,eAAe,eAAe,KAAK;EAChE,CAAC;CAET;CAEA,AAAQ,0BAAgC;EACpC,KAAK,QAAQ,QAAQ,aAAa,eAAe,oBAAoB,YAAY;GAC7E,IAAI,KAAK,cAAc,KAAK,WAAW,SAAS,KAAK,YAAY;GACjE,IAAI,KAAK,iBAAiB,KAAK,WAAW,SAAS,KAAK,eAAe;GACvE,KAAK,WAAW,SAAS,KAAK,WAAW;GACzC,KAAK,MAAM,UAAU,KAAK,SACtB,KAAK,WAAW,SAAS,MAAM;GAEnC,MAAM,QAAQ,QAAQ;EAC1B,CAAC;CACL;CAEA,AAAQ,eAAqB;EACzB,KAAK,KAAK,SAAS,qBAAqB,SAAS,IAAI,mBAAmB,CAAC,CAAC;CAC9E;CAGA,AAAQ,gBAAiC;EACrC,KAAK,iBAAiB,mBAAmB,KAAK,IAAI;EAClD,OAAO,KAAK;CAChB;CAGA,AAAQ,uBAA+B;EACnC,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,cAAc,kBAAkB,0BAA0B;EACrF,OAAO,KAAK;CAChB;CAEA,MAAc,SAAwB;EAClC,MAAM,OAAO,KAAK,cAAc,QAAQ,eAAe,cAAc;EACrE,MAAM,EAAE,QAAQ,aAAa,YAAY,MAAM,IAAI;EAEnD,MAAM,SAAS,cAAc,UAAU,aAAa;GAChD,CAAM,YAAY;IAEd,MAAM,iBAAiB,MADA,OAAO,MAAM,aAAa,QAAQ,CAAC,GACzB,QAAQ;GAC7C,EAAC,CAAE,CAAC,CAAC,OAAO,UAAmB;IAE3B,SAAS,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;GAC5E,CAAC;EACL,CAAC;EACD,KAAK,SAAS;EAEd,OAAO,OAAO,KAAK,aAAa;EAChC,MAAM,KAAK,QAAQ,WAAW;EAE9B,KAAK,YAAa,OAAO,QAAQ,CAAC,CAAiB;EACnD,KAAK,OAAO,KAAK,yCAAyC,MAAM,IAAI,KAAK,OAAO,KAAK,SAAS,CAAC,GAAG;EAElG,KAAK,SAAS,QACV,cAAc,QACd,0BACM,KAAK,WAAW,GACtB,0BACJ;EAEA,KAAK,SAAS,QACV,cAAc,OACd,kBACA,YAAY;GACR,MAAM,QAAQ,WAAW,QAAQ;EACrC,GACA,gBACJ;CACJ;CAEA,MAAc,gBAA+B;EACzC,IAAI;GAEA,MAAM,KAAM,MAAM,KAAK,KAAK,IAAI,OAAO,KAAK,KAAK,CAAC;GAClD,KAAK,kBAAkB,GAAG;EAC9B,SAAS,QAAQ;GAEb,KAAK,OAAO,KAAK,oCAAoC,MAAM;EAC/D;CACJ;CAEA,AAAQ,aAA4B;EAChC,MAAM,SAAS,KAAK;EACpB,IAAI,CAAC,QAAQ,WAAW,OAAO,QAAQ,QAAQ;EAE/C,OAAO,IAAI,SAAS,cAAc,gBAAgB;GAC9C,OAAO,OAAO,QAAQ;IAClB,IAAI,KAAK;KACL,YAAY,GAAG;KACf;IACJ;IACA,KAAK,OAAO,KAAK,MAAM,MAAM,KAAK,6BAA6B,CAAC;IAChE,aAAa;GACjB,CAAC;GAGD,OAAO,qBAAqB;EAChC,CAAC;CACL;AACJ;;;;;;;;;;;;ACvSA,IAAsB,SAAtB,cAA0EC,SAAuB,CAAC"}
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.2",
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.7",
60
- "@seedcord/errors": "0.3.0-next.6",
61
- "@seedcord/logger": "0.1.0-next.3",
60
+ "@seedcord/core": "0.1.0-next.8",
61
+ "@seedcord/errors": "0.3.0-next.7",
62
+ "@seedcord/logger": "0.1.0-next.4",
62
63
  "@seedcord/rate-limiter": "0.1.0-next.5",
63
64
  "@seedcord/types": "0.8.0-next.8",
64
- "@seedcord/utils": "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",