131 lines
4.6 KiB
TypeScript
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)
|
|
);
|
|
}
|
|
}
|