spearkit

API reference

Every symbol spearkit exports, in addition to the entire re-exported discord.js surface. Import any of these from "spearkit".

import { SpearClient, command, option, event, button, modal, row /* … */ } from "spearkit";

Client

class SpearClient extends Client

A discord.js Client with registries and interaction routing wired up.

new SpearClient(options?: SpearClientOptions)
MemberTypeDescription
commandsCommandRegistrySlash command registry + dispatcher.
eventsEventRegistryEvent listener registry.
componentsComponentRegistryButton/select/modal router.
register(...items: Registerable[])thisRoute each item to the matching registry.
use(...plugins: SpearPlugin[])Promise<this>Run each plugin's setup.
load(dir: string, options?: LoadOptions)Promise<number>Import a directory and register its exports. Returns count.
start(token?: string)Promise<this>Log in (falls back to DISCORD_TOKEN).
deployCommands(options?: { guildId?: string })Promise<DeployResult>Push commands using the client's REST. Call after ready.

Inherits everything from discord.js Client (on, once, login, ws, rest, application, user, …).

type SpearClientOptions = Partial<ClientOptions>

Same as discord.js ClientOptions, but intents may be omitted (defaults to Intents.default).

const Intents

Ready-made intent presets (arrays of GatewayIntentBits).

KeyContents
Intents.none[]
Intents.default[Guilds]
Intents.guilds[Guilds, GuildMembers]
Intents.messages[Guilds, GuildMessages, MessageContent]
Intents.allEvery intent (includes privileged).

type Registerable = SlashCommand | EventDef | ComponentDef

The union accepted by SpearClient.register.


Commands

function command<O, R>(config): SlashCommand

Define a leaf slash command.

interface CommandConfig<O extends OptionMap, R> {
  name: string;
  description: string;
  options?: O;
  defaultMemberPermissions?: PermissionResolvable | null;
  nsfw?: boolean;
  guildOnly?: boolean;
  nameLocalizations?: LocalizationMap;
  descriptionLocalizations?: LocalizationMap;
  run: (ctx: CommandContext<O>) => Awaitable<R>;
}

function commandGroup(config: CommandGroupConfig): SlashCommand

Define a command that routes to subcommands and/or subcommand groups.

interface CommandGroupConfig {
  name: string;
  description: string;
  subcommands?: Record<string, Subcommand>;
  groups?: Record<string, SubcommandGroup>;
  defaultMemberPermissions?: PermissionResolvable | null;
  nsfw?: boolean;
  guildOnly?: boolean;
  nameLocalizations?: LocalizationMap;
  descriptionLocalizations?: LocalizationMap;
}

function subcommand<O, R>(config): Subcommand

interface SubcommandConfig<O extends OptionMap, R> {
  description: string;
  options?: O;
  nameLocalizations?: LocalizationMap;
  descriptionLocalizations?: LocalizationMap;
  run: (ctx: CommandContext<O>) => Awaitable<R>;
}

function subcommandGroup(config: SubcommandGroupConfig): SubcommandGroup

interface SubcommandGroupConfig {
  description: string;
  subcommands: Record<string, Subcommand>;
  nameLocalizations?: LocalizationMap;
  descriptionLocalizations?: LocalizationMap;
}

class SlashCommand

MemberTypeDescription
namestringTop-level command name.
hasAutocompletebooleanTrue if any option declares autocomplete.
toJSON()RESTPostAPIChatInputApplicationCommandsJSONBodyREST payload.
execute(interaction)Promise<void>Run for a chat-input interaction.
autocomplete(interaction)Promise<void>Run autocomplete for the focused option.

class CommandContext<O> extends BaseContext<ChatInputCommandInteraction>

MemberTypeDescription
optionsResolvedOptions<O>Resolved, fully-typed option values.
commandNamestringInvoked command name.
subcommandstring | nullInvoked subcommand, if any.
showModal(modal)Promise<void>Present a modal.

Plus all BaseContext members.

class CommandRegistry

MemberTypeDescription
add(...commands: SlashCommand[])thisRegister commands (override by name).
remove(name: string)booleanRemove a command.
get(name: string)SlashCommand | undefinedLook up a command.
all()SlashCommand[]All commands.
namesstring[]All command names.
sizenumberCount.
onError(handler: CommandErrorHandler)thisSet the error handler.
toJSON()RESTPostAPIApplicationCommandsJSONBody[]Serialise all commands.
handle(interaction)Promise<void>Dispatch a chat-input interaction.
handleAutocomplete(interaction)Promise<void>Dispatch an autocomplete interaction.
deploy(options: DeployOptions)Promise<DeployResult>Push commands to discord.
type CommandErrorHandler = (error: Error, interaction: ChatInputCommandInteraction) => Awaitable<void>;
interface DeployOptions { token?: string; applicationId: string; guildId?: string; rest?: REST; }
type DeployResult = RESTPutAPIApplicationCommandsResult | RESTPutAPIApplicationGuildCommandsResult;

Options

const option

Type-safe option builders. Each returns an OptionDef whose resolved value type is inferred (required → value, optional → value | undefined, choices → literal union).

BuilderResolved typeExtra config
option.string(config)stringchoices?, minLength?, maxLength?, autocomplete?
option.integer(config)numberchoices?, minValue?, maxValue?, autocomplete?
option.number(config)numberchoices?, minValue?, maxValue?, autocomplete?
option.boolean(config)boolean
option.user(config)User
option.channel(config)channel unionchannelTypes?
option.role(config)Role | APIRole
option.mentionable(config)user/role/member
option.attachment(config)Attachment

Common config (BaseConfig):

{
  description: string;
  required?: boolean;            // default false
  nameLocalizations?: LocalizationMap;
  descriptionLocalizations?: LocalizationMap;
}

choices items are OptionChoice<V>:

interface OptionChoice<V extends string | number = string | number> {
  name: string;
  value: V;
  nameLocalizations?: LocalizationMap;
}

autocomplete:

type AutocompleteHandler<V extends string | number> =
  (ctx: AutocompleteContext) => Awaitable<OptionChoice<V>[]>;

Option types

SymbolDescription
interface OptionDef<TValue, TRequired>A described option (phantom-typed for inference).
type AnyOptionDefOptionDef<OptionValue, boolean>.
type OptionMapRecord<string, AnyOptionDef>.
type ResolvedOption<O>The handler value for one option.
type ResolvedOptions<O>The handler's options object.
type OptionValueUnion of all possible resolved values.
type AllowedChannelTypeChannel types valid for a channel option.
function toAPIOption(name, def)Serialise one option to REST.
function readOption(resolver, name, def)Read a resolved value (null → undefined).
function optionsHaveAutocomplete(options)True if any option has autocomplete.

class AutocompleteContext

MemberTypeDescription
interactionAutocompleteInteractionRaw interaction.
client / user / guild / guildIdConvenience accessors.
commandNamestringCommand being completed.
focusedNamestringName of the focused option.
valuestringCurrent partial value typed by the user.
respond(choices: OptionChoice[])Promise<void>Send up to 25 suggestions.

Events

function event(name, run): EventDef / function event(config): EventDef

type EventHandler<E extends keyof ClientEvents> = (...args: ClientEvents[E]) => Awaitable<void>;
interface EventConfig<E extends keyof ClientEvents> { name: E; once?: boolean; run: EventHandler<E>; }
interface EventDef { name: keyof ClientEvents; once: boolean; attach(client: Client): void; detach(client: Client): void; }

Thrown errors and rejected promises are routed to the client's error event.

class EventRegistry

MemberTypeDescription
add(...defs: EventDef[])thisRegister listeners.
sizenumberCount.
attachAll(client: Client)voidAttach every listener.
detachAll(client: Client)voidDetach every listener.

Components

Builders

FunctionReturnsNotes
button(config)Button<P>Interactive button.
linkButton(config)ButtonBuilderURL button, no handler.
stringSelect(config)StringSelect<P>String select; takes options.
userSelect(config)UserSelect<P>User select.
roleSelect(config)RoleSelect<P>Role select.
channelSelect(config)ChannelSelect<P>Channel select; takes channelTypes?.
mentionableSelect(config)MentionableSelect<P>User + role select.
modal(config)Modal<P>Modal with fields.
textInput(config)TextInputDefA modal text-input field.
row(...components)ActionRowBuilder<C>Wrap components in a row.

Each registrable component (Button, StringSelect, …, Modal) extends its routing interface and adds build(...args: BuildArgs<P>), which returns the discord.js builder. build requires exactly the params declared in the id pattern.

interface ButtonConfig<P extends string, R> {
  id: P;                       // pattern: "name" or "name:{param}"
  label?: string;
  style?: ButtonStyleInput;    // "Primary" | "Secondary" | "Success" | "Danger" | ButtonStyle.*
  emoji?: ComponentEmojiResolvable;
  disabled?: boolean;
  run: (ctx: ButtonContext<Params<P>>) => Awaitable<R>;
}

interface LinkButtonConfig { url: string; label?: string; emoji?: ComponentEmojiResolvable; disabled?: boolean; }

interface StringSelectConfig<P extends string, R> {
  id: P;
  options: readonly SelectMenuComponentOptionData[];
  placeholder?: string; minValues?: number; maxValues?: number; disabled?: boolean;
  run: (ctx: StringSelectContext<Params<P>>) => Awaitable<R>;
}

interface EntitySelectConfig<P extends string> {
  id: P; placeholder?: string; minValues?: number; maxValues?: number; disabled?: boolean;
}
// user/role/mentionable selects take EntitySelectConfig & { run };
// channelSelect additionally takes { channelTypes?: readonly ChannelType[] }.

function textInput(config: {
  label: string;
  style?: TextInputStyleInput;     // "Short" | "Paragraph" | TextInputStyle
  placeholder?: string; required?: boolean; minLength?: number; maxLength?: number; value?: string;
}): TextInputDef;

interface ModalConfig<P extends string, F extends Record<string, TextInputDef>, R> {
  id: P;
  title: string;
  fields: F;
  run: (ctx: ModalContext<Params<P>, keyof F & string>) => Awaitable<R>;
}

Component contexts

ClassExtra members
MessageComponentContext<P, I>params, customId, message, update(input), deferUpdate(), showModal(modal) (+ BaseContext)
ButtonContext<P>
StringSelectContext<P>values: string[], value: string | undefined
UserSelectContext<P>values, users, members
RoleSelectContext<P>values, roles
ChannelSelectContext<P>values, channels
MentionableSelectContext<P>values, users, roles, members
ModalContext<P, F>params, fields: Record<F, string>, customId (+ BaseContext)

class ComponentRegistry

MemberTypeDescription
add(...defs: ComponentDef[])thisRegister components (override by namespace).
onError(handler: ComponentErrorHandler)thisSet the error handler.
sizenumberCount.
handle(interaction: Interaction)Promise<boolean>Route an interaction; true if matched.
type ComponentErrorHandler = (error: Error, interaction: RepliableInteraction) => Awaitable<void>;
type ComponentDef = ButtonRoute | StringSelectRoute | UserSelectRoute | RoleSelectRoute
  | ChannelSelectRoute | MentionableSelectRoute | ModalRoute;

Custom-id codec

SymbolDescription
type ParamNames<S>Union of {param} names in a pattern.
type Params<S>The params object a pattern resolves to.
type BuildArgs<S>build() args (none when no params).
const MAX_CUSTOM_ID_LENGTH100.
function compilePattern(pattern)CompiledPattern { pattern, namespace, paramNames }.
function buildCustomId(compiled, params)Encode a concrete id.
function parseCustomId(customId)ParsedCustomId { namespace, values }.
function paramsFromValues(paramNames, values)Map values onto names.

Contexts (shared)

abstract class BaseContext<I>

The base for every interaction context.

MemberTypeDescription
interactionIRaw discord.js interaction.
client / user / member / guild / guildId / channel / channelId / localeAccessors.
deferred / repliedbooleanInteraction state.
reply(input)Promise<InteractionResponse>Initial response.
replyEphemeral(input)Promise<InteractionResponse>Hidden reply.
defer({ ephemeral? })Promise<InteractionResponse>Acknowledge, respond later.
editReply(input)Promise<Message>Edit the response.
followUp(input)Promise<Message>Additional message.
send(input)Promise<void>State-aware reply/edit/followUp.
error(message)Promise<void>State-aware ephemeral error.
type ReplyData = InteractionReplyOptions & { ephemeral?: boolean };
type ReplyInput = string | ReplyData;
function normalizeReply(input: ReplyInput): InteractionReplyOptions;
function asEphemeral(input: ReplyInput): ReplyData;

Plugins

interface SpearPlugin { name: string; setup(client: SpearClient): Awaitable<void>; }
function definePlugin(plugin: SpearPlugin): SpearPlugin;

Loading

interface LoadOptions { extensions?: readonly string[]; recursive?: boolean; } // defaults: [.js,.mjs,.cjs], true
function collectModules(dir: string, options?: LoadOptions): Promise<Registerable[]>;
function loadInto(client: SpearClient, dir: string, options?: LoadOptions): Promise<number>;

SpearClient.load(dir, options?) is the method form of loadInto.


Added in 0.2

New subsystems, each with a dedicated guide. The SpearClient options { logger?, dotenv?, cooldown?, prefix?, usage? } configure them.

Logging — guide

class Logger { debug/info/warn/error(message: string, options?: { error?: Error; data?: Record<string, LogValue> }): void; child(scope: string): Logger; setLevel(level: LogThreshold): this; enabled(level: LogLevel): boolean; }
type LogLevel = "debug" | "info" | "warn" | "error";
type LogThreshold = LogLevel | "silent";
function consoleSink(entry: LogEntry): void;
function toError(value: unknown): Error;
// client.logger is a Logger; new SpearClient({ logger: { level: "debug" } })

Environment — guide

function parseEnv(content: string): Record<string, string>;
function loadEnv(options?: { path?: string; override?: boolean }): Record<string, string>;
const env: { string(k, fallback?); number(k, fallback?); boolean(k, fallback?); require(k): string };
// client auto-loads .env on start(); disable/configure via the dotenv option

Cooldowns — guide

interface CooldownConfig { duration: number; scope?: "user" | "guild" | "channel" | "global"; exempt?: { users?: string[]; roles?: string[] }; overrides?: { users?: Record<string, number>; roles?: Record<string, number> }; message?: string | ((remainingMs: number) => string); }
class CooldownManager { consume(bucket, input, actor, now?); peek(...); reset(...); clear(); }
// command({ cooldown: number | CooldownConfig }); new SpearClient({ cooldown }); client.cooldowns

Scheduled tasks — guide

function task(config: { name: string; cron?: string; interval?: number; runOnStart?: boolean; run: (client: SpearClient) => Awaitable<void> }): ScheduledTask;
function cron(expression: string): CronExpression; // .next(from?: Date): Date
class TaskScheduler { add/remove/list/size/active/start/stop }
// client.register(task(...)); client.schedule(config); client.scheduler

Prefix commands — guide

function prefixCommand(config: { name: string; aliases?: string[]; description?: string; cooldown?: CooldownInput; run: (ctx: PrefixContext) => Awaitable<R> }): PrefixCommand;
class PrefixContext { message; commandName; args: string[]; rest: string; reply(content); send(content); }
// new SpearClient({ prefix: "!" | string[] | { prefix, mention?, ignoreBots?, caseInsensitive? } }); client.prefix
// reading others' content needs the privileged MessageContent intent (Intents.messages)

Usage tracking — guide

interface UsageEvent { type: "command" | "prefix" | "component" | "event"; name: string; userId?; userTag?; guildId?; channelId?; detail?; timestamp: Date; }
interface UsageStore { record(event): Awaitable<void>; all(): Awaitable<readonly UsageEvent[]>; }
class MemoryUsageStore { record; all; size; byUser(id); clear; }
class JsonFileUsageStore { constructor(path: string); record; all; }
class UsageTracker { setStore(store); reportTo(channelId, format?); track(event); store; enabled; }
// new SpearClient({ usage: { store?, channel?, format? } }); client.usage

On this page