@nectar-js/nectar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/cli-Ce-ZUj6M.js +2225 -0
- package/dist/cli-Ce-ZUj6M.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/index-DGMxBsub.d.ts +757 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins-CGvM19v9.js +663 -0
- package/dist/plugins-CGvM19v9.js.map +1 -0
- package/dist/registration-CaE0QBT6.js +545 -0
- package/dist/registration-CaE0QBT6.js.map +1 -0
- package/dist/runtime-CZJeZvSL.js +891 -0
- package/dist/runtime-CZJeZvSL.js.map +1 -0
- package/dist/start.d.ts +8 -0
- package/dist/start.js +15 -0
- package/dist/start.js.map +1 -0
- package/dist/testing.d.ts +111 -0
- package/dist/testing.js +320 -0
- package/dist/testing.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-CZJeZvSL.js","names":["describe"],"sources":["../src/components/matcher.ts","../src/manifest/load.ts","../src/runtime/signals.ts","../src/runtime/errors.ts","../src/runtime/middleware.ts","../src/runtime/modules.ts","../src/runtime/state.ts","../src/runtime/dispatch.ts","../src/runtime/events.ts","../src/runtime/logger.ts","../src/runtime/runtime.ts"],"sourcesContent":["import type { ComponentKind } from \"./compile.js\";\nimport { decodeCustomId } from \"./customId.js\";\n\n/** What the matcher needs from a route. Both compiled and manifest routes satisfy it. */\nexport interface MatchableRoute {\n kind: ComponentKind;\n shortId: string;\n params: string[];\n catchAll: string | null;\n}\n\nexport type MatchResult<Route extends MatchableRoute = MatchableRoute> =\n | { ok: true; route: Route; params: Record<string, string | string[]> }\n /** The custom ID is not Nectar's. Hand-built components should be left alone. */\n | { ok: false; reason: \"not-nectar\" }\n /** The ID carries the Nectar prefix but cannot be decoded, names no route, or has the wrong number of values. */\n | { ok: false; reason: \"malformed\" | \"unknown-route\" | \"param-count\" };\n\nexport interface ComponentMatcher<Route extends MatchableRoute = MatchableRoute> {\n match(kind: ComponentKind, customId: string): MatchResult<Route>;\n}\n\n/**\n * Resolves incoming custom IDs to routes.\n *\n * Custom IDs carry the route's short ID, so matching is a direct lookup rather than a pattern\n * scan. The interaction kind is part of the key, so a button's ID sent back as a modal\n * submission finds no route.\n */\nexport function createMatcher<Route extends MatchableRoute>(\n routes: readonly Route[],\n): ComponentMatcher<Route> {\n const byId = new Map<string, Route>();\n for (const route of routes) byId.set(`${route.kind}:${route.shortId}`, route);\n\n return {\n match(kind, customId) {\n const decoded = decodeCustomId(customId);\n if (!decoded.ok) return decoded;\n\n const route = byId.get(`${kind}:${decoded.shortId}`);\n if (route === undefined) return { ok: false, reason: \"unknown-route\" };\n\n const fixed = route.catchAll === null ? route.params.length : route.params.length - 1;\n if (\n route.catchAll === null ? decoded.values.length !== fixed : decoded.values.length < fixed\n ) {\n return { ok: false, reason: \"param-count\" };\n }\n\n const params: Record<string, string | string[]> = {};\n for (let i = 0; i < fixed; i++)\n params[route.params[i] as string] = decoded.values[i] as string;\n if (route.catchAll !== null) params[route.catchAll] = decoded.values.slice(fixed);\n return { ok: true, route, params };\n },\n };\n}\n","import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { MANIFEST_VERSION, type Manifest } from \"./schema.js\";\n\nexport interface LoadedManifest {\n manifest: Manifest;\n /** Absolute path of the app directory the manifest was compiled from. */\n appDir: string;\n}\n\nexport class ManifestVersionError extends Error {\n constructor(\n readonly file: string,\n readonly found: unknown,\n ) {\n super(\n `${file} is manifest version ${String(found)}, this build of @nectar-js/nectar reads version ${MANIFEST_VERSION}. Run \\`nectar build\\` again.`,\n );\n this.name = \"ManifestVersionError\";\n }\n}\n\n/** Reads a manifest from disk and checks its version. Does not validate the rest of the shape. */\nexport function loadManifest(file: string): LoadedManifest {\n const absolute = path.resolve(file);\n const parsed: unknown = JSON.parse(readFileSync(absolute, \"utf8\"));\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new TypeError(`${absolute} is not a manifest object.`);\n }\n const manifest = parsed as Partial<Manifest>;\n if (manifest.version !== MANIFEST_VERSION) {\n throw new ManifestVersionError(absolute, manifest.version);\n }\n if (typeof manifest.appDir !== \"string\") {\n throw new TypeError(`${absolute} has no appDir.`);\n }\n return {\n manifest: manifest as Manifest,\n appDir: path.resolve(path.dirname(absolute), manifest.appDir),\n };\n}\n","import type { Interaction } from \"discord.js\";\nimport { ApplicationCommandType } from \"discord-api-types/v10\";\nimport type { Logger, RouteInfo } from \"./types.js\";\n\n/** Structured, non-sensitive description of an interaction for logs and signals. */\nexport interface InteractionMeta {\n type:\n | \"chatInput\"\n | \"userContextMenu\"\n | \"messageContextMenu\"\n | \"autocomplete\"\n | \"button\"\n | \"select\"\n | \"modal\"\n | \"unknown\";\n /** Full command name with subcommand group and subcommand, for commands and autocomplete. */\n command?: string;\n /** Custom ID with Nectar param values replaced by `*`. */\n customId?: string;\n guildId: string | null;\n channelId: string | null;\n userId: string | null;\n}\n\nexport interface RegistrationScopeResult {\n scope: string;\n /** A bulk overwrite was sent. */\n applied: boolean;\n}\n\n/** Everything the runtime reports about itself, without the timestamp. */\nexport type SignalData =\n | { type: \"interaction:start\"; trace: string; interaction: InteractionMeta }\n | { type: \"route:match\"; trace: string; interaction: InteractionMeta; route: RouteInfo }\n | {\n type: \"middleware:enter\";\n trace: string;\n interaction: InteractionMeta;\n route: RouteInfo;\n file: string;\n }\n | { type: \"handler:enter\"; trace: string; interaction: InteractionMeta; route: RouteInfo }\n | {\n type: \"handler:complete\";\n trace: string;\n interaction: InteractionMeta;\n route: RouteInfo;\n /** Milliseconds the handler took. */\n duration: number;\n }\n | {\n type: \"interaction:complete\";\n trace: string;\n interaction: InteractionMeta;\n route: RouteInfo;\n /** Milliseconds since the runtime received the interaction. */\n duration: number;\n /** `false` when a middleware stopped the chain before the handler. */\n handled: boolean;\n }\n | {\n type: \"interaction:fail\";\n trace: string;\n interaction: InteractionMeta;\n route: RouteInfo;\n error: unknown;\n /** The `error.ts` that handled it, or `null` for the default boundary. */\n boundary: string | null;\n }\n | {\n /** The interaction was refused before any application code ran. */\n type: \"interaction:reject\";\n trace: string;\n interaction: InteractionMeta;\n reason: RejectReason;\n /** Known when the route matched but a parameter failed validation. */\n route?: RouteInfo;\n /** The parameter that failed, for `invalid-param`. Its value is never included. */\n param?: string;\n }\n | { type: \"event:fail\"; event: string; route: RouteInfo; error: unknown; boundary: string | null }\n | { type: \"registration:start\"; scopes: string[] }\n | { type: \"registration:complete\"; scopes: RegistrationScopeResult[]; duration: number }\n | { type: \"gateway:connect\"; shard: number; resumed: boolean }\n | { type: \"gateway:disconnect\"; shard: number; code: number }\n | { type: \"shutdown\" };\n\nexport type RejectReason =\n /** A command, autocomplete option, or context menu Discord sent that no route serves. */\n | \"no-route\"\n /** An interaction type Nectar does not route at all. */\n | \"unknown-interaction\"\n /** A Nectar custom ID that cannot be decoded. */\n | \"malformed\"\n /** A decoded custom ID whose short ID names no route of that kind. */\n | \"unknown-route\"\n /** The wrong number of values for the route. */\n | \"param-count\"\n /** A route's own validator refused a value. */\n | \"invalid-param\";\n\nexport type SignalType = SignalData[\"type\"];\n\nexport type Signal = SignalData & {\n /** Epoch milliseconds. */\n at: number;\n};\n\nexport type SignalListener = (signal: Signal) => void;\n\nexport interface SignalEmitter {\n /** Delivers every signal to `listener`. Returns a function that unsubscribes. */\n on(listener: SignalListener): () => void;\n emit(data: SignalData): void;\n}\n\n/**\n * Fan-out for framework signals. Listeners run synchronously in subscription order; one that\n * throws is reported through the logger and does not affect the others or the interaction.\n */\nexport function createSignals(logger: Pick<Logger, \"error\">): SignalEmitter {\n const listeners = new Set<SignalListener>();\n return {\n on(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n emit(data) {\n if (listeners.size === 0) return;\n const signal: Signal = { ...data, at: Date.now() };\n for (const listener of listeners) {\n try {\n listener(signal);\n } catch (error) {\n logger.error(`A signal listener threw on ${data.type}.`, { error });\n }\n }\n },\n };\n}\n\n/** Reads the fields spec 31 asks for off a discord.js interaction, tolerating stubs. */\nexport function interactionMeta(interaction: Interaction): InteractionMeta {\n const i = interaction as unknown as InteractionLike;\n const guard = (name: GuardName) => typeof i[name] === \"function\" && i[name]?.() === true;\n const where = {\n guildId: i.guildId ?? null,\n channelId: i.channelId ?? null,\n userId: i.user?.id ?? null,\n };\n if (guard(\"isChatInputCommand\") || guard(\"isAutocomplete\")) {\n return {\n type: guard(\"isAutocomplete\") ? \"autocomplete\" : \"chatInput\",\n command: [\n i.commandName,\n i.options?.getSubcommandGroup(false) ?? null,\n i.options?.getSubcommand(false) ?? null,\n ]\n .filter((p): p is string => typeof p === \"string\")\n .join(\" \"),\n ...where,\n };\n }\n if (guard(\"isContextMenuCommand\")) {\n return {\n type:\n i.commandType === ApplicationCommandType.Message ? \"messageContextMenu\" : \"userContextMenu\",\n command: i.commandName ?? \"\",\n ...where,\n };\n }\n const kind = guard(\"isButton\")\n ? \"button\"\n : guard(\"isAnySelectMenu\")\n ? \"select\"\n : guard(\"isModalSubmit\")\n ? \"modal\"\n : null;\n if (kind !== null) return { type: kind, customId: redactCustomId(i.customId ?? \"\"), ...where };\n return { type: \"unknown\", ...where };\n}\n\n/**\n * Keeps the route part of a Nectar custom ID and hides the parameter values, which may carry\n * anything the application put there. Other custom IDs are not ours and pass through.\n */\nexport function redactCustomId(customId: string): string {\n if (!customId.startsWith(\"n:\")) return customId;\n const params = customId.indexOf(\":\", 2);\n return params === -1 ? customId : `${customId.slice(0, params)}:*`;\n}\n\ntype GuardName =\n | \"isChatInputCommand\"\n | \"isAutocomplete\"\n | \"isContextMenuCommand\"\n | \"isButton\"\n | \"isAnySelectMenu\"\n | \"isModalSubmit\";\n\ninterface InteractionLike extends Partial<Record<GuardName, () => boolean>> {\n commandName?: string;\n commandType?: number;\n customId?: string;\n guildId?: string | null;\n channelId?: string | null;\n user?: { id: string };\n options?: {\n getSubcommandGroup(required: false): string | null;\n getSubcommand(required: false): string | null;\n };\n}\n","import type { Interaction } from \"discord.js\";\nimport { MessageFlags } from \"discord-api-types/v10\";\nimport type { LogFields } from \"./logger.js\";\nimport type { ModuleRegistry } from \"./modules.js\";\nimport { type InteractionMeta, interactionMeta } from \"./signals.js\";\nimport type { ErrorHandler, EventContext, InteractionContext, Logger } from \"./types.js\";\n\n/** The reply the default boundary sends when an interaction is still unanswered. */\nexport const GENERIC_ERROR_REPLY = \"Something went wrong while handling that.\";\n\n/**\n * Passes an error through the route's boundaries, nearest first, then the default boundary.\n * A boundary handles the error by returning normally. Returning `\"unhandled\"` or throwing\n * hands it (or the newly thrown error) to the next one. Nothing is ever swallowed: the default\n * boundary always logs.\n *\n * `middleware` is the chain that ran before the handler; development output lists it.\n * Resolves to the boundary file that handled the error, or `null` for the default boundary.\n */\nexport async function handleError(\n error: unknown,\n ctx: InteractionContext | EventContext,\n boundaries: readonly string[],\n modules: ModuleRegistry,\n logger: Logger,\n middleware: readonly string[] = [],\n): Promise<string | null> {\n let current = error;\n for (const file of boundaries) {\n try {\n const boundary = await modules.loadDefault<ErrorHandler>(file, \"An error boundary\");\n const result = await boundary(current, ctx);\n if (result !== \"unhandled\") return file;\n } catch (thrown) {\n current = thrown;\n }\n }\n await defaultBoundary(current, ctx, logger, middleware);\n return null;\n}\n\n/** The structured metadata every framework log line about a route carries. */\nexport function logFields(\n ctx: InteractionContext | EventContext,\n meta?: InteractionMeta,\n): LogFields {\n const fields: LogFields = { route: ctx.route.id };\n if (!(\"interaction\" in ctx)) {\n fields.event = ctx.route.path.split(\"/\")[0];\n return fields;\n }\n const i = meta ?? interactionMeta(ctx.interaction);\n fields.trace = ctx.trace.id;\n fields.interaction = i.type;\n if (i.command !== undefined) fields.command = i.command;\n if (i.customId !== undefined) fields.customId = i.customId;\n fields.guild = i.guildId;\n fields.channel = i.channelId;\n fields.user = i.userId;\n return fields;\n}\n\nasync function defaultBoundary(\n error: unknown,\n ctx: InteractionContext | EventContext,\n logger: Logger,\n middleware: readonly string[],\n): Promise<void> {\n logger.error(\n ctx.env === \"development\"\n ? developmentReport(ctx, middleware)\n : `Unhandled error in ${ctx.route.id} (${ctx.route.file})`,\n { ...logFields(ctx), error },\n );\n\n if (!(\"interaction\" in ctx)) return;\n const interaction = ctx.interaction as RepliableLike;\n if (typeof interaction.isRepliable !== \"function\" || !interaction.isRepliable()) return;\n if (interaction.replied || interaction.deferred) return;\n\n try {\n await interaction.reply({ content: GENERIC_ERROR_REPLY, flags: MessageFlags.Ephemeral });\n } catch (replyError) {\n logger.error(`Could not send the error reply for ${ctx.route.id}`, {\n ...logFields(ctx),\n error: replyError,\n });\n }\n}\n\n/** Where the failure sits in the app, so the developer can go straight to the boundary. */\nfunction developmentReport(\n ctx: InteractionContext | EventContext,\n middleware: readonly string[],\n): string {\n const rows: [string, string][] = [[\"file\", ctx.route.file]];\n if (\"interaction\" in ctx) {\n rows.push([\"interaction\", describeInteraction(ctx.interaction)]);\n // Discord gives a handler 3000ms to respond. Far past that means a slow handler or a\n // second process on the same token that answered first.\n rows.push([\"elapsed\", `${ctx.trace.elapsed()}ms since Discord created it`]);\n rows.push([\n \"middleware\",\n middleware.length === 0 ? \"none\" : middleware.join(`\\n${\" \".repeat(15)}`),\n ]);\n }\n const width = Math.max(...rows.map(([key]) => key.length));\n return [\n `Unhandled error in ${ctx.route.id}`,\n ...rows.map(([key, value]) => ` ${key.padEnd(width)} ${value}`),\n ].join(\"\\n\");\n}\n\nfunction describeInteraction(interaction: Interaction): string {\n const meta = interactionMeta(interaction);\n let what: string;\n switch (meta.type) {\n case \"chatInput\":\n what = `/${meta.command}`;\n break;\n case \"autocomplete\":\n what = `autocomplete for /${meta.command}`;\n break;\n case \"userContextMenu\":\n case \"messageContextMenu\":\n what = `context menu \"${meta.command}\"`;\n break;\n case \"unknown\":\n what = \"unknown interaction\";\n break;\n default:\n what = `${meta.type} \"${meta.customId}\"`;\n }\n const where = [\n meta.guildId === null ? \"direct message\" : `guild ${meta.guildId}`,\n meta.channelId === null ? null : `channel ${meta.channelId}`,\n meta.userId === null ? null : `user ${meta.userId}`,\n ].filter((p): p is string => p !== null);\n return `${what} (${where.join(\", \")})`;\n}\n\ninterface RepliableLike {\n isRepliable?: () => boolean;\n replied?: boolean;\n deferred?: boolean;\n reply: (options: { content: string; flags: MessageFlags }) => Promise<unknown>;\n}\n","import type {\n ContextExtension,\n Extended,\n Handler,\n InteractionContext,\n Middleware,\n Next,\n} from \"./types.js\";\n\nexport interface ChainHooks {\n /** Called with the index of each middleware right before it runs. */\n middleware?: (index: number) => void;\n /** Called right before the handler runs. Skipped when a middleware stopped the chain. */\n handler?: () => void;\n}\n\n/**\n * Runs middleware outer to inner, then the handler.\n *\n * `next()` continues unchanged; `next({ member })` puts `member` on every downstream context.\n * Returning without calling `next` stops the chain. Throwing anywhere unwinds to the caller,\n * which hands it to the error boundaries. Code after `await next()` runs after the handler.\n */\nexport async function runChain(\n middleware: readonly Middleware[],\n ctx: InteractionContext,\n handler: Handler,\n hooks: ChainHooks = {},\n): Promise<void> {\n await step(0);\n\n async function step(index: number, current: InteractionContext = ctx): Promise<unknown> {\n const layer = middleware[index];\n if (layer === undefined) {\n hooks.handler?.();\n return handler(current);\n }\n hooks.middleware?.(index);\n\n let called = false;\n const next: Next = <E extends ContextExtension>(extension?: E) => {\n if (called) throw new Error(\"next() was called twice in the same middleware.\");\n called = true;\n const downstream = extension === undefined ? current : { ...current, ...extension };\n return step(index + 1, downstream) as Promise<Extended<E>>;\n };\n return layer(current, next);\n }\n}\n","import { loadModule } from \"../compiler/load.js\";\n\nexport type HandlerModule = Record<string, unknown>;\n\nexport class HandlerLoadError extends Error {\n constructor(\n readonly file: string,\n readonly detail: string,\n ) {\n super(`${file}: ${detail}`);\n this.name = \"HandlerLoadError\";\n }\n}\n\n/**\n * Imports handler modules once and caches them. Files are absolute paths taken from the\n * manifest, so nothing here touches the filesystem beyond `import()`.\n */\nexport class ModuleRegistry {\n private readonly cache = new Map<string, Promise<HandlerModule>>();\n\n load(file: string): Promise<HandlerModule> {\n let pending = this.cache.get(file);\n if (pending === undefined) {\n pending = loadModule(file).catch((error: unknown) => {\n this.cache.delete(file);\n throw new HandlerLoadError(file, error instanceof Error ? error.message : String(error));\n });\n this.cache.set(file, pending);\n }\n return pending;\n }\n\n /**\n * Forgets cached modules so the next load imports them again. With no argument, forgets\n * everything. Only useful together with `enableModuleReloading`; otherwise `import()` hands\n * back the same instance.\n */\n invalidate(files?: Iterable<string>): void {\n if (files === undefined) {\n this.cache.clear();\n return;\n }\n for (const file of files) this.cache.delete(file);\n }\n\n /** Imports every file up front so a bad module fails startup instead of the first interaction. */\n async preload(files: Iterable<string>): Promise<void> {\n await Promise.all([...new Set(files)].map((file) => this.load(file)));\n }\n\n /** The default export of a file, checked to be a function. */\n async loadDefault<T>(file: string, what: string): Promise<T> {\n const module = await this.load(file);\n const value = module.default;\n if (typeof value !== \"function\") {\n throw new HandlerLoadError(\n file,\n `${what} must be the default export and a function, got ${describe(value)}.`,\n );\n }\n return value as T;\n }\n\n /** A named export of a file, checked to be a function. */\n async loadNamed<T>(file: string, name: string, what: string): Promise<T> {\n const module = await this.load(file);\n const value = module[name];\n if (typeof value !== \"function\") {\n throw new HandlerLoadError(\n file,\n `${what} must be exported as \"${name}\" and be a function, got ${describe(value)}.`,\n );\n }\n return value as T;\n }\n}\n\nfunction describe(value: unknown): string {\n return value === undefined ? \"no export\" : typeof value;\n}\n","import path from \"node:path\";\nimport type { Client } from \"discord.js\";\nimport type { NectarServices } from \"../index.js\";\nimport type { Manifest, ManifestRoute } from \"../manifest/schema.js\";\nimport type { ModuleRegistry } from \"./modules.js\";\nimport type { SignalEmitter } from \"./signals.js\";\nimport type { Env, Logger, RouteInfo } from \"./types.js\";\n\n/** Everything dispatch needs, shared by interactions and events. */\nexport interface RuntimeState {\n manifest: Manifest;\n /** Absolute. */\n appDir: string;\n client: Client;\n modules: ModuleRegistry;\n env: Env;\n logger: Logger;\n signals: SignalEmitter;\n /** Filled by plugin `start` hooks. */\n services: NectarServices;\n}\n\nexport function absolute(state: RuntimeState, file: string): string {\n return path.join(state.appDir, ...file.split(\"/\"));\n}\n\nexport function routeInfo(state: RuntimeState, route: ManifestRoute): RouteInfo {\n return {\n id: route.id,\n category: route.category,\n path: route.path,\n file: absolute(state, route.file),\n };\n}\n\nexport function chains(state: RuntimeState, route: ManifestRoute) {\n return {\n middleware: route.middleware.map((f) => absolute(state, f)),\n errors: route.errors.map((f) => absolute(state, f)),\n };\n}\n","import type { AutocompleteInteraction, Interaction } from \"discord.js\";\nimport { ApplicationCommandType } from \"discord-api-types/v10\";\nimport {\n type ComponentKind,\n createMatcher,\n findInvalidParam,\n type ParamValidators,\n paramValidatorsOf,\n} from \"../components/index.js\";\nimport type {\n ManifestAutocompleteRoute,\n ManifestCommand,\n ManifestCommandRoute,\n ManifestComponentRoute,\n ManifestRoute,\n} from \"../manifest/schema.js\";\nimport { handleError, logFields } from \"./errors.js\";\nimport { runChain } from \"./middleware.js\";\nimport { HandlerLoadError } from \"./modules.js\";\nimport { type InteractionMeta, interactionMeta } from \"./signals.js\";\nimport { chains, type RuntimeState, routeInfo } from \"./state.js\";\nimport type { Handler, InteractionContext, Middleware } from \"./types.js\";\n\n/** Routes one incoming interaction. Resolves to nothing; every error ends in a boundary. */\nexport type InteractionDispatcher = (interaction: Interaction) => Promise<void>;\n\nexport function createInteractionDispatcher(state: RuntimeState): InteractionDispatcher {\n const tables = buildTables(state.manifest.routes, state.manifest.commands);\n\n return async (interaction) => {\n const receivedAt = Date.now();\n const meta = interactionMeta(interaction);\n state.signals.emit({ type: \"interaction:start\", trace: interaction.id, interaction: meta });\n\n if (interaction.isChatInputCommand()) {\n const group = interaction.options.getSubcommandGroup(false);\n const sub = interaction.options.getSubcommand(false);\n const key = [group, sub].filter((p) => p !== null).join(\"/\");\n const route = tables.commands.get(\n commandKey(ApplicationCommandType.ChatInput, interaction.commandName, key),\n );\n if (route === undefined) {\n return unknown(state, interaction, meta, `chat input command /${meta.command}`);\n }\n return run(state, route, interaction, meta, {}, receivedAt);\n }\n\n if (interaction.isContextMenuCommand()) {\n const route = tables.commands.get(\n commandKey(interaction.commandType, interaction.commandName, \"\"),\n );\n if (route === undefined) {\n return unknown(state, interaction, meta, `context menu command \"${meta.command}\"`);\n }\n return run(state, route, interaction, meta, {}, receivedAt);\n }\n\n if (interaction.isAutocomplete()) {\n const group = interaction.options.getSubcommandGroup(false);\n const sub = interaction.options.getSubcommand(false);\n const key = [group, sub].filter((p) => p !== null).join(\"/\");\n const command = tables.commands.get(\n commandKey(ApplicationCommandType.ChatInput, interaction.commandName, key),\n );\n const route = command === undefined ? undefined : tables.autocomplete.get(command.id);\n const option = interaction.options.getFocused(true).name;\n if (route === undefined || !route.options.includes(option)) {\n return unknown(state, interaction, meta, `autocomplete for /${meta.command} \"${option}\"`);\n }\n return run(state, route, interaction, meta, {}, receivedAt, option);\n }\n\n const component: [ComponentKind, string] | null = interaction.isButton()\n ? [\"button\", interaction.customId]\n : interaction.isAnySelectMenu()\n ? [\"select\", interaction.customId]\n : interaction.isModalSubmit()\n ? [\"modal\", interaction.customId]\n : null;\n if (component === null) {\n // Not a type Nectar routes. The app may listen for it on the client itself.\n state.signals.emit({\n type: \"interaction:reject\",\n trace: interaction.id,\n interaction: meta,\n reason: \"unknown-interaction\",\n });\n return;\n }\n\n const [kind, customId] = component;\n const match = tables.matcher.match(kind, customId);\n if (!match.ok) {\n if (match.reason === \"not-nectar\") return;\n state.logger.warn(`Ignoring ${kind} with custom ID \"${meta.customId}\": ${match.reason}.`, {\n trace: interaction.id,\n interaction: kind,\n customId: meta.customId,\n });\n state.signals.emit({\n type: \"interaction:reject\",\n trace: interaction.id,\n interaction: meta,\n reason: match.reason,\n });\n return;\n }\n return run(state, match.route, interaction, meta, match.params, receivedAt);\n };\n}\n\nasync function run(\n state: RuntimeState,\n route: ManifestRoute,\n interaction: Interaction,\n meta: InteractionMeta,\n params: Record<string, string | string[]>,\n receivedAt: number,\n autocompleteOption?: string,\n): Promise<void> {\n const ctx: InteractionContext = {\n interaction,\n client: state.client,\n route: routeInfo(state, route),\n params,\n env: state.env,\n trace: {\n id: interaction.id,\n receivedAt,\n elapsed: () => Date.now() - interaction.createdTimestamp,\n },\n services: state.services,\n };\n const files = chains(state, route);\n const tag = { trace: interaction.id, interaction: meta, route: ctx.route };\n state.signals.emit({ type: \"route:match\", ...tag });\n\n let handlerStart = 0;\n try {\n const middleware = await Promise.all(\n files.middleware.map((file) => state.modules.loadDefault<Middleware>(file, \"Middleware\")),\n );\n const handler =\n autocompleteOption === undefined\n ? await state.modules.loadDefault<Handler>(ctx.route.file, \"The handler\")\n : await state.modules.loadNamed<Handler>(\n ctx.route.file,\n autocompleteOption,\n \"The autocomplete handler\",\n );\n if (route.kind === \"button\" || route.kind === \"select\" || route.kind === \"modal\") {\n const invalid = await findInvalidParam(validators(ctx.route.file, handler, route), params);\n if (invalid !== null) {\n state.logger.warn(\n `Rejected ${route.kind} for ${ctx.route.id}: \"${invalid}\" failed validation.`,\n {\n ...logFields(ctx, meta),\n param: invalid,\n },\n );\n state.signals.emit({\n type: \"interaction:reject\",\n trace: interaction.id,\n interaction: meta,\n reason: \"invalid-param\",\n route: ctx.route,\n param: invalid,\n });\n return;\n }\n }\n await runChain(middleware, ctx, handler, {\n middleware: (index) =>\n state.signals.emit({\n type: \"middleware:enter\",\n ...tag,\n file: files.middleware[index] ?? \"\",\n }),\n handler: () => {\n handlerStart = Date.now();\n state.signals.emit({ type: \"handler:enter\", ...tag });\n },\n });\n const handled = handlerStart !== 0;\n if (handled) {\n state.signals.emit({ type: \"handler:complete\", ...tag, duration: Date.now() - handlerStart });\n }\n const duration = Date.now() - receivedAt;\n state.signals.emit({ type: \"interaction:complete\", ...tag, duration, handled });\n state.logger.debug(\n handled ? `Handled ${ctx.route.id} in ${duration}ms.` : `Middleware stopped ${ctx.route.id}.`,\n logFields(ctx, meta),\n );\n } catch (error) {\n const boundary = await handleError(\n error,\n ctx,\n files.errors,\n state.modules,\n state.logger,\n files.middleware,\n );\n state.signals.emit({ type: \"interaction:fail\", ...tag, error, boundary });\n if (boundary !== null) {\n state.logger.debug(`${ctx.route.id} failed, handled by ${boundary}.`, {\n ...logFields(ctx, meta),\n boundary,\n error,\n });\n }\n if (autocompleteOption !== undefined)\n await closeAutocomplete(interaction as AutocompleteInteraction);\n }\n}\n\n/** Discord shows a spinner until autocomplete answers, so a failed handler answers with nothing. */\nasync function closeAutocomplete(interaction: AutocompleteInteraction): Promise<void> {\n if (interaction.responded) return;\n try {\n await interaction.respond([]);\n } catch {\n // Already answered or expired. Nothing left to do.\n }\n}\n\nfunction unknown(\n state: RuntimeState,\n interaction: Interaction,\n meta: InteractionMeta,\n what: string,\n): void {\n state.logger.warn(`No route for ${what}. Run \\`nectar sync\\` if commands changed.`, {\n trace: interaction.id,\n interaction: meta.type,\n command: meta.command,\n });\n state.signals.emit({\n type: \"interaction:reject\",\n trace: interaction.id,\n interaction: meta,\n reason: \"no-route\",\n });\n}\n\n/**\n * The route's parameter validators, checked once per handler instance. The compiler checked\n * the shape at build time; this repeats it so a JavaScript project or a hot-reloaded file\n * fails the same way instead of at the first click.\n */\nfunction validators(\n file: string,\n handler: Handler,\n route: ManifestComponentRoute,\n): ParamValidators {\n const cached = validatorCache.get(handler);\n if (cached !== undefined) return cached;\n let result: ParamValidators;\n try {\n result = paramValidatorsOf(handler, route);\n } catch (error) {\n throw new HandlerLoadError(file, error instanceof Error ? error.message : String(error));\n }\n validatorCache.set(handler, result);\n return result;\n}\n\nconst validatorCache = new WeakMap<Handler, ParamValidators>();\n\ninterface Tables {\n /** `${type}:${name}:${handlerKey}` to the handler route. */\n commands: Map<string, ManifestCommandRoute>;\n autocomplete: Map<string, ManifestAutocompleteRoute>;\n matcher: ReturnType<typeof createMatcher<ManifestComponentRoute>>;\n}\n\nfunction buildTables(routes: ManifestRoute[], commands: ManifestCommand[]): Tables {\n const commandRoutes = new Map<string, ManifestCommandRoute>();\n const autocomplete = new Map<string, ManifestAutocompleteRoute>();\n const components: ManifestComponentRoute[] = [];\n\n for (const route of routes) {\n if (route.kind === \"command\") commandRoutes.set(route.id, route);\n else if (route.kind === \"autocomplete\") autocomplete.set(route.id, route);\n else if (route.kind !== \"event\") components.push(route);\n }\n\n const table = new Map<string, ManifestCommandRoute>();\n for (const command of commands) {\n for (const [key, id] of Object.entries(command.handlers)) {\n const route = commandRoutes.get(id);\n if (route !== undefined) table.set(commandKey(command.type, command.name, key), route);\n }\n }\n\n return { commands: table, autocomplete, matcher: createMatcher(components) };\n}\n\nfunction commandKey(type: number, name: string, handlerKey: string): string {\n return `${type}:${name}:${handlerKey}`;\n}\n","import type { ManifestEvent, ManifestEventRoute } from \"../manifest/schema.js\";\nimport { handleError } from \"./errors.js\";\nimport { chains, type RuntimeState, routeInfo } from \"./state.js\";\nimport type { EventContext, EventHandler } from \"./types.js\";\n\nexport interface EventBinding {\n name: string;\n /** Resolves once every handler it fanned out to has finished. */\n listener: (...args: unknown[]) => Promise<void>;\n}\n\n/**\n * One discord.js listener per event. It fans out to the compiled handlers in manifest order,\n * sequentially or concurrently as the event's mode says. A `once` handler runs on the first\n * emission only; when every handler of an event is spent, the listener is removed.\n *\n * Handler errors go to the route's boundaries and never reach the client's `error` event.\n * Returns the bindings so the runtime can remove them on shutdown.\n */\nexport function bindEvents(state: RuntimeState): EventBinding[] {\n const routes = new Map<string, ManifestEventRoute>();\n for (const route of state.manifest.routes) {\n if (route.kind === \"event\") routes.set(route.id, route);\n }\n\n const bindings: EventBinding[] = [];\n for (const event of state.manifest.events) {\n const handlers = event.handlers\n .map((id) => routes.get(id))\n .filter((r): r is ManifestEventRoute => r !== undefined);\n if (handlers.length === 0) continue;\n\n const spent = new Set<string>();\n const listener = (...args: unknown[]) => {\n const live = handlers.filter((h) => !spent.has(h.id));\n for (const h of live) if (h.once) spent.add(h.id);\n if (spent.size === handlers.length) state.client.off(event.name, listener);\n return fanOut(state, event, live, args);\n };\n\n state.client.on(event.name, listener);\n bindings.push({ name: event.name, listener });\n }\n return bindings;\n}\n\nasync function fanOut(\n state: RuntimeState,\n event: ManifestEvent,\n handlers: ManifestEventRoute[],\n args: unknown[],\n): Promise<void> {\n if (event.mode === \"concurrent\") {\n await Promise.all(handlers.map((route) => invoke(state, event, route, args)));\n return;\n }\n for (const route of handlers) await invoke(state, event, route, args);\n}\n\nasync function invoke(\n state: RuntimeState,\n event: ManifestEvent,\n route: ManifestEventRoute,\n args: unknown[],\n) {\n const ctx: EventContext = {\n client: state.client,\n route: routeInfo(state, route),\n env: state.env,\n services: state.services,\n };\n try {\n const handler = await state.modules.loadDefault<EventHandler>(ctx.route.file, \"The handler\");\n await handler(...args, ctx);\n } catch (error) {\n const boundary = await handleError(\n error,\n ctx,\n chains(state, route).errors,\n state.modules,\n state.logger,\n );\n state.signals.emit({\n type: \"event:fail\",\n event: event.name,\n route: ctx.route,\n error,\n boundary,\n });\n }\n}\n","import type { Logger } from \"./types.js\";\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/** Structured metadata attached to a log line. `error` is the thrown value, when there is one. */\nexport type LogFields = Record<string, unknown>;\n\nexport interface LogRecord {\n level: LogLevel;\n message: string;\n /** Epoch milliseconds. */\n at: number;\n fields: LogFields;\n}\n\n/** Where log records go. The default prints to the console; an app can hand records to any library. */\nexport type LogSink = (record: LogRecord) => void;\n\nexport interface LoggerOptions {\n /** Lowest level that reaches the sink. Defaults to `info`. */\n level?: LogLevel;\n sink?: LogSink;\n}\n\nconst ORDER: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\nexport function createLogger(options: LoggerOptions = {}): Logger {\n const threshold = ORDER[options.level ?? \"info\"];\n const sink = options.sink ?? consoleSink;\n const log = (level: LogLevel, message: string, fields: LogFields = {}) => {\n if (ORDER[level] < threshold) return;\n sink({ level, message, at: Date.now(), fields });\n };\n return {\n debug: (message, fields) => log(\"debug\", message, fields),\n info: (message, fields) => log(\"info\", message, fields),\n warn: (message, fields) => log(\"warn\", message, fields),\n error: (message, fields) => log(\"error\", message, fields),\n };\n}\n\n/** `[nectar] message key=value ...` on the matching console method, then the error if any. */\nexport const consoleSink: LogSink = ({ level, message, fields }) => {\n const { error, ...rest } = fields;\n const pairs = Object.entries(rest)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${key}=${typeof value === \"string\" ? value : JSON.stringify(value)}`);\n const line = [`[nectar] ${message}`, ...pairs].join(\" \");\n if (error === undefined) console[level](line);\n else console[level](line, error);\n};\n","import path from \"node:path\";\nimport { Client, DiscordjsError, DiscordjsErrorCodes, Events } from \"discord.js\";\nimport { registerComponentRoutes } from \"../components/registry.js\";\nimport type { Manifest, ManifestComponentRoute } from \"../manifest/schema.js\";\nimport { type NectarPlugin, type PluginApp, PluginError } from \"../plugins/index.js\";\nimport { createInteractionDispatcher } from \"./dispatch.js\";\nimport { bindEvents, type EventBinding } from \"./events.js\";\nimport { createLogger } from \"./logger.js\";\nimport { ModuleRegistry } from \"./modules.js\";\nimport { createSignals, type SignalEmitter } from \"./signals.js\";\nimport type { RuntimeState } from \"./state.js\";\nimport type { Env, Logger, RuntimeConfig } from \"./types.js\";\n\nexport interface RuntimeOptions {\n manifest: Manifest;\n /** Absolute path of the app directory the manifest was compiled from. */\n appDir: string;\n config: RuntimeConfig;\n /** Defaults from `NODE_ENV`. */\n env?: Env;\n /** Overrides `config.logger`. */\n logger?: Logger;\n /** Share an emitter created earlier, so signals from before the runtime existed line up. */\n signals?: SignalEmitter;\n /** Reuse an existing client instead of building one from `config`. Tests use this. */\n client?: Client;\n}\n\n/** `client.login` failed: Discord refused the token, or could not be reached. */\nexport class LoginError extends Error {\n readonly invalidToken: boolean;\n\n constructor(cause: unknown) {\n const invalidToken =\n cause instanceof DiscordjsError && cause.code === DiscordjsErrorCodes.TokenInvalid;\n super(\n invalidToken ? \"Discord rejected the bot token.\" : `Could not log in: ${describe(cause)}`,\n { cause },\n );\n this.name = \"LoginError\";\n this.invalidToken = invalidToken;\n }\n}\n\nexport interface StartOptions {\n token: string;\n /**\n * Stop on SIGINT and SIGTERM, and when the IPC channel to a parent process closes, so a shard\n * does not outlive the manager that spawned it. Defaults to `true`.\n */\n signals?: boolean;\n /** How long `stop()` waits for in-flight interactions, in milliseconds. Defaults to 10000. */\n drainTimeout?: number;\n}\n\nexport interface Runtime {\n readonly client: Client;\n readonly env: Env;\n readonly modules: ModuleRegistry;\n /** Framework signals. `config.observe` is subscribed already. */\n readonly signals: SignalEmitter;\n /** Loads handlers, attaches listeners, and logs in. A failed login stops it and throws `LoginError`. */\n start(options: StartOptions): Promise<void>;\n /**\n * Stops taking interactions, waits for the in-flight ones, detaches listeners, and\n * destroys the client. Safe to call twice.\n */\n stop(): Promise<void>;\n /**\n * Swaps in a recompiled manifest: dispatch tables, component routes, and event listeners\n * follow it. Handler modules are not touched; invalidate them through `modules`.\n */\n update(manifest: Manifest): void;\n}\n\nexport function createRuntime(options: RuntimeOptions): Runtime {\n const env = options.env ?? envFromProcess();\n const logger = options.logger ?? createLogger(options.config.logger);\n const signals = options.signals ?? createSignals(logger);\n if (options.config.observe !== undefined) signals.on(options.config.observe);\n const client =\n options.client ??\n new Client({\n ...options.config.client,\n intents: options.config.intents,\n ...(options.config.partials === undefined ? {} : { partials: options.config.partials }),\n });\n\n const state: RuntimeState = {\n manifest: options.manifest,\n appDir: path.resolve(options.appDir),\n client,\n modules: new ModuleRegistry(),\n env,\n logger,\n signals,\n services: {},\n };\n const plugins = options.config.plugins ?? [];\n const app: PluginApp = {\n client,\n env,\n logger,\n signals,\n get manifest() {\n return state.manifest;\n },\n };\n\n registerComponentRoutes(componentRoutes(state.manifest));\n let dispatch = createInteractionDispatcher(state);\n const inFlight = new Set<Promise<void>>();\n let bindings: EventBinding[] = [];\n let started = false;\n let globalStarted = false;\n let drainTimeout = 10_000;\n let stopping: Promise<void> | null = null;\n let onSignal: (() => void) | null = null;\n\n const onInteraction = (interaction: Parameters<typeof dispatch>[0]) => {\n const task = dispatch(interaction).finally(() => inFlight.delete(task));\n inFlight.add(task);\n };\n // The parent that forked this process closed the channel: a shard manager that exited.\n const onDisconnect = () => void runtime.stop();\n const gateway = {\n [Events.ShardReady]: (shard: number) =>\n signals.emit({ type: \"gateway:connect\", shard, resumed: false }),\n [Events.ShardResume]: (shard: number) =>\n signals.emit({ type: \"gateway:connect\", shard, resumed: true }),\n [Events.ShardDisconnect]: (event: { code: number }, shard: number) =>\n signals.emit({ type: \"gateway:disconnect\", shard, code: event.code }),\n };\n\n const runtime: Runtime = {\n client,\n env,\n modules: state.modules,\n signals,\n\n async start({ token, signals: osSignals = true, drainTimeout: timeout = 10_000 }) {\n drainTimeout = timeout;\n await startPlugins(plugins, app, state.services as Record<string, unknown>);\n if (runsShardZero(client.options.shards)) {\n await startGlobal(plugins, app);\n globalStarted = true;\n }\n if (options.config.eager ?? env === \"production\") {\n await state.modules.preload(manifestFiles(state.manifest, state.appDir));\n }\n\n bindings = bindEvents(state);\n started = true;\n client.on(Events.InteractionCreate, onInteraction);\n client.on(Events.ShardReady, gateway[Events.ShardReady]);\n client.on(Events.ShardResume, gateway[Events.ShardResume]);\n client.on(Events.ShardDisconnect, gateway[Events.ShardDisconnect]);\n\n if (osSignals) {\n onSignal = () => {\n if (stopping !== null) {\n logger.warn(\"Second signal received, exiting now.\");\n process.exit(1);\n }\n void this.stop();\n };\n process.once(\"SIGINT\", onSignal);\n process.once(\"SIGTERM\", onSignal);\n if (process.connected) process.once(\"disconnect\", onDisconnect);\n }\n\n try {\n await client.login(token);\n } catch (error) {\n await this.stop();\n throw new LoginError(error);\n }\n },\n\n stop() {\n if (stopping !== null) return stopping;\n stopping = (async () => {\n signals.emit({ type: \"shutdown\" });\n client.off(Events.InteractionCreate, onInteraction);\n client.off(Events.ShardReady, gateway[Events.ShardReady]);\n client.off(Events.ShardResume, gateway[Events.ShardResume]);\n client.off(Events.ShardDisconnect, gateway[Events.ShardDisconnect]);\n for (const { name, listener } of bindings) client.off(name, listener);\n bindings = [];\n if (onSignal !== null) {\n process.off(\"SIGINT\", onSignal);\n process.off(\"SIGTERM\", onSignal);\n onSignal = null;\n }\n process.off(\"disconnect\", onDisconnect);\n await drain(inFlight, drainTimeout, logger);\n if (globalStarted) await stopPlugins(plugins, app, logger, \"stopGlobal\");\n await stopPlugins(plugins, app, logger, \"stop\");\n if (process.send !== undefined) ignoreClosedChannel();\n await client.destroy();\n })();\n return stopping;\n },\n\n update(manifest) {\n state.manifest = manifest;\n registerComponentRoutes(componentRoutes(manifest));\n dispatch = createInteractionDispatcher(state);\n if (started && stopping === null) {\n for (const { name, listener } of bindings) client.off(name, listener);\n bindings = bindEvents(state);\n }\n },\n };\n return runtime;\n}\n\n/** Runs `start` hooks in config order. Two plugins offering the same service is a startup failure. */\nasync function startPlugins(\n plugins: readonly NectarPlugin[],\n app: PluginApp,\n services: Record<string, unknown>,\n): Promise<void> {\n const providers = new Map<string, string>();\n for (const plugin of plugins) {\n let provided: unknown;\n try {\n provided = await plugin.start?.(app);\n } catch (error) {\n throw new PluginError(plugin.name, `start failed: ${describe(error)}`);\n }\n if (provided === undefined || provided === null) continue;\n if (typeof provided !== \"object\") {\n throw new PluginError(plugin.name, `start must return an object of services or nothing.`);\n }\n for (const [name, service] of Object.entries(provided)) {\n const owner = providers.get(name);\n if (owner !== undefined) {\n throw new PluginError(\n plugin.name,\n `provides service \"${name}\", which plugin \"${owner}\" already provides.`,\n );\n }\n providers.set(name, plugin.name);\n services[name] = service;\n }\n }\n}\n\nlet ignoringClosedChannel = false;\n\n/**\n * discord.js's shard client reports gateway events to the manager with `process.send`, and\n * destroying the client produces some. If the manager has gone, each report fails with an\n * `error` event on `process` that would crash it. Only that failure is dropped; the listener\n * stays, since the failures arrive on a later tick.\n */\nfunction ignoreClosedChannel(): void {\n if (ignoringClosedChannel) return;\n ignoringClosedChannel = true;\n process.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code !== \"ERR_IPC_CHANNEL_CLOSED\") throw error;\n });\n}\n\n/** Runs `startGlobal` hooks in config order. */\nasync function startGlobal(plugins: readonly NectarPlugin[], app: PluginApp): Promise<void> {\n for (const plugin of plugins) {\n try {\n await plugin.startGlobal?.(app);\n } catch (error) {\n throw new PluginError(plugin.name, `startGlobal failed: ${describe(error)}`);\n }\n }\n}\n\n/** Runs `stopGlobal` or `stop` hooks in reverse order. A failing hook is logged; shutdown continues. */\nasync function stopPlugins(\n plugins: readonly NectarPlugin[],\n app: PluginApp,\n logger: Logger,\n hook: \"stop\" | \"stopGlobal\",\n): Promise<void> {\n for (const plugin of [...plugins].reverse()) {\n try {\n await plugin[hook]?.(app);\n } catch (error) {\n logger.error(`Plugin \"${plugin.name}\": ${hook} failed.`, { plugin: plugin.name, error });\n }\n }\n}\n\n/**\n * Whether this process runs shard 0. Application-global hooks run there, so they run once\n * however the shards are spread over processes. `auto` means this process runs them all.\n */\nfunction runsShardZero(shards: Client[\"options\"][\"shards\"]): boolean {\n if (shards === undefined || shards === \"auto\") return true;\n return typeof shards === \"number\" ? shards === 0 : shards.includes(0);\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function drain(inFlight: Set<Promise<void>>, timeout: number, logger: Logger) {\n if (inFlight.size === 0) return;\n let timer: NodeJS.Timeout | undefined;\n const expired = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), timeout);\n });\n const result = await Promise.race([Promise.allSettled([...inFlight]), expired]);\n clearTimeout(timer);\n if (result === \"timeout\") {\n logger.warn(\n `${inFlight.size} interaction(s) still running after ${timeout}ms, shutting down anyway.`,\n );\n }\n}\n\nfunction componentRoutes(manifest: Manifest): ManifestComponentRoute[] {\n return manifest.routes.filter(\n (r): r is ManifestComponentRoute =>\n r.kind === \"button\" || r.kind === \"select\" || r.kind === \"modal\",\n );\n}\n\n/** Absolute paths of every handler, middleware, and error boundary file a manifest refers to. */\nexport function manifestFiles(manifest: Manifest, appDir: string): Set<string> {\n const files = new Set<string>();\n for (const route of manifest.routes) {\n files.add(route.file);\n for (const file of route.middleware) files.add(file);\n for (const file of route.errors) files.add(file);\n }\n return new Set([...files].map((file) => path.join(appDir, ...file.split(\"/\"))));\n}\n\nfunction envFromProcess(): Env {\n const value = process.env.NODE_ENV;\n return value === \"production\" || value === \"test\" ? value : \"development\";\n}\n"],"mappings":"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"}
|
package/dist/start.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/start.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Runs `nectar start` in the project at `root`. The `start.mjs` that `nectar build` writes calls
|
|
4
|
+
* this, so a build runs with `node` alone and shard managers have a file to spawn.
|
|
5
|
+
*/
|
|
6
|
+
export declare function start(root: string | URL): Promise<void>;
|
|
7
|
+
//#endregion
|
|
8
|
+
//# sourceMappingURL=start.d.ts.map
|
package/dist/start.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { t as main } from "./cli-Ce-ZUj6M.js";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
//#region src/start.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runs `nectar start` in the project at `root`. The `start.mjs` that `nectar build` writes calls
|
|
6
|
+
* this, so a build runs with `node` alone and shard managers have a file to spawn.
|
|
7
|
+
*/
|
|
8
|
+
async function start(root) {
|
|
9
|
+
if (process.env.SHARDING_MANAGER === "true" && process.env.DISCORD_TOKEN === "null") delete process.env.DISCORD_TOKEN;
|
|
10
|
+
process.exitCode = await main(["start"], typeof root === "string" ? root : fileURLToPath(root));
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { start };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.js","names":[],"sources":["../src/start.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { main } from \"./cli/index.js\";\n\n/**\n * Runs `nectar start` in the project at `root`. The `start.mjs` that `nectar build` writes calls\n * this, so a build runs with `node` alone and shard managers have a file to spawn.\n */\nexport async function start(root: string | URL): Promise<void> {\n // A ShardingManager with no token of its own sets DISCORD_TOKEN=\"null\" for its shards, and\n // .env never overrides a variable that is already set. Without this, `.env` would be ignored.\n if (process.env.SHARDING_MANAGER === \"true\" && process.env.DISCORD_TOKEN === \"null\") {\n delete process.env.DISCORD_TOKEN;\n }\n process.exitCode = await main([\"start\"], typeof root === \"string\" ? root : fileURLToPath(root));\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,MAAM,MAAmC;CAG7D,IAAI,QAAQ,IAAI,qBAAqB,UAAU,QAAQ,IAAI,kBAAkB,QAC3E,OAAO,QAAQ,IAAI;CAErB,QAAQ,WAAW,MAAM,KAAK,CAAC,OAAO,GAAG,OAAO,SAAS,WAAW,OAAO,cAAc,IAAI,CAAC;AAChG"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { $ as InteractionContext, T as ComponentPath, Y as Env, b as CommandRoutes, et as Logger, jt as OptionType, lt as Signal, n as NectarServices, t as NectarRoutes, v as CommandContext, w as ComponentParams, x as ComponentContext, y as CommandPath } from "./index-DGMxBsub.js";
|
|
2
|
+
import { AnySelectMenuInteraction, Attachment, AutocompleteInteraction, ButtonInteraction, Channel, Client, ClientEvents, GuildMember, Interaction, ModalSubmitInteraction, Role, User } from "discord.js";
|
|
3
|
+
//#region src/testing.d.ts
|
|
4
|
+
type Empty = Record<never, never>;
|
|
5
|
+
/**
|
|
6
|
+
* A stand-in for a discord.js object. Property names are checked against the real type;
|
|
7
|
+
* values are not, so pass whatever the handler reads, like `guild`, `member`, or `values`.
|
|
8
|
+
*/
|
|
9
|
+
export type StubFields<T> = T extends unknown ? { [K in keyof T]?: unknown; } : never;
|
|
10
|
+
/** What a test passes for each option type. */
|
|
11
|
+
interface OptionValues {
|
|
12
|
+
string: string;
|
|
13
|
+
integer: number;
|
|
14
|
+
number: number;
|
|
15
|
+
boolean: boolean;
|
|
16
|
+
user: StubFields<User>;
|
|
17
|
+
channel: StubFields<Channel>;
|
|
18
|
+
role: StubFields<Role>;
|
|
19
|
+
mentionable: StubFields<User | GuildMember | Role>;
|
|
20
|
+
attachment: StubFields<Attachment>;
|
|
21
|
+
}
|
|
22
|
+
type OptionTypes<P extends CommandPath> = CommandRoutes[P] extends {
|
|
23
|
+
options: infer O;
|
|
24
|
+
} ? O : Record<string, OptionType>;
|
|
25
|
+
/** Option values by name, typed from the command's generated options. */
|
|
26
|
+
export type CommandOptions<P extends CommandPath> = [keyof OptionTypes<P>] extends [never] ? Record<string, never> : { [K in keyof OptionTypes<P>]?: OptionValues[OptionTypes<P>[K] & OptionType]; };
|
|
27
|
+
type AutocompleteRoutes = NectarRoutes extends {
|
|
28
|
+
autocomplete: infer A;
|
|
29
|
+
} ? A : Record<string, string>;
|
|
30
|
+
/** Command paths that have an `autocomplete.ts`. */
|
|
31
|
+
export type AutocompletePath = keyof AutocompleteRoutes & string;
|
|
32
|
+
/** A route's context with the interaction narrowed to the kind being tested. */
|
|
33
|
+
type With<C, I> = Omit<C, "interaction"> & {
|
|
34
|
+
interaction: I;
|
|
35
|
+
};
|
|
36
|
+
type ComponentArgs<P extends ComponentPath, I> = Empty extends ComponentParams<P> ? [params?: ComponentParams<P>, interaction?: StubFields<I>] : [params: ComponentParams<P>, interaction?: StubFields<I>];
|
|
37
|
+
type SelectInteraction<P extends ComponentPath> = Extract<ComponentContext<P>["interaction"], AnySelectMenuInteraction>;
|
|
38
|
+
type ResponseMethod = "reply" | "deferReply" | "editReply" | "followUp" | "deleteReply" | "update" | "deferUpdate" | "showModal" | "respond";
|
|
39
|
+
export interface TestResponse {
|
|
40
|
+
method: ResponseMethod;
|
|
41
|
+
/** The first argument the handler passed. */
|
|
42
|
+
options: unknown;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The signal that ended dispatch: `interaction:complete` (with `handled: false` when a
|
|
46
|
+
* middleware stopped the chain), `interaction:fail` (with the error and the boundary that took
|
|
47
|
+
* it), or `interaction:reject` (refused before any application code ran).
|
|
48
|
+
*/
|
|
49
|
+
export type Outcome = Extract<Signal, {
|
|
50
|
+
type: "interaction:complete" | "interaction:fail" | "interaction:reject";
|
|
51
|
+
}>;
|
|
52
|
+
export interface InteractionResult<I, C = InteractionContext<I>> {
|
|
53
|
+
/** The stub that was dispatched. */
|
|
54
|
+
interaction: I;
|
|
55
|
+
/** What the route sent back through the interaction, in call order. */
|
|
56
|
+
responses: TestResponse[];
|
|
57
|
+
/** What the handler was called with, middleware additions included. `null` if it never ran. */
|
|
58
|
+
context: C | null;
|
|
59
|
+
outcome: Outcome;
|
|
60
|
+
/** Every signal the dispatch emitted, `interaction:start` through the outcome. */
|
|
61
|
+
signals: Signal[];
|
|
62
|
+
}
|
|
63
|
+
export interface EventResult {
|
|
64
|
+
/** One per handler that threw, naming the boundary that took the error. */
|
|
65
|
+
failures: Extract<Signal, {
|
|
66
|
+
type: "event:fail";
|
|
67
|
+
}>[];
|
|
68
|
+
}
|
|
69
|
+
export interface TestAppOptions {
|
|
70
|
+
/** Handlers see it as `ctx.client`. Defaults to a discord.js client that never logs in. */
|
|
71
|
+
client?: Client;
|
|
72
|
+
/** Defaults to `"test"`. */
|
|
73
|
+
env?: Env;
|
|
74
|
+
/** Receives dispatch warnings and the default error boundary's output. Defaults to the console. */
|
|
75
|
+
logger?: Logger;
|
|
76
|
+
/** What handlers find on `ctx.services`, in place of plugin `start` hooks. */
|
|
77
|
+
services?: Partial<NectarServices>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Runs routes from a built manifest through the runtime's own dispatch, without a gateway.
|
|
81
|
+
* The interaction methods build a stubbed discord.js interaction for the route and resolve
|
|
82
|
+
* once the middleware chain, handler, and error boundaries are done.
|
|
83
|
+
*/
|
|
84
|
+
export interface TestApp {
|
|
85
|
+
readonly client: Client;
|
|
86
|
+
/**
|
|
87
|
+
* Runs a chat input or context menu command. `options` are values by name; the handler
|
|
88
|
+
* reads them through discord.js's own option resolver.
|
|
89
|
+
*/
|
|
90
|
+
command<P extends CommandPath>(path: P, options?: CommandOptions<P>, interaction?: StubFields<CommandContext<P>["interaction"]>): Promise<InteractionResult<CommandContext<P>["interaction"], CommandContext<P>>>;
|
|
91
|
+
/**
|
|
92
|
+
* Runs the autocomplete handler for `focused`, whose value is `options[focused]` or `""`.
|
|
93
|
+
* As in a real autocomplete, user, channel, role, and attachment options arrive as IDs only.
|
|
94
|
+
*/
|
|
95
|
+
autocomplete<P extends AutocompletePath>(path: P, focused: AutocompleteRoutes[P] & string, options?: CommandOptions<P & CommandPath>, interaction?: StubFields<AutocompleteInteraction>): Promise<InteractionResult<AutocompleteInteraction, With<CommandContext<P & CommandPath>, AutocompleteInteraction>>>;
|
|
96
|
+
/** Clicks a button. The custom ID is encoded from `params`, then decoded and validated. */
|
|
97
|
+
button<P extends ComponentPath>(path: P, ...args: ComponentArgs<P, ButtonInteraction>): Promise<InteractionResult<ButtonInteraction, With<ComponentContext<P>, ButtonInteraction>>>;
|
|
98
|
+
/** Submits a select menu. `values` starts empty; set it, or `users` and the like, on the stub. */
|
|
99
|
+
select<P extends ComponentPath>(path: P, ...args: ComponentArgs<P, SelectInteraction<P>>): Promise<InteractionResult<SelectInteraction<P>, With<ComponentContext<P>, SelectInteraction<P>>>>;
|
|
100
|
+
/** Submits a modal. Set `fields` on the stub when the handler reads inputs. */
|
|
101
|
+
modal<P extends ComponentPath>(path: P, ...args: ComponentArgs<P, ModalSubmitInteraction>): Promise<InteractionResult<ModalSubmitInteraction, With<ComponentContext<P>, ModalSubmitInteraction>>>;
|
|
102
|
+
/**
|
|
103
|
+
* Emits a discord.js event to its routes, in manifest order and mode, and resolves once they
|
|
104
|
+
* finish. A `once` handler runs on the first call only, as it would in the runtime.
|
|
105
|
+
*/
|
|
106
|
+
event<N extends keyof ClientEvents>(name: N, ...args: ClientEvents[N]): Promise<EventResult>;
|
|
107
|
+
}
|
|
108
|
+
/** `manifestFile` is a `.nectar/manifest.json` written by `nectar build` or `nectar dev`. */
|
|
109
|
+
export declare function createTestApp(manifestFile: string | URL, options?: TestAppOptions): TestApp;
|
|
110
|
+
//#endregion
|
|
111
|
+
//# sourceMappingURL=testing.d.ts.map
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { p as customIdFor, u as registerComponentRoutes } from "./plugins-CGvM19v9.js";
|
|
2
|
+
import { a as bindEvents, c as ModuleRegistry, i as createLogger, l as createSignals, o as createInteractionDispatcher, u as loadManifest } from "./runtime-CZJeZvSL.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { BaseInteraction, Client, CommandInteractionOptionResolver, SnowflakeUtil } from "discord.js";
|
|
7
|
+
import { ApplicationCommandOptionType, ApplicationCommandType, ComponentType, InteractionType } from "discord-api-types/v10";
|
|
8
|
+
//#region src/testing.ts
|
|
9
|
+
/** `manifestFile` is a `.nectar/manifest.json` written by `nectar build` or `nectar dev`. */
|
|
10
|
+
function createTestApp(manifestFile, options = {}) {
|
|
11
|
+
const file = manifestFile instanceof URL ? fileURLToPath(manifestFile) : manifestFile;
|
|
12
|
+
if (!existsSync(file)) throw new Error(`${file} not found. Run \`nectar build\` before the tests.`);
|
|
13
|
+
const { manifest, appDir } = loadManifest(file);
|
|
14
|
+
const client = options.client ?? new Client({ intents: [] });
|
|
15
|
+
const logger = options.logger ?? createLogger();
|
|
16
|
+
const signals = createSignals(logger);
|
|
17
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
18
|
+
const handlers = manifest.routes.filter((r) => r.kind !== "event").map((r) => path.join(appDir, ...r.file.split("/")));
|
|
19
|
+
const state = {
|
|
20
|
+
manifest,
|
|
21
|
+
appDir,
|
|
22
|
+
client,
|
|
23
|
+
modules: new RecordingModules(new Set(handlers), contexts),
|
|
24
|
+
env: options.env ?? "test",
|
|
25
|
+
logger,
|
|
26
|
+
signals,
|
|
27
|
+
services: options.services ?? {}
|
|
28
|
+
};
|
|
29
|
+
registerComponentRoutes(manifest.routes.filter(isComponentRoute));
|
|
30
|
+
const dispatch = createInteractionDispatcher(state);
|
|
31
|
+
const events = bindEvents(state);
|
|
32
|
+
async function invoke({ interaction, responses }) {
|
|
33
|
+
const seen = [];
|
|
34
|
+
const off = signals.on((signal) => {
|
|
35
|
+
if ("trace" in signal && signal.trace === interaction.id) seen.push(signal);
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
await dispatch(interaction);
|
|
39
|
+
} finally {
|
|
40
|
+
off();
|
|
41
|
+
}
|
|
42
|
+
const outcome = seen.at(-1);
|
|
43
|
+
if (outcome === void 0 || !isOutcome(outcome)) throw new Error(`Dispatch of ${interaction.id} ended without an outcome signal.`);
|
|
44
|
+
const context = contexts.get(interaction.id) ?? null;
|
|
45
|
+
contexts.delete(interaction.id);
|
|
46
|
+
return {
|
|
47
|
+
interaction,
|
|
48
|
+
responses,
|
|
49
|
+
context,
|
|
50
|
+
outcome,
|
|
51
|
+
signals: seen
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function component(kind, path, params = {}, fields) {
|
|
55
|
+
const route = manifest.routes.find((r) => r.kind === kind && r.path === path);
|
|
56
|
+
if (route === void 0) throw missing(kind, path);
|
|
57
|
+
const base = {
|
|
58
|
+
customId: customIdFor(route, params),
|
|
59
|
+
replied: false,
|
|
60
|
+
deferred: false
|
|
61
|
+
};
|
|
62
|
+
if (route.kind === "modal") return stub(client, {
|
|
63
|
+
...base,
|
|
64
|
+
type: InteractionType.ModalSubmit
|
|
65
|
+
}, MODAL, fields);
|
|
66
|
+
const componentType = route.selectKind === null ? ComponentType.Button : SELECT_TYPES[route.selectKind];
|
|
67
|
+
const values = route.kind === "select" ? { values: [] } : {};
|
|
68
|
+
return stub(client, {
|
|
69
|
+
...base,
|
|
70
|
+
...values,
|
|
71
|
+
type: InteractionType.MessageComponent,
|
|
72
|
+
componentType
|
|
73
|
+
}, COMPONENT, fields);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
client,
|
|
77
|
+
async command(path, values = {}, fields) {
|
|
78
|
+
const { command, position } = findCommand(manifest, path);
|
|
79
|
+
return invoke(stub(client, {
|
|
80
|
+
type: InteractionType.ApplicationCommand,
|
|
81
|
+
commandType: command.type,
|
|
82
|
+
commandName: command.name,
|
|
83
|
+
options: resolver(client, optionTree(command, position, values)),
|
|
84
|
+
replied: false,
|
|
85
|
+
deferred: false
|
|
86
|
+
}, COMMAND, fields));
|
|
87
|
+
},
|
|
88
|
+
async autocomplete(path, focused, values = {}, fields) {
|
|
89
|
+
const route = manifest.routes.find((r) => r.kind === "autocomplete" && r.path === path);
|
|
90
|
+
if (route?.kind !== "autocomplete") throw missing("autocomplete", path);
|
|
91
|
+
if (!route.options.includes(focused)) throw new Error(`Route ${route.id} has no autocomplete handler for "${focused}". It handles: ${route.options.join(", ")}.`);
|
|
92
|
+
const { command, position } = findCommand(manifest, path);
|
|
93
|
+
return invoke(stub(client, {
|
|
94
|
+
type: InteractionType.ApplicationCommandAutocomplete,
|
|
95
|
+
commandType: ApplicationCommandType.ChatInput,
|
|
96
|
+
commandName: command.name,
|
|
97
|
+
options: resolver(client, optionTree(command, position, values, focused)),
|
|
98
|
+
responded: false
|
|
99
|
+
}, AUTOCOMPLETE, fields));
|
|
100
|
+
},
|
|
101
|
+
async button(path, ...args) {
|
|
102
|
+
return invoke(component("button", path, args[0], args[1]));
|
|
103
|
+
},
|
|
104
|
+
async select(path, ...args) {
|
|
105
|
+
return invoke(component("select", path, args[0], args[1]));
|
|
106
|
+
},
|
|
107
|
+
async modal(path, ...args) {
|
|
108
|
+
return invoke(component("modal", path, args[0], args[1]));
|
|
109
|
+
},
|
|
110
|
+
async event(name, ...args) {
|
|
111
|
+
const binding = events.find((b) => b.name === name);
|
|
112
|
+
if (binding === void 0) throw missing("event", name);
|
|
113
|
+
const failures = [];
|
|
114
|
+
const off = signals.on((signal) => {
|
|
115
|
+
if (signal.type === "event:fail" && signal.event === name) failures.push(signal);
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
await binding.listener(...args);
|
|
119
|
+
} finally {
|
|
120
|
+
off();
|
|
121
|
+
}
|
|
122
|
+
return { failures };
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Hands out interaction handlers behind a proxy that records the context each call receives,
|
|
128
|
+
* keyed by trace ID. Middleware, error boundaries, and event handlers pass through untouched.
|
|
129
|
+
*/
|
|
130
|
+
var RecordingModules = class extends ModuleRegistry {
|
|
131
|
+
handlers;
|
|
132
|
+
contexts;
|
|
133
|
+
proxies = /* @__PURE__ */ new WeakMap();
|
|
134
|
+
constructor(handlers, contexts) {
|
|
135
|
+
super();
|
|
136
|
+
this.handlers = handlers;
|
|
137
|
+
this.contexts = contexts;
|
|
138
|
+
}
|
|
139
|
+
async loadDefault(file, what) {
|
|
140
|
+
return this.record(file, await super.loadDefault(file, what));
|
|
141
|
+
}
|
|
142
|
+
async loadNamed(file, name, what) {
|
|
143
|
+
return this.record(file, await super.loadNamed(file, name, what));
|
|
144
|
+
}
|
|
145
|
+
record(file, handler) {
|
|
146
|
+
if (!this.handlers.has(file) || typeof handler !== "function") return handler;
|
|
147
|
+
let proxy = this.proxies.get(handler);
|
|
148
|
+
if (proxy === void 0) {
|
|
149
|
+
proxy = new Proxy(handler, { apply: (target, self, args) => {
|
|
150
|
+
this.contexts.set(args[0].trace.id, args[0]);
|
|
151
|
+
return Reflect.apply(target, self, args);
|
|
152
|
+
} });
|
|
153
|
+
this.proxies.set(handler, proxy);
|
|
154
|
+
}
|
|
155
|
+
return proxy;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const REPLIES = [
|
|
159
|
+
"reply",
|
|
160
|
+
"deferReply",
|
|
161
|
+
"editReply",
|
|
162
|
+
"followUp",
|
|
163
|
+
"deleteReply"
|
|
164
|
+
];
|
|
165
|
+
const COMMAND = [...REPLIES, "showModal"];
|
|
166
|
+
const COMPONENT = [
|
|
167
|
+
...REPLIES,
|
|
168
|
+
"update",
|
|
169
|
+
"deferUpdate",
|
|
170
|
+
"showModal"
|
|
171
|
+
];
|
|
172
|
+
const MODAL = [
|
|
173
|
+
...REPLIES,
|
|
174
|
+
"update",
|
|
175
|
+
"deferUpdate"
|
|
176
|
+
];
|
|
177
|
+
const AUTOCOMPLETE = ["respond"];
|
|
178
|
+
/** The flag discord.js sets when each response goes through. */
|
|
179
|
+
const FLAGS = {
|
|
180
|
+
reply: "replied",
|
|
181
|
+
update: "replied",
|
|
182
|
+
showModal: "replied",
|
|
183
|
+
deferReply: "deferred",
|
|
184
|
+
deferUpdate: "deferred",
|
|
185
|
+
respond: "responded"
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* A plain object on discord.js's `BaseInteraction` prototype, so `isButton()`, `inGuild()`,
|
|
189
|
+
* `inCachedGuild()`, and the other guards are discord.js's own checks against these fields.
|
|
190
|
+
* Response methods record the call and set the flag discord.js would. Nothing reaches Discord.
|
|
191
|
+
*/
|
|
192
|
+
function stub(client, fields, methods, overrides = {}) {
|
|
193
|
+
const responses = [];
|
|
194
|
+
const interaction = Object.create(BaseInteraction.prototype);
|
|
195
|
+
const own = {
|
|
196
|
+
client,
|
|
197
|
+
id: SnowflakeUtil.generate().toString(),
|
|
198
|
+
guildId: null,
|
|
199
|
+
channelId: null,
|
|
200
|
+
member: null,
|
|
201
|
+
...fields
|
|
202
|
+
};
|
|
203
|
+
for (const method of methods) own[method] = async (options) => {
|
|
204
|
+
responses.push({
|
|
205
|
+
method,
|
|
206
|
+
options
|
|
207
|
+
});
|
|
208
|
+
const flag = FLAGS[method];
|
|
209
|
+
if (flag !== void 0) interaction[flag] = true;
|
|
210
|
+
};
|
|
211
|
+
Object.defineProperties(interaction, Object.getOwnPropertyDescriptors({
|
|
212
|
+
...own,
|
|
213
|
+
...overrides
|
|
214
|
+
}));
|
|
215
|
+
return {
|
|
216
|
+
interaction,
|
|
217
|
+
responses
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function findCommand(manifest, path) {
|
|
221
|
+
const id = `command:${path}`;
|
|
222
|
+
for (const command of manifest.commands) for (const [position, route] of Object.entries(command.handlers)) if (route === id) return {
|
|
223
|
+
command,
|
|
224
|
+
position
|
|
225
|
+
};
|
|
226
|
+
throw missing("command", path);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The options Discord would send for `values`, nested under the subcommand and group at
|
|
230
|
+
* `position`. Each option's type comes from the registration payload. With `focused` this is
|
|
231
|
+
* an autocomplete payload: user, channel, role, and attachment options carry only their ID.
|
|
232
|
+
*/
|
|
233
|
+
function optionTree(command, position, values, focused) {
|
|
234
|
+
const [group, sub] = position.includes("/") ? position.split("/") : [void 0, position];
|
|
235
|
+
let schema = "options" in command.payload ? command.payload.options ?? [] : [];
|
|
236
|
+
for (const name of [group, sub]) if (name) schema = schema.find((o) => o.name === name)?.options ?? [];
|
|
237
|
+
const route = command.handlers[position] ?? command.name;
|
|
238
|
+
const entries = Object.entries(values);
|
|
239
|
+
if (focused !== void 0 && !(focused in values)) entries.push([focused, ""]);
|
|
240
|
+
let tree = entries.map(([name, value]) => {
|
|
241
|
+
const built = option(schema, name, value, route, focused === void 0);
|
|
242
|
+
return name === focused ? {
|
|
243
|
+
...built,
|
|
244
|
+
focused: true
|
|
245
|
+
} : built;
|
|
246
|
+
});
|
|
247
|
+
if (sub) tree = [{
|
|
248
|
+
name: sub,
|
|
249
|
+
type: ApplicationCommandOptionType.Subcommand,
|
|
250
|
+
options: tree
|
|
251
|
+
}];
|
|
252
|
+
if (group) tree = [{
|
|
253
|
+
name: group,
|
|
254
|
+
type: ApplicationCommandOptionType.SubcommandGroup,
|
|
255
|
+
options: tree
|
|
256
|
+
}];
|
|
257
|
+
return tree;
|
|
258
|
+
}
|
|
259
|
+
/** Where discord.js keeps the resolved object for option types that have one. */
|
|
260
|
+
const RESOLVED = {
|
|
261
|
+
[ApplicationCommandOptionType.User]: "user",
|
|
262
|
+
[ApplicationCommandOptionType.Channel]: "channel",
|
|
263
|
+
[ApplicationCommandOptionType.Role]: "role",
|
|
264
|
+
[ApplicationCommandOptionType.Mentionable]: "user",
|
|
265
|
+
[ApplicationCommandOptionType.Attachment]: "attachment"
|
|
266
|
+
};
|
|
267
|
+
function option(schema, name, value, route, resolve) {
|
|
268
|
+
const found = schema.find((o) => o.name === name);
|
|
269
|
+
if (found === void 0) {
|
|
270
|
+
const known = schema.map((o) => o.name);
|
|
271
|
+
throw new TypeError(`Route ${route} has no option "${name}". ${known.length === 0 ? "It takes none." : `It takes: ${known.join(", ")}.`}`);
|
|
272
|
+
}
|
|
273
|
+
const resolved = RESOLVED[found.type];
|
|
274
|
+
if (resolved === void 0) return {
|
|
275
|
+
name,
|
|
276
|
+
type: found.type,
|
|
277
|
+
value
|
|
278
|
+
};
|
|
279
|
+
const id = typeof value === "object" && value !== null && "id" in value ? value.id : void 0;
|
|
280
|
+
return resolve ? {
|
|
281
|
+
name,
|
|
282
|
+
type: found.type,
|
|
283
|
+
value: id,
|
|
284
|
+
[resolved]: value
|
|
285
|
+
} : {
|
|
286
|
+
name,
|
|
287
|
+
type: found.type,
|
|
288
|
+
value: id
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/** discord.js marks the constructor private; it is the same resolver real interactions carry. */
|
|
292
|
+
const Resolver = CommandInteractionOptionResolver;
|
|
293
|
+
function resolver(client, options) {
|
|
294
|
+
return new Resolver(client, options);
|
|
295
|
+
}
|
|
296
|
+
const SELECT_TYPES = {
|
|
297
|
+
string: ComponentType.StringSelect,
|
|
298
|
+
user: ComponentType.UserSelect,
|
|
299
|
+
role: ComponentType.RoleSelect,
|
|
300
|
+
channel: ComponentType.ChannelSelect,
|
|
301
|
+
mentionable: ComponentType.MentionableSelect
|
|
302
|
+
};
|
|
303
|
+
function isComponentRoute(route) {
|
|
304
|
+
return route.kind === "button" || route.kind === "select" || route.kind === "modal";
|
|
305
|
+
}
|
|
306
|
+
const OUTCOMES = /* @__PURE__ */ new Set([
|
|
307
|
+
"interaction:complete",
|
|
308
|
+
"interaction:fail",
|
|
309
|
+
"interaction:reject"
|
|
310
|
+
]);
|
|
311
|
+
function isOutcome(signal) {
|
|
312
|
+
return OUTCOMES.has(signal.type);
|
|
313
|
+
}
|
|
314
|
+
function missing(kind, path) {
|
|
315
|
+
return /* @__PURE__ */ new Error(`No ${kind} route "${path}" in the manifest. Run \`nectar build\` if you just added it.`);
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
export { createTestApp };
|
|
319
|
+
|
|
320
|
+
//# sourceMappingURL=testing.js.map
|