From 4d8c787dbc7900eaaa0d11349e2c88f607c6fe9b Mon Sep 17 00:00:00 2001 From: nav Date: Wed, 8 Apr 2026 20:33:25 +0000 Subject: [PATCH] feat(protocol): add shared protocol error helpers --- src/errors.ts | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + 2 files changed, 94 insertions(+) create mode 100644 src/errors.ts diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..0c45801 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,93 @@ +import type { BuiltinEnvelope, ErrorPayload, ProtocolErrorCode, TypedBuiltinEnvelope } from "./types.js"; + +export type ProtocolErrorCategory = + | "configuration" + | "protocol" + | "authentication" + | "pairing" + | "runtime"; + +export interface ProtocolErrorOptions { + message?: string; + details?: Record; + cause?: unknown; +} + +const protocolErrorCategories: Record = { + MALFORMED_MESSAGE: "protocol", + UNSUPPORTED_PROTOCOL_VERSION: "protocol", + IDENTIFIER_NOT_ALLOWED: "protocol", + PAIRING_REQUIRED: "pairing", + PAIRING_EXPIRED: "pairing", + ADMIN_NOTIFICATION_FAILED: "pairing", + AUTH_FAILED: "authentication", + NONCE_COLLISION: "authentication", + RATE_LIMITED: "authentication", + RE_PAIR_REQUIRED: "authentication", + CLIENT_OFFLINE: "runtime", + INTERNAL_ERROR: "runtime" +}; + +export function getProtocolErrorCategory(code: ProtocolErrorCode): ProtocolErrorCategory { + return protocolErrorCategories[code]; +} + +export class YonexusProtocolError extends Error { + readonly code: ProtocolErrorCode; + readonly category: ProtocolErrorCategory; + readonly details?: Record; + override readonly cause?: unknown; + + constructor(code: ProtocolErrorCode, options: ProtocolErrorOptions = {}) { + super(options.message ?? code); + this.name = "YonexusProtocolError"; + this.code = code; + this.category = getProtocolErrorCategory(code); + this.details = options.details; + this.cause = options.cause; + } + + toPayload(): ErrorPayload { + return { + code: this.code, + message: this.message, + details: this.details + }; + } + + toEnvelope(timestamp: number = Math.floor(Date.now() / 1000)): TypedBuiltinEnvelope<"error"> { + return { + type: "error", + timestamp, + payload: this.toPayload() + }; + } +} + +export function createProtocolError( + code: ProtocolErrorCode, + options: ProtocolErrorOptions = {} +): YonexusProtocolError { + return new YonexusProtocolError(code, options); +} + +export function protocolErrorFromPayload( + payload: ErrorPayload, + cause?: unknown +): YonexusProtocolError { + return new YonexusProtocolError(payload.code, { + message: payload.message, + details: payload.details, + cause + }); +} + +export function isProtocolError(value: unknown): value is YonexusProtocolError { + return value instanceof YonexusProtocolError; +} + +export function isProtocolErrorEnvelope( + envelope: BuiltinEnvelope +): envelope is TypedBuiltinEnvelope<"error"> { + return envelope.type === "error"; +} diff --git a/src/index.ts b/src/index.ts index 8212ccf..418e72d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from "./types.js"; export * from "./codec.js"; +export * from "./errors.js";