Spraxium logoSpraxium

Error Fundamentals

The exception layer turns runtime and domain failures into predictable, user-facing behavior. Build the mental model for exception classes, layouts, and policies before writing your first error handler.

Why the exception layer matters

In Spraxium, exceptions transform both technical and domain failures into predictable user-facing behavior. Without a structured exception layer, each handler invents its own error response style, leading to inconsistent UX and noisy logs.

Users will trigger permission denials, cooldown limits, invalid arguments, and unavailable resources as part of normal bot usage. A dedicated exception layer ensures all of these produce consistent, meaningful responses.

Error as contract, not accident

Each relevant failure should have explicit intent: a dedicated exception class representing a concrete scenario such as missing permissions, cooldown violations, or unavailable resources. This creates a shared vocabulary where any engineer can read code and immediately understand what failed and how the user is informed.

The goal is predictability. The same failure type should always produce the same exception class, the same response format, and the same operational signal regardless of where in the codebase the error originates.

The SpraxiumException base class

Every exception in the Spraxium ecosystem extends SpraxiumException, which itself extends the native JavaScript Error. The base class adds structured fields that the pipeline uses for layout resolution, logging decisions, and response rendering. Understanding these fields is essential before creating custom exceptions or working with built-in ones, because each field controls a specific aspect of how the error is processed at runtime.

shop.command.ts
import { SpraxiumException } from '@spraxium/core';

throw new SpraxiumException({
  code: 'ORDER_LIMIT_EXCEEDED',
  message: 'You have exceeded the {{limit}} order limit for today.',
  props: { limit: 5, userId: interaction.user.id },
  shouldReply: true,
  shouldLog: false,
});

For anything beyond a one-off throw, extend SpraxiumException into a named class so the exception carries a stable identity that layouts and monitoring rules can target. The constructor accepts the same options object and forwards it to super, letting each subclass own its code, default message, and typed props interface.

order-limit.exception.ts
import { SpraxiumException } from '@spraxium/core';

interface OrderLimitProps {
  limit: number;
  userId: string;
}

export class OrderLimitExceededException extends SpraxiumException {
  constructor(props: OrderLimitProps) {
    super({
      code: 'ORDER_LIMIT_EXCEEDED',
      message: 'You have exceeded the {{limit}} order limit for today.',
      props,
    });
  }
}

// Usage anywhere in domain code
throw new OrderLimitExceededException({
  limit: 5,
  userId: interaction.user.id,
});
FieldTypePurpose
codestring

Machine-readable identifier for filtering, monitoring, and log queries

messagestring

Human-readable description with double-brace placeholder support

propsRecord

Runtime data for placeholder interpolation and layout rendering

shouldReplybooleanWhether to send a Discord reply, defaults to true
shouldLogbooleanWhether to log the exception, defaults to false
layoutExceptionLayoutClass

Per-instance layout override with highest resolution priority

Built-in exceptions

Spraxium ships with fifteen exception classes that cover the most common failure scenarios in Discord bots. Each class extends SpraxiumException with a predefined code, a default message with placeholder support, and typed props for layout rendering. You can throw any of these directly without creating custom classes, and they are all imported from @spraxium/core.

Using built-in exceptions

The built-in exceptions are ready to use with zero configuration. Every class accepts an optional props object that populates placeholders in the default message and provides context for layout rendering. The example below shows three common scenarios you will encounter in most bots.

shop.command.ts
import {
  CooldownException,
  PermissionDeniedException,
  MaintenanceException,
} from '@spraxium/core';

// User must wait before retrying the command
throw new CooldownException({ seconds: 10 });

// User lacks the required permission
throw new PermissionDeniedException({ permission: 'MANAGE_MESSAGES' });

// Feature is temporarily disabled
throw new MaintenanceException({ feature: 'Leaderboard', eta: '2 hours' });

Where exceptions enter runtime flow

In practice, an exception is raised inside domain code such as a service, guard, handler, or listener, and then resolved by the core pipeline that decides the rendering strategy and logging behavior. Understanding this flow is essential because it determines where your exception classes originate, how layouts are selected to present errors to users, and what kind of operational data gets recorded for diagnostics and monitoring.

StageResponsibilityExpected outcome
Exception throw siteDomain code signals a policy or rule failureExplicit and traceable failure state
Layout resolution

Core selects layout by override, class mapping, or fallback

Consistent user-facing response
ObservabilityPipeline records handled and unhandled failuresPredictable operational diagnostics