Files
suaveplan/packages/networking/events/broadcast-channel/src/errors/codes.ts
2026-05-27 23:47:53 +01:00

131 lines
4.6 KiB
TypeScript

import type { SuaveplanErrorOptions } from "@suaveplan/error";
import { defineCodes, SuaveplanError } from "@suaveplan/error";
/**
* Stable error-code registry for `@suaveplan/broadcast-channel`. Every error
* thrown by the package surfaces one of these 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.
*/
export const BROADCAST_CHANNEL_CODES = defineCodes("BROADCAST_CHANNEL", [
"CHANNEL_CLOSED",
"MESSAGE_MALFORMED",
"PAYLOAD_TOO_LARGE",
"STORAGE_FAILED",
"ASK_TIMEOUT",
"ASK_NO_RESPONDER",
"ASK_HANDLER_DUPLICATE",
] as const);
/** Union of every fully-qualified code emitted by this package. */
export type BroadcastChannelErrorCode =
(typeof BROADCAST_CHANNEL_CODES)[keyof typeof BROADCAST_CHANNEL_CODES];
/** Base class for every typed broadcast-channel error. */
export abstract class BroadcastChannelError extends SuaveplanError<BroadcastChannelErrorCode> {}
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;
}
/** Send attempted on a channel that has not been opened or has been closed. */
export class BroadcastChannelClosedError extends BroadcastChannelError {
public constructor(opts: BaseOpts = {}) {
super(
BROADCAST_CHANNEL_CODES.CHANNEL_CLOSED,
opts.message ?? "broadcast-channel.errors.channelClosed",
pickOpts(opts)
);
}
}
/** An inbound frame failed `ChannelMessageSchema` validation. */
export class BroadcastChannelMessageMalformedError extends BroadcastChannelError {
public constructor(opts: BaseOpts = {}) {
super(
BROADCAST_CHANNEL_CODES.MESSAGE_MALFORMED,
opts.message ?? "broadcast-channel.errors.messageMalformed",
pickOpts(opts)
);
}
}
/** Outbound payload exceeded the configured `maxMessageBytes` budget. */
export class BroadcastChannelPayloadTooLargeError extends BroadcastChannelError {
public constructor(
opts: BaseOpts & {
readonly sizeBytes?: number;
readonly maxBytes?: number;
} = {}
) {
const ctx: Record<string, unknown> = { ...(opts.context ?? {}) };
if (opts.sizeBytes !== undefined) ctx["sizeBytes"] = opts.sizeBytes;
if (opts.maxBytes !== undefined) ctx["maxBytes"] = opts.maxBytes;
super(
BROADCAST_CHANNEL_CODES.PAYLOAD_TOO_LARGE,
opts.message ?? "broadcast-channel.errors.payloadTooLarge",
pickOpts({ ...opts, context: ctx })
);
}
}
/** Injected `StorageLike` rejected a get/set/list call in the fallback transport. */
export class BroadcastChannelStorageFailedError extends BroadcastChannelError {
public constructor(opts: BaseOpts = {}) {
super(
BROADCAST_CHANNEL_CODES.STORAGE_FAILED,
opts.message ?? "broadcast-channel.errors.storageFailed",
pickOpts(opts)
);
}
}
/** Pending `ask()` timed out before any peer responded. */
export class BroadcastChannelAskTimeoutError extends BroadcastChannelError {
public constructor(opts: BaseOpts = {}) {
super(
BROADCAST_CHANNEL_CODES.ASK_TIMEOUT,
opts.message ?? "broadcast-channel.errors.askTimeout",
pickOpts(opts)
);
}
}
/**
* `ask()` was called with `requireResponder: true` and timed out without any
* peer claiming the handler key. Distinct from `ASK_TIMEOUT` so callers can
* drive different retry policies for "no listener" vs. "slow listener".
*/
export class BroadcastChannelAskNoResponderError extends BroadcastChannelError {
public constructor(opts: BaseOpts = {}) {
super(
BROADCAST_CHANNEL_CODES.ASK_NO_RESPONDER,
opts.message ?? "broadcast-channel.errors.askNoResponder",
pickOpts(opts)
);
}
}
/** `onAsk()` called twice for the same `handlerKey` without disposal in between. */
export class BroadcastChannelAskHandlerDuplicateError extends BroadcastChannelError {
public constructor(opts: BaseOpts = {}) {
super(
BROADCAST_CHANNEL_CODES.ASK_HANDLER_DUPLICATE,
opts.message ?? "broadcast-channel.errors.askHandlerDuplicate",
pickOpts(opts)
);
}
}