Errors
Catch FluxerError, read ErrorCodes, and turn API failures into useful replies.
User-facing validation and not-found cases in @fluxerjs/core throw FluxerError with an ErrorCodes value. Prefer checking the code over scraping the message string.
Cheat sheet
| Situation | ErrorCodes |
|---|---|
| Guild / channel / message / member / role missing | GuildNotFound, ChannelNotFound, MessageNotFound, MemberNotFound, RoleNotFound |
| Bad fetch / bulk limit | InvalidFetchLimit |
| Empty or invalid message options | EmptyMessage, InvalidMessageOptions |
| Wrong channel type for send/delete | InvalidChannelType |
Collector missing time / max | CollectorOptionsRequired |
| Collector ended on timer / max | CollectorIdle, CollectorMax |
| Bad token / not ready | InvalidToken, ClientNotReady, NotLoggedIn |
Exact members live on ErrorCodes. Builders (EmbedBuilder, etc.) may still throw plain RangeError for length limits. That is intentional.
Catch and inspect
import { FluxerError, ErrorCodes } from '@fluxerjs/core';
try {
await guild.fetchMember(userId);
} catch (err) {
if (err instanceof FluxerError && err.code === ErrorCodes.MemberNotFound) {
await message.reply('That user is not in this server.');
return;
}
console.error(err);
await message.reply('Something went wrong.');
}FluxerError often wraps a REST failure in cause. HTTP status may live on err.statusCode or err.cause?.statusCode depending on the path.
Control automatic retries
REST retries GET, HEAD, and OPTIONS requests up to three times by default. Other methods run once so a failed mutation is not repeated automatically.
Use retryPolicy when a mutation is safe to retry:
import { Client } from '@fluxerjs/core';
const client = new Client({
rest: {
retries: 3,
retryPolicy: ({ method, routeKey, defaultRetries }) => {
if (method === 'POST' && routeKey === '/channels/:id/messages') return defaultRetries;
return undefined;
},
},
});The policy runs once per request. Return a non-negative integer to set the retry count. Return undefined to keep the normal default, which is the configured retries value for safe methods and zero for mutations. The callback receives the method, the configured retry count, and a sanitized routeKey for matching.
routeKey replaces Snowflake IDs and webhook tokens, drops queries and fragments, and reduces absolute URLs to their origin plus /:external. It never includes request headers or bodies. Custom relative routes may still contain application-specific path segments, so use routeKey for matching rather than logging.
On 429, the wait comes from JSON retry_after when present. If that field is missing (or the body is not JSON), the client uses the Retry-After header. Retryable 5xx responses also honor Retry-After when the header is set.
Automatic retries cannot resolve an uncertain mutation. A timeout may happen after Fluxer accepted the request. Store the unknown result and reconcile it before retrying.
Authentication on absolute URLs
Relative routes and absolute URLs on the configured API origin include the bot token by default. Other absolute URLs do not.
Pass auth: true only when the other origin is trusted and should receive the same token.
Pattern for commands
Keep handlers boring: try the mutation, map known codes to a short reply, log everything else.
try {
await guild.ban(userId, { reason, deleteMessageDays: 1 });
await message.reply(`Banned <@${userId}>.`);
} catch (err) {
if (err instanceof FluxerError && err.code === ErrorCodes.MemberNotFound) {
await message.reply('User not found.');
return;
}
throw err; // or log + generic reply
}See moderation-bot, first-steps-bot, and ping-bot for this pattern.