Initial Commit
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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<SlackErrorCode> {}
|
||||
|
||||
interface BaseOpts extends SuaveplanErrorOptions {
|
||||
readonly message?: string;
|
||||
}
|
||||
|
||||
function pickOpts(opts: BaseOpts): SuaveplanErrorOptions {
|
||||
const out: {
|
||||
cause?: unknown;
|
||||
context?: Readonly<Record<string, unknown>>;
|
||||
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<string, unknown> = { ...(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)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user