@seedcord/http 0.10.0 → 0.10.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["packageVersion","CorePlugin"],"sources":["../src/node/InteractionDispatcher.ts","../src/node/webBridge.ts","../src/node/Seedcord.ts","../src/plugin.ts"],"sourcesContent":["import { InteractionKind } from '@seedcord/core';\nimport { HmrModuleHandler } from '@seedcord/core/hmr';\nimport {\n interactionMiddleware,\n InteractionMetadataKey,\n InteractionMiddlewareMetadataKey,\n MiddlewareRegistry\n} from '@seedcord/core/internal';\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Envapter } from 'envapt';\n\nimport { RouteRegistry } from '#src/dispatch/RouteRegistry';\nimport { isHandlerClass, isMiddlewareClass } from '#src/manifest/entries';\n\nimport type { HandlerConstructor, InteractionMiddlewareConstructor } from '#handlers/constructors';\nimport type { RouteMap, RouteMaps } from '#src/dispatch/resolve';\nimport type { Initializeable, ContextMenuLeaves } from '@seedcord/core/internal';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\n// hmr swaps entries in these maps while resolve() reads them per request\nexport class InteractionDispatcher implements Initializeable, HmrAware {\n /** @internal */\n public readonly middlewares = new MiddlewareRegistry<InteractionMiddlewareConstructor>(interactionMiddleware);\n\n /** @internal */\n public readonly logger = new Logger('Interactions', { channel: 'interactions' });\n\n private readonly routes = new RouteRegistry();\n\n public get maps(): RouteMaps {\n return this.routes.maps;\n }\n\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<HandlerConstructor, InteractionMiddlewareConstructor, string[]>;\n\n private loading = false;\n private readonly loadedHandlers: { name: string; from: string }[] = [];\n private readonly loadedMiddlewares: { name: string; from: string }[] = [];\n\n constructor(\n private readonly handlersDir: string,\n private readonly middlewaresDir?: string\n ) {\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n\n this.hmrHandler = new HmrModuleHandler({\n handlersDir,\n ...(middlewaresDir && { middlewaresDir }),\n isHandler: this.isHandler.bind(this),\n isMiddleware: this.isMiddleware.bind(this),\n registerHandler: this.registerHandler.bind(this),\n registerMiddleware: this.registerMiddleware.bind(this),\n unregisterHandler: this.routes.unregister.bind(this.routes),\n unregisterMiddleware: this.unregisterMiddleware.bind(this),\n getArtifacts: this.routes.routesOf.bind(this.routes),\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 this.loadedMiddlewares.length = 0;\n try {\n if (this.middlewaresDir) await this.loadMiddlewares(this.middlewaresDir);\n await this.loadHandlers(this.handlersDir);\n } finally {\n this.loading = false;\n }\n\n this.reportLoad();\n }\n\n private async loadHandlers(dir: string): Promise<void> {\n await traverseDirectory(dir, (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 }\n\n private async loadMiddlewares(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, relativePath, imported) => {\n for (const value of Object.values(imported)) {\n if (!this.isMiddleware(value)) continue;\n this.registerMiddleware(value, relativePath);\n this.hmrHandler?.trackMiddleware(fullPath, value);\n }\n });\n }\n\n private reportLoad(): void {\n const { utils } = this.logger;\n\n if (this.loadedMiddlewares.length > 0) {\n utils.block('Loaded middlewares', utils.entries(this.loadedMiddlewares), 'debug');\n }\n\n utils.block(\n 'Loaded handlers',\n [...utils.entries(this.loadedHandlers), ...utils.counts({ routes: this.routes.size })],\n 'debug'\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[InteractionKind.Slash], 'Slash route', '@SlashRoute');\n }\n\n public warnUnhandledContextMenuRoutes(leaves: ContextMenuLeaves): void {\n const user = this.maps[InteractionKind.UserContextMenu];\n const message = this.maps[InteractionKind.MessageContextMenu];\n this.warnMissing(leaves.user, user, 'User context menu', '@ContextMenuRoute');\n this.warnMissing(leaves.message, message, '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 ${paint.bold(decorator)} handler.`);\n }\n }\n\n private isHandler(value: unknown): value is HandlerConstructor {\n return isHandlerClass(value) && Reflect.hasMetadata(InteractionMetadataKey, value);\n }\n\n private isMiddleware(value: unknown): value is InteractionMiddlewareConstructor {\n return isMiddlewareClass(value) && Reflect.hasMetadata(InteractionMiddlewareMetadataKey, value);\n }\n\n private registerMiddleware(ctor: InteractionMiddlewareConstructor, relativePath: string): void {\n const metadata = this.middlewares.register(ctor);\n if (!metadata || !this.loading) return;\n\n // the kinds are the only place a dev sees that a middleware is scoped\n const scope = metadata.keys ? `${metadata.priority}, ${metadata.keys.join(', ')}` : metadata.priority;\n this.loadedMiddlewares.push({\n name: `${ctor.name} (${String(scope)})`,\n from: formatFilePath(relativePath)\n });\n }\n\n private unregisterMiddleware(ctor: InteractionMiddlewareConstructor): void {\n this.middlewares.unregister(ctor);\n }\n\n private registerHandler(ctor: HandlerConstructor, relativePath: string): void {\n const from = formatFilePath(relativePath);\n if (!this.routes.register(ctor, from)) return;\n if (this.loading) this.loadedHandlers.push({ name: ctor.name, from });\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 {\n busLoggerOf,\n getDevChannel,\n HmrManager,\n interactionMiddleware,\n MiddlewareRegistry,\n setBotColor\n} from '@seedcord/core/internal';\nimport { CoordinatedShutdown, CoordinatedStartup, Pluggable } from '@seedcord/core/node';\nimport {\n CommandRegistry,\n DRAIN_TASK_TIMEOUT_MS,\n DRAIN_WINDOW_MS,\n drainInFlight,\n ShutdownPhase,\n shutdownOf,\n StartupPhase,\n SubscriberLoader\n} from '@seedcord/core/node/internal';\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { applicationIdFromToken, SeedcordError, validateDiscordToken } from '@seedcord/errors/internal';\nimport { Logger, LoggerChannelRegistry } from '@seedcord/logger';\nimport { installNodeDefaults } from '@seedcord/logger/node';\nimport { MemoryRateLimiter } from '@seedcord/rate-limiter';\nimport { HostAugmentTarget, HostVersion, SeedcordBrand } from '@seedcord/types/internal';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { emptyRouteMaps } from '#src/dispatch/resolve';\nimport { EmojiInjector } from '#src/emojis/EmojiInjector';\nimport { buildEngine } from '#src/engine';\n\nimport { InteractionDispatcher } from './InteractionDispatcher';\nimport { toWebRequest, writeWebResponse } from './webBridge';\nimport { version as packageVersion } from '../version';\n\nimport type { InteractionMiddlewareConstructor } from '#handlers/constructors';\nimport type { HttpConfig } from '#interfaces/Config';\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;\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()`, and runs coordinated shutdown with an in-flight drain. The edge deploy path calls\n * `createSeedcord` from a generated entry.\n */\n// tests/node/seedcord-core.types-test.ts checks this class against Core in place of an implements clause\nexport class Seedcord<Cfg extends HttpConfig = HttpConfig>\n extends Pluggable<'http', RuntimeOfConfig<Cfg>>\n implements 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 [HostAugmentTarget] = '@seedcord/http';\n /** @internal */\n public readonly [HostVersion]: 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 readonly emojiInjector = new EmojiInjector(this);\n private readonly hmrManager: HmrManager;\n private readonly logger = new Logger('Server', { channel: 'bot' });\n\n private token?: string;\n private server?: Server;\n private boundPort?: number;\n private fetchedUsername?: string | undefined;\n\n constructor(public readonly config: Cfg) {\n super(new CoordinatedShutdown(config.lifecycle?.shutdownDeadline), new CoordinatedStartup());\n\n installNodeDefaults(config.logger);\n setBotColor(config.botColor);\n\n this.hmrManager = new HmrManager();\n this.hmrManager.init();\n\n const interactions = this.config.bot.interactions;\n if (interactions.path) {\n this.interactions = new InteractionDispatcher(interactions.path, interactions.middlewares);\n }\n\n if (this.config.bot.commands.path) this.commandRegistry = new CommandRegistry(this);\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\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 public async start(): Promise<this> {\n try {\n await super.init();\n } catch (caught) {\n await shutdownOf(this).run(1, false);\n Seedcord.reset(this);\n throw caught;\n }\n return this;\n }\n\n protected static override reset(host?: object): boolean {\n if (!super.reset(host)) return false;\n // super.reset() drops the dev TUI's log sink\n LoggerChannelRegistry.instance.configure({});\n return true;\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 busLoggerOf(this.bus).utils.initialization('Subscribers', 'start');\n await this.subscribers.init();\n busLoggerOf(this.bus).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 in a phase run concurrently and the deploy reads the id\n this.startup.addTask(StartupPhase.Login, 'command-deploy', async () => {\n await commandRegistry.init();\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\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.token = validateDiscordToken(Envapter.get('DISCORD_BOT_TOKEN'));\n this.rest.setToken(this.token);\n }\n\n /** The bot's Discord application id. Throws if you read it before the Configuration phase. */\n public get applicationId(): string {\n if (!this.token) throw new SeedcordError(SeedcordErrorCode.CoreApplicationUnavailable);\n return applicationIdFromToken(this.token);\n }\n\n private async listen(): Promise<void> {\n const maps = this.interactions?.maps ?? emptyRouteMaps();\n const middlewares =\n this.interactions?.middlewares ??\n new MiddlewareRegistry<InteractionMiddlewareConstructor>(interactionMiddleware);\n const { handle, inFlight } = buildEngine(this, maps, middlewares);\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 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.config.port ?? DEFAULT_PORT);\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 getDevChannel()?.send('seedcord:server-listening', { port: 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 () => drainInFlight(inFlight, DRAIN_WINDOW_MS, this.logger, 'Interactions'),\n DRAIN_TASK_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 if (me.username) this.logger.info(`Running as ${paint.sky.bold(me.username)}`);\n } catch (caught) {\n // a bad token errors on the first real send anyway\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 // node's close() leaves idle keep-alive sockets open\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' };\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 = { transport: 'http' }> extends CorePlugin<Opts, Core> {}\n\nexport type { PluginLifecycleSpec, PluginOptions } from '@seedcord/core/plugin';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["packageVersion","CorePlugin"],"sources":["../src/node/InteractionDispatcher.ts","../src/node/webBridge.ts","../src/node/Seedcord.ts","../src/Plugin.ts"],"sourcesContent":["import { InteractionKind } from '@seedcord/core';\nimport { HmrModuleHandler } from '@seedcord/core/hmr';\nimport {\n interactionMiddleware,\n InteractionMetadataKey,\n InteractionMiddlewareMetadataKey,\n MiddlewareRegistry\n} from '@seedcord/core/internal';\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { formatFilePath } from '@seedcord/utils';\nimport { traverseDirectory } from '@seedcord/utils/node';\nimport { Envapter } from 'envapt';\n\nimport { RouteRegistry } from '#src/dispatch/RouteRegistry';\nimport { isHandlerClass, isMiddlewareClass } from '#src/manifest/entries';\n\nimport type { HandlerConstructor, InteractionMiddlewareConstructor } from '#handlers/constructors';\nimport type { RouteMap, RouteMaps } from '#src/dispatch/resolve';\nimport type { Initializeable, ContextMenuLeaves } from '@seedcord/core/internal';\nimport type { HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\n// hmr swaps entries in these maps while resolve() reads them per request\nexport class InteractionDispatcher implements Initializeable, HmrAware {\n /** @internal */\n public readonly middlewares = new MiddlewareRegistry<InteractionMiddlewareConstructor>(interactionMiddleware);\n\n /** @internal */\n public readonly logger = new Logger('Interactions', { channel: 'interactions' });\n\n private readonly routes = new RouteRegistry();\n\n public get maps(): RouteMaps {\n return this.routes.maps;\n }\n\n private isInitialized = false;\n private readonly hmrHandler?: HmrModuleHandler<HandlerConstructor, InteractionMiddlewareConstructor, string[]>;\n\n private loading = false;\n private readonly loadedHandlers: { name: string; from: string }[] = [];\n private readonly loadedMiddlewares: { name: string; from: string }[] = [];\n\n constructor(\n private readonly handlersDir: string,\n private readonly middlewaresDir?: string\n ) {\n if (!Envapter.isDevelopment && !Envapter.isTest) return;\n\n this.hmrHandler = new HmrModuleHandler({\n handlersDir,\n ...(middlewaresDir && { middlewaresDir }),\n isHandler: this.isHandler.bind(this),\n isMiddleware: this.isMiddleware.bind(this),\n registerHandler: this.registerHandler.bind(this),\n registerMiddleware: this.registerMiddleware.bind(this),\n unregisterHandler: this.routes.unregister.bind(this.routes),\n unregisterMiddleware: this.unregisterMiddleware.bind(this),\n getArtifacts: this.routes.routesOf.bind(this.routes),\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 this.loadedMiddlewares.length = 0;\n try {\n if (this.middlewaresDir) await this.loadMiddlewares(this.middlewaresDir);\n await this.loadHandlers(this.handlersDir);\n } finally {\n this.loading = false;\n }\n\n this.reportLoad();\n }\n\n private async loadHandlers(dir: string): Promise<void> {\n await traverseDirectory(dir, (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 }\n\n private async loadMiddlewares(dir: string): Promise<void> {\n await traverseDirectory(dir, (fullPath, relativePath, imported) => {\n for (const value of Object.values(imported)) {\n if (!this.isMiddleware(value)) continue;\n this.registerMiddleware(value, relativePath);\n this.hmrHandler?.trackMiddleware(fullPath, value);\n }\n });\n }\n\n private reportLoad(): void {\n const { utils } = this.logger;\n\n if (this.loadedMiddlewares.length > 0) {\n utils.block('Loaded middlewares', utils.entries(this.loadedMiddlewares), 'debug');\n }\n\n utils.block(\n 'Loaded handlers',\n [...utils.entries(this.loadedHandlers), ...utils.counts({ routes: this.routes.size })],\n 'debug'\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[InteractionKind.Slash], 'Slash route', '@SlashRoute');\n }\n\n public warnUnhandledContextMenuRoutes(leaves: ContextMenuLeaves): void {\n const user = this.maps[InteractionKind.UserContextMenu];\n const message = this.maps[InteractionKind.MessageContextMenu];\n this.warnMissing(leaves.user, user, 'User context menu', '@ContextMenuRoute');\n this.warnMissing(leaves.message, message, '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 ${paint.bold(decorator)} handler.`);\n }\n }\n\n private isHandler(value: unknown): value is HandlerConstructor {\n return isHandlerClass(value) && Reflect.hasMetadata(InteractionMetadataKey, value);\n }\n\n private isMiddleware(value: unknown): value is InteractionMiddlewareConstructor {\n return isMiddlewareClass(value) && Reflect.hasMetadata(InteractionMiddlewareMetadataKey, value);\n }\n\n private registerMiddleware(ctor: InteractionMiddlewareConstructor, relativePath: string): void {\n const metadata = this.middlewares.register(ctor);\n if (!metadata || !this.loading) return;\n\n // the kinds are the only place a dev sees that a middleware is scoped\n const scope = metadata.keys ? `${metadata.priority}, ${metadata.keys.join(', ')}` : metadata.priority;\n this.loadedMiddlewares.push({\n name: `${ctor.name} (${String(scope)})`,\n from: formatFilePath(relativePath)\n });\n }\n\n private unregisterMiddleware(ctor: InteractionMiddlewareConstructor): void {\n this.middlewares.unregister(ctor);\n }\n\n private registerHandler(ctor: HandlerConstructor, relativePath: string): void {\n const from = formatFilePath(relativePath);\n if (!this.routes.register(ctor, from)) return;\n if (this.loading) this.loadedHandlers.push({ name: ctor.name, from });\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 {\n bindBotColor,\n busLoggerOf,\n getDevChannel,\n HmrManager,\n interactionMiddleware,\n MiddlewareRegistry\n} from '@seedcord/core/internal';\nimport { CoordinatedShutdown, CoordinatedStartup, Pluggable } from '@seedcord/core/node';\nimport {\n CommandRegistry,\n DRAIN_TASK_TIMEOUT_MS,\n DRAIN_WINDOW_MS,\n drainInFlight,\n ShutdownPhase,\n shutdownOf,\n StartupPhase,\n SubscriberLoader\n} from '@seedcord/core/node/internal';\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { applicationIdFromToken, SeedcordError, validateDiscordToken } from '@seedcord/errors/internal';\nimport { Logger, LoggerChannelRegistry } from '@seedcord/logger';\nimport { installNodeDefaults } from '@seedcord/logger/node';\nimport { MemoryRateLimiter } from '@seedcord/rate-limiter';\nimport { HostAugmentTarget, HostVersion, SeedcordBrand } from '@seedcord/types/internal';\nimport { Routes } from 'discord-api-types/v10';\nimport { Envapter } from 'envapt';\n\nimport { emptyRouteMaps } from '#src/dispatch/resolve';\nimport { EmojiInjector } from '#src/emojis/EmojiInjector';\nimport { buildEngine } from '#src/engine';\nimport { version as packageVersion } from '#src/version';\n\nimport { InteractionDispatcher } from './InteractionDispatcher';\nimport { toWebRequest, writeWebResponse } from './webBridge';\n\nimport type { InteractionMiddlewareConstructor } from '#handlers/constructors';\nimport type { HttpConfig } from '#interfaces/Config';\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;\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()`, and runs coordinated shutdown with an in-flight drain. The edge deploy path calls\n * `createSeedcord` from a generated entry.\n */\n// tests/node/seedcord-core.types-test.ts checks this class against Core in place of an implements clause\nexport class Seedcord<Cfg extends HttpConfig = HttpConfig>\n extends Pluggable<'http', RuntimeOfConfig<Cfg>>\n implements 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 [HostAugmentTarget] = '@seedcord/http';\n /** @internal */\n public readonly [HostVersion]: string = packageVersion;\n\n /** Discord REST client built from `bot.restOptions`. `start()` sets the token. */\n public readonly rest: 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 readonly emojiInjector = new EmojiInjector(this);\n private readonly hmrManager: HmrManager;\n private readonly logger = new Logger('Server', { channel: 'bot' });\n\n private token?: string;\n private server?: Server;\n private boundPort?: number;\n private fetchedUsername?: string | undefined;\n\n public readonly config: HttpConfig;\n\n constructor(config: Cfg) {\n super(new CoordinatedShutdown(config.lifecycle?.shutdownDeadline), new CoordinatedStartup());\n this.config = config;\n this.rest = new REST(config.bot.restOptions);\n\n installNodeDefaults(config.logger);\n bindBotColor(() => this.config.botColor);\n\n this.hmrManager = new HmrManager();\n this.hmrManager.init();\n\n const interactions = this.config.bot.interactions;\n if (interactions.path) {\n this.interactions = new InteractionDispatcher(interactions.path, interactions.middlewares);\n }\n\n if (this.config.bot.commands.path) this.commandRegistry = new CommandRegistry(this);\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\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 public async start(): Promise<this> {\n try {\n await super.init();\n } catch (caught) {\n await shutdownOf(this).run(1, false);\n Seedcord.reset(this);\n throw caught;\n }\n return this;\n }\n\n protected static override reset(host?: object): boolean {\n if (!super.reset(host)) return false;\n // super.reset() drops the dev TUI's log sink\n LoggerChannelRegistry.instance.configure({});\n return true;\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 busLoggerOf(this.bus).utils.initialization('Subscribers', 'start');\n await this.subscribers.init();\n busLoggerOf(this.bus).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 in a phase run concurrently and the deploy reads the id\n this.startup.addTask(StartupPhase.Login, 'command-deploy', async () => {\n await commandRegistry.init();\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\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.token = validateDiscordToken(Envapter.get('DISCORD_BOT_TOKEN'));\n this.rest.setToken(this.token);\n }\n\n /** The bot's Discord application id. Throws if you read it before the Configuration phase. */\n public get applicationId(): string {\n if (!this.token) throw new SeedcordError(SeedcordErrorCode.CoreApplicationUnavailable);\n return applicationIdFromToken(this.token);\n }\n\n private async listen(): Promise<void> {\n const maps = this.interactions?.maps ?? emptyRouteMaps();\n const middlewares =\n this.interactions?.middlewares ??\n new MiddlewareRegistry<InteractionMiddlewareConstructor>(interactionMiddleware);\n const { handle, inFlight } = buildEngine(this, maps, middlewares);\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 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.config.port ?? DEFAULT_PORT);\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 getDevChannel()?.send('seedcord:server-listening', { port: 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 () => drainInFlight(inFlight, DRAIN_WINDOW_MS, this.logger, 'Interactions'),\n DRAIN_TASK_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 if (me.username) this.logger.info(`Running as ${paint.sky.bold(me.username)}`);\n } catch (caught) {\n // a bad token errors on the first real send anyway\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 // node's close() leaves idle keep-alive sockets open\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' };\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 = { transport: 'http' }> extends CorePlugin<Opts, Core> {}\n\nexport type { PluginLifecycleSpec, PluginOptions } from '@seedcord/core/plugin';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,wBAAb,MAAuE;CAqB9C;CACA;;CApBrB,AAAgB,cAAc,IAAI,mBAAqD,qBAAqB;;CAG5G,AAAgB,SAAS,IAAI,OAAO,gBAAgB,EAAE,SAAS,eAAe,CAAC;CAE/E,AAAiB,SAAS,IAAI,cAAc;CAE5C,IAAW,OAAkB;EACzB,OAAO,KAAK,OAAO;CACvB;CAEA,AAAQ,gBAAgB;CACxB,AAAiB;CAEjB,AAAQ,UAAU;CAClB,AAAiB,iBAAmD,CAAC;CACrE,AAAiB,oBAAsD,CAAC;CAExE,YACI,AAAiB,aACjB,AAAiB,gBACnB;EAFmB;EACA;EAEjB,IAAI,CAAC,SAAS,iBAAiB,CAAC,SAAS,QAAQ;EAEjD,KAAK,aAAa,IAAI,iBAAiB;GACnC;GACA,GAAI,kBAAkB,EAAE,eAAe;GACvC,WAAW,KAAK,UAAU,KAAK,IAAI;GACnC,cAAc,KAAK,aAAa,KAAK,IAAI;GACzC,iBAAiB,KAAK,gBAAgB,KAAK,IAAI;GAC/C,oBAAoB,KAAK,mBAAmB,KAAK,IAAI;GACrD,mBAAmB,KAAK,OAAO,WAAW,KAAK,KAAK,MAAM;GAC1D,sBAAsB,KAAK,qBAAqB,KAAK,IAAI;GACzD,cAAc,KAAK,OAAO,SAAS,KAAK,KAAK,MAAM;GACnD,QAAQ,KAAK;EACjB,CAAC;CACL;;CAGA,MAAa,OAAsB;EAC/B,IAAI,KAAK,eAAe;EACxB,KAAK,gBAAgB;EAErB,KAAK,UAAU;EACf,KAAK,eAAe,SAAS;EAC7B,KAAK,kBAAkB,SAAS;EAChC,IAAI;GACA,IAAI,KAAK,gBAAgB,MAAM,KAAK,gBAAgB,KAAK,cAAc;GACvE,MAAM,KAAK,aAAa,KAAK,WAAW;EAC5C,UAAU;GACN,KAAK,UAAU;EACnB;EAEA,KAAK,WAAW;CACpB;CAEA,MAAc,aAAa,KAA4B;EACnD,MAAM,kBAAkB,MAAM,UAAU,cAAc,aAAa;GAC/D,KAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,GAAG;IACzC,IAAI,CAAC,KAAK,UAAU,KAAK,GAAG;IAC5B,KAAK,gBAAgB,OAAO,YAAY;IACxC,KAAK,YAAY,aAAa,UAAU,KAAK;GACjD;EACJ,CAAC;CACL;CAEA,MAAc,gBAAgB,KAA4B;EACtD,MAAM,kBAAkB,MAAM,UAAU,cAAc,aAAa;GAC/D,KAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,GAAG;IACzC,IAAI,CAAC,KAAK,aAAa,KAAK,GAAG;IAC/B,KAAK,mBAAmB,OAAO,YAAY;IAC3C,KAAK,YAAY,gBAAgB,UAAU,KAAK;GACpD;EACJ,CAAC;CACL;CAEA,AAAQ,aAAmB;EACvB,MAAM,EAAE,UAAU,KAAK;EAEvB,IAAI,KAAK,kBAAkB,SAAS,GAChC,MAAM,MAAM,sBAAsB,MAAM,QAAQ,KAAK,iBAAiB,GAAG,OAAO;EAGpF,MAAM,MACF,mBACA,CAAC,GAAG,MAAM,QAAQ,KAAK,cAAc,GAAG,GAAG,MAAM,OAAO,EAAE,QAAQ,KAAK,OAAO,KAAK,CAAC,CAAC,GACrF,OACJ;CACJ;;CAGA,MAAa,MAAM,OAAsC;EACrD,MAAM,KAAK,YAAY,OAAO,KAAK;CACvC;CAEA,AAAO,oBAAoB,eAAuC;EAC9D,KAAK,YAAY,eAAe,KAAK,KAAK,gBAAgB,QAAQ,eAAe,aAAa;CAClG;CAEA,AAAO,+BAA+B,QAAiC;EACnE,MAAM,OAAO,KAAK,KAAK,gBAAgB;EACvC,MAAM,UAAU,KAAK,KAAK,gBAAgB;EAC1C,KAAK,YAAY,OAAO,MAAM,MAAM,qBAAqB,mBAAmB;EAC5E,KAAK,YAAY,OAAO,SAAS,SAAS,wBAAwB,mBAAmB;CACzF;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,OAAO,eAAe,KAAK,KAAK,QAAQ,YAAY,wBAAwB,KAAK;CACrF;CAEA,AAAQ,aAAa,OAA2D;EAC5E,OAAO,kBAAkB,KAAK,KAAK,QAAQ,YAAY,kCAAkC,KAAK;CAClG;CAEA,AAAQ,mBAAmB,MAAwC,cAA4B;EAC3F,MAAM,WAAW,KAAK,YAAY,SAAS,IAAI;EAC/C,IAAI,CAAC,YAAY,CAAC,KAAK,SAAS;EAGhC,MAAM,QAAQ,SAAS,OAAO,GAAG,SAAS,SAAS,IAAI,SAAS,KAAK,KAAK,IAAI,MAAM,SAAS;EAC7F,KAAK,kBAAkB,KAAK;GACxB,MAAM,GAAG,KAAK,KAAK,IAAI,OAAO,KAAK,EAAE;GACrC,MAAM,eAAe,YAAY;EACrC,CAAC;CACL;CAEA,AAAQ,qBAAqB,MAA8C;EACvE,KAAK,YAAY,WAAW,IAAI;CACpC;CAEA,AAAQ,gBAAgB,MAA0B,cAA4B;EAC1E,MAAM,OAAO,eAAe,YAAY;EACxC,IAAI,CAAC,KAAK,OAAO,SAAS,MAAM,IAAI,GAAG;EACvC,IAAI,KAAK,SAAS,KAAK,eAAe,KAAK;GAAE,MAAM,KAAK;GAAM;EAAK,CAAC;CACxE;AACJ;;;;ACrKA,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;;;;ACSA,MAAM,eAAe;AACrB,MAAM,6BAA6B;;;;;;;;AAYnC,IAAa,WAAb,MAAa,iBACD,UAEZ;;CAGI,CAAiB,iBAAiB;;CAElC,CAAiB,qBAAqB;;CAEtC,CAAiB,eAAuBA;;CAGxC,AAAgB;;CAGhB,AAAgB;;CAGhB,AAAgB;CAEhB,AAAiB;CAEjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB,gBAAgB,IAAI,cAAc,IAAI;CACvD,AAAiB;CACjB,AAAiB,SAAS,IAAI,OAAO,UAAU,EAAE,SAAS,MAAM,CAAC;CAEjE,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,AAAgB;CAEhB,YAAY,QAAa;EACrB,MAAM,IAAI,oBAAoB,OAAO,WAAW,gBAAgB,GAAG,IAAI,mBAAmB,CAAC;EAC3F,KAAK,SAAS;EACd,KAAK,OAAO,IAAI,KAAK,OAAO,IAAI,WAAW;EAE3C,oBAAoB,OAAO,MAAM;EACjC,mBAAmB,KAAK,OAAO,QAAQ;EAEvC,KAAK,aAAa,IAAI,WAAW;EACjC,KAAK,WAAW,KAAK;EAErB,MAAM,eAAe,KAAK,OAAO,IAAI;EACrC,IAAI,aAAa,MACb,KAAK,eAAe,IAAI,sBAAsB,aAAa,MAAM,aAAa,WAAW;EAG7F,IAAI,KAAK,OAAO,IAAI,SAAS,MAAM,KAAK,kBAAkB,IAAI,gBAAgB,IAAI;EAElF,KAAK,cAAc,OAAO,SAAS,IAAI,kBAAkB;EACzD,KAAK,MAAM,IAAI,IAAI,IAAI;EACvB,KAAK,cAAc,IAAI,iBAAiB,KAAK,KAAK,OAAO,YAAY,IAAI;EAEzE,KAAK,qBAAqB;CAC9B;;CAGA,IAAW,WAA+B;EACtC,OAAO,KAAK;CAChB;;CAGA,IAAW,OAA2B;EAClC,OAAO,KAAK;CAChB;;;;CAKA,MAAa,QAAuB;EAChC,IAAI;GACA,MAAM,MAAM,KAAK;EACrB,SAAS,QAAQ;GACb,MAAM,WAAW,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK;GACnC,SAAS,MAAM,IAAI;GACnB,MAAM;EACV;EACA,OAAO;CACX;CAEA,OAA0B,MAAM,MAAwB;EACpD,IAAI,CAAC,MAAM,MAAM,IAAI,GAAG,OAAO;EAE/B,sBAAsB,SAAS,UAAU,CAAC,CAAC;EAC3C,OAAO;CACX;CAEA,AAAQ,uBAA6B;EACjC,IAAI,SAAS,iBAAiB,SAAS,QAAQ,KAAK,wBAAwB;EAE5E,KAAK,QAAQ,QAAQ,aAAa,eAAe,sBAAsB,YAAY;GAC/E,YAAY,KAAK,GAAG,CAAC,CAAC,MAAM,eAAe,eAAe,OAAO;GACjE,MAAM,KAAK,YAAY,KAAK;GAC5B,YAAY,KAAK,GAAG,CAAC,CAAC,MAAM,eAAe,eAAe,KAAK;EACnE,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,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;CAEvF;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,QAAQ,qBAAqB,SAAS,IAAI,mBAAmB,CAAC;EACnE,KAAK,KAAK,SAAS,KAAK,KAAK;CACjC;;CAGA,IAAW,gBAAwB;EAC/B,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,cAAc,kBAAkB,0BAA0B;EACrF,OAAO,uBAAuB,KAAK,KAAK;CAC5C;CAEA,MAAc,SAAwB;EAClC,MAAM,OAAO,KAAK,cAAc,QAAQ,eAAe;EACvD,MAAM,cACF,KAAK,cAAc,eACnB,IAAI,mBAAqD,qBAAqB;EAClF,MAAM,EAAE,QAAQ,aAAa,YAAY,MAAM,MAAM,WAAW;EAEhE,MAAM,SAAS,cAAc,UAAU,aAAa;GAChD,CAAM,YAAY;IACd,MAAM,WAAW,MAAM,OAAO,MAAM,aAAa,QAAQ,CAAC;IAC1D,MAAM,iBAAiB,UAAU,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,OAAO,QAAQ,YAAY;EAC9C,MAAM,KAAK,QAAQ,WAAW;EAE9B,KAAK,YAAa,OAAO,QAAQ,CAAC,CAAiB;EACnD,KAAK,OAAO,KAAK,yCAAyC,MAAM,IAAI,KAAK,OAAO,KAAK,SAAS,CAAC,GAAG;EAClG,cAAc,CAAC,EAAE,KAAK,6BAA6B,EAAE,MAAM,KAAK,UAAU,CAAC;EAE3E,KAAK,SAAS,QACV,cAAc,QACd,0BACM,KAAK,WAAW,GACtB,0BACJ;EAEA,KAAK,SAAS,QACV,cAAc,OACd,wBACM,cAAc,UAAU,iBAAiB,KAAK,QAAQ,cAAc,GAC1E,qBACJ;CACJ;CAEA,MAAc,gBAA+B;EACzC,IAAI;GAEA,MAAM,KAAM,MAAM,KAAK,KAAK,IAAI,OAAO,KAAK,KAAK,CAAC;GAClD,KAAK,kBAAkB,GAAG;GAC1B,IAAI,GAAG,UAAU,KAAK,OAAO,KAAK,cAAc,MAAM,IAAI,KAAK,GAAG,QAAQ,GAAG;EACjF,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;GAED,OAAO,qBAAqB;EAChC,CAAC;CACL;AACJ;;;;;;;;;;;;AChRA,IAAsB,SAAtB,cAA6FC,SAAuB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { t as Manifest } from "./Manifest-D61MNdcV.mjs";
1
+ import { t as Manifest } from "./Manifest-D5MEsTmH.mjs";
2
2
  //#region src/manifest.index.d.ts
3
3
  /**
4
4
  * The generated class list. `seedcord build` aliases this module to the emitted file. Reading a class
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@seedcord/http",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.10.2",
5
5
  "description": "HTTP interactions transport for seedcord bots",
6
6
  "keywords": [
7
7
  "discord",
@@ -12,7 +12,8 @@
12
12
  "typescript"
13
13
  ],
14
14
  "engines": {
15
- "node": ">=24.11"
15
+ "node": ">=24.11",
16
+ "bun": ">=1.4.2"
16
17
  },
17
18
  "repository": {
18
19
  "type": "git",
@@ -55,14 +56,14 @@
55
56
  "@discordjs/collection": "^2.1.1",
56
57
  "@discordjs/core": "^2.6.0",
57
58
  "@discordjs/rest": "^2.6.3",
58
- "@seedcord/core": "^0.9.3",
59
- "@seedcord/custom-id": "^0.2.6",
60
- "@seedcord/errors": "^0.12.0",
61
- "@seedcord/event-emitter": "^0.1.7",
62
- "@seedcord/logger": "^0.4.3",
63
- "@seedcord/rate-limiter": "^0.1.11",
64
- "@seedcord/types": "^0.14.2",
65
- "@seedcord/utils": "^0.8.16",
59
+ "@seedcord/core": "^0.10.0",
60
+ "@seedcord/custom-id": "^0.2.7",
61
+ "@seedcord/errors": "^0.13.0",
62
+ "@seedcord/event-emitter": "^0.1.8",
63
+ "@seedcord/logger": "^0.4.4",
64
+ "@seedcord/rate-limiter": "^0.1.12",
65
+ "@seedcord/types": "^0.14.4",
66
+ "@seedcord/utils": "^0.8.17",
66
67
  "discord-api-types": "^0.38.52",
67
68
  "reflect-metadata": "^0.2.2",
68
69
  "type-fest": "^5.8.0"
@@ -74,8 +75,8 @@
74
75
  "devDependencies": {
75
76
  "@cloudflare/vitest-pool-workers": "^0.22.0",
76
77
  "@discordjs/builders": "^1.14.1",
77
- "@seedcord/eslint-config": "^2.3.0",
78
- "@seedcord/tsconfig": "^2.0.5",
78
+ "@seedcord/eslint-config": "^2.5.0",
79
+ "@seedcord/tsconfig": "^2.0.6",
79
80
  "@seedcord/tsdown-config": "^2.0.1",
80
81
  "@seedcord/vitest-config": "^0.0.1",
81
82
  "@vitest/coverage-istanbul": "^4.1.11",
@@ -99,5 +100,5 @@
99
100
  "test:watch": "vitest dev",
100
101
  "coverage": "vitest run --coverage"
101
102
  },
102
- "readme": "<div align=\"center\">\n <picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://cdn.seedcord.org/assets/wordmark-dark.webp\" />\n <img src=\"https://cdn.seedcord.org/assets/wordmark-light.webp\" alt=\"seedcord\" width=\"440\" />\n </picture>\n</div>\n\n<div align=\"center\">\n <h3>The whole Discord bot, typed end to end</h3>\n <a href=\"https://seedcord.org\">Website</a> ·\n <a href=\"https://seedcord.org/guide\">Guide</a> ·\n <a href=\"https://seedcord.org/docs\">Reference</a> ·\n <a href=\"https://discord.gg/DzFxY58WXf\">Discord</a>\n</div>\n\n<br />\n\n<div align=\"center\">\n\n[![npm](https://img.shields.io/npm/v/@seedcord/http?style=flat-square&logo=npm&logoColor=c8341f&label=&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/@seedcord/http) [![node](https://img.shields.io/node/v/@seedcord/http?style=flat-square&label=node&labelColor=1f1f1f&color=4d7d33)](https://nodejs.org) [![license](https://img.shields.io/npm/l/@seedcord/http?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)\n\n</div>\n\n## About\n\n`@seedcord/http` runs a seedcord bot on Discord's interactions endpoint. Discord posts each interaction to your URL. The framework verifies the Ed25519 signature before dispatching it.\n\nDiscord posts only interactions over this transport. Use [`@seedcord/gateway`](https://www.npmjs.com/package/@seedcord/gateway) when the bot reacts to messages, members, or voice.\n\nUntil v1.0.0, minor versions can break.\n\n## Installation\n\n```sh\npnpm add @seedcord/http\n```\n\n`pnpm create seedcord` scaffolds the whole project. On this transport `pnpm dev` also opens a cloudflared tunnel and registers the URL with Discord.\n\n## Entry points\n\nThe root entry runs on Node through the `Seedcord` class.\n\n`@seedcord/http/edge` targets Web-standard runtimes through `createSeedcord`. The Node entry re-exports all of it. The edge build for Cloudflare Workers is a work in progress and cannot be used yet.\n"
103
+ "readme": "<div align=\"center\">\n <picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://cdn.seedcord.org/assets/wordmark-dark.webp\" />\n <img src=\"https://cdn.seedcord.org/assets/wordmark-light.webp\" alt=\"seedcord\" width=\"440\" />\n </picture>\n</div>\n\n<div align=\"center\">\n <h3>The whole Discord bot, typed end to end</h3>\n <a href=\"https://seedcord.org\">Website</a> ·\n <a href=\"https://seedcord.org/guide\">Guide</a> ·\n <a href=\"https://seedcord.org/docs\">Reference</a> ·\n <a href=\"https://discord.gg/DzFxY58WXf\">Discord</a>\n</div>\n\n<br />\n\n<div align=\"center\">\n\n[![npm](https://img.shields.io/npm/v/@seedcord/http?style=flat-square&label=npm&labelColor=1f1f1f&color=c8341f)](https://www.npmjs.com/package/@seedcord/http) [![node](https://img.shields.io/node/v/@seedcord/http?style=flat-square&label=node&labelColor=1f1f1f&color=4d7d33)](https://nodejs.org) [![bun](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fseedcord%2Fseedcord%2Fnext%2Fpackages%2Fhttp%2Fpackage.json&query=%24.engines.bun&style=flat-square&label=bun&labelColor=1f1f1f&color=fbf0df)](https://bun.com) [![license](https://img.shields.io/npm/l/@seedcord/http?style=flat-square&label=license&labelColor=1f1f1f&color=f8f6e8)](LICENSE)\n\n</div>\n\n## About\n\n`@seedcord/http` runs a seedcord bot on Discord's interactions endpoint. Discord posts each interaction to your URL. The framework verifies the Ed25519 signature before dispatching it.\n\nDiscord posts only interactions over this transport. Use [`@seedcord/gateway`](https://www.npmjs.com/package/@seedcord/gateway) when the bot reacts to messages, members, or voice.\n\nUntil v1.0.0, minor versions can break.\n\n## Installation\n\n```sh\npnpm add @seedcord/http\n```\n\n`pnpm create seedcord` scaffolds the whole project. On this transport `pnpm dev` also opens a cloudflared tunnel and registers the URL with Discord.\n\n## Entry points\n\nThe root entry runs on Node through the `Seedcord` class.\n\n`@seedcord/http/edge` targets Web-standard runtimes through `createSeedcord`. The Node entry re-exports all of it. The edge build for Cloudflare Workers is a work in progress and cannot be used yet.\n"
103
104
  }