import type { SuaveplanErrorOptions } from "@suaveplan/error"; import { defineCodes, SuaveplanError } from "@suaveplan/error"; /** * Stable error-code registry for `@suaveplan/slack-client`. Every error * thrown by the package surfaces one of these eight codes so downstream * consumers — loggers, problem+json responders, retry wrappers — can map * to retry policy, HTTP status, and i18n keys without re-implementing the * classifier. * * The registry uses the defineCodes builder from `@suaveplan/error`, * which produces a frozen `{ REASON: "SLACK.REASON" }` record at compile * time so the literal-typed union is preserved through the public surface. */ export const SLACK_CODES = defineCodes("SLACK", [ "API_ERROR", "AUTH_ERROR", "RATE_LIMITED", "CHANNEL_NOT_FOUND", "WORKSPACE_NOT_FOUND", "CONFIG_MISSING", "WEBHOOK_FAILED", "TIMEOUT", ] as const); /** Union of every fully-qualified code emitted by this package. */ export type SlackErrorCode = (typeof SLACK_CODES)[keyof typeof SLACK_CODES]; /** Base class for every typed Slack error. */ export abstract class SlackError extends SuaveplanError {} interface BaseOpts extends SuaveplanErrorOptions { readonly message?: string; } function pickOpts(opts: BaseOpts): SuaveplanErrorOptions { const out: { cause?: unknown; context?: Readonly>; traceId?: string; } = {}; if (opts.cause !== undefined) out.cause = opts.cause; if (opts.context !== undefined) out.context = opts.context; if (opts.traceId !== undefined) out.traceId = opts.traceId; return out; } /** Slack API returned a non-ok response (default mapping for other 4xx/5xx). */ export class SlackApiError extends SlackError { public constructor(opts: BaseOpts = {}) { super( SLACK_CODES.API_ERROR, opts.message ?? "slack.errors.apiError", pickOpts(opts) ); } } /** Token rejected or missing (401/403, invalid_auth, token_revoked, ...). */ export class SlackAuthError extends SlackError { public constructor(opts: BaseOpts = {}) { super( SLACK_CODES.AUTH_ERROR, opts.message ?? "slack.errors.authError", pickOpts(opts) ); } } /** Slack rate-limited the caller; honour `context.retryAfterMs`. */ export class SlackRateLimitedError extends SlackError { public constructor( opts: BaseOpts & { readonly retryAfterMs?: number } = {} ) { const ctx: Record = { ...(opts.context ?? {}) }; if (opts.retryAfterMs !== undefined) ctx["retryAfterMs"] = opts.retryAfterMs; super( SLACK_CODES.RATE_LIMITED, opts.message ?? "slack.errors.rateLimited", pickOpts({ ...opts, context: ctx }) ); } } /** Channel does not exist or is inaccessible (404, channel_not_found, ...). */ export class SlackChannelNotFoundError extends SlackError { public constructor(opts: BaseOpts = {}) { super( SLACK_CODES.CHANNEL_NOT_FOUND, opts.message ?? "slack.errors.channelNotFound", pickOpts(opts) ); } } /** Workspace not found (team_not_found, no_workspace). */ export class SlackWorkspaceNotFoundError extends SlackError { public constructor(opts: BaseOpts = {}) { super( SLACK_CODES.WORKSPACE_NOT_FOUND, opts.message ?? "slack.errors.workspaceNotFound", pickOpts(opts) ); } } /** Required configuration value (token, webhookUrl, ...) is missing. */ export class SlackConfigMissingError extends SlackError { public constructor(opts: BaseOpts = {}) { super( SLACK_CODES.CONFIG_MISSING, opts.message ?? "slack.errors.configMissing", pickOpts(opts) ); } } /** Incoming-webhook POST failed with a non-2xx response. */ export class SlackWebhookFailedError extends SlackError { public constructor(opts: BaseOpts = {}) { super( SLACK_CODES.WEBHOOK_FAILED, opts.message ?? "slack.errors.webhookFailed", pickOpts(opts) ); } } /** Slack request timed out (internal `timeoutMs` or caller `AbortSignal`). */ export class SlackTimeoutError extends SlackError { public constructor(opts: BaseOpts = {}) { super( SLACK_CODES.TIMEOUT, opts.message ?? "slack.errors.timeout", pickOpts(opts) ); } }