import type { SuaveplanErrorOptions } from "@suaveplan/error"; import { defineCodes, SuaveplanError } from "@suaveplan/error"; /** * Stable error-code registry for `@suaveplan/imessage-adapter`. Every error * thrown by the package surfaces one of these ten 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 IMESSAGE_CODES = defineCodes("IMESSAGE", [ "DB_OPEN_FAILED", "DB_QUERY_FAILED", "WATCHER_NOT_RUNNING", "WATCHER_ALREADY_RUNNING", "ADAPTER_NOT_INITIALIZED", "INVALID_CONFIG", "POLL_ABORTED", "POLL_FAILED", "ATTACHMENT_PARSE_FAILED", "APPLE_DATE_PARSE_FAILED", ] as const); /** Union of every fully-qualified code emitted by this package. */ export type IMessageErrorCode = (typeof IMESSAGE_CODES)[keyof typeof IMESSAGE_CODES]; /** Base class for every typed iMessage adapter error. */ export abstract class IMessageError 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; } /** Failed to open the chat.db SQLite database. */ export class IMessageDbOpenFailedError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.DB_OPEN_FAILED, opts.message ?? "imessage.errors.dbOpenFailed", pickOpts(opts) ); } } /** SQLite query against chat.db failed. */ export class IMessageDbQueryFailedError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.DB_QUERY_FAILED, opts.message ?? "imessage.errors.dbQueryFailed", pickOpts(opts) ); } } /** stop() called while watcher is not running. */ export class IMessageWatcherNotRunningError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.WATCHER_NOT_RUNNING, opts.message ?? "imessage.errors.watcherNotRunning", pickOpts(opts) ); } } /** start() called twice. */ export class IMessageWatcherAlreadyRunningError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.WATCHER_ALREADY_RUNNING, opts.message ?? "imessage.errors.watcherAlreadyRunning", pickOpts(opts) ); } } /** poll() called before initialize(). */ export class IMessageAdapterNotInitializedError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.ADAPTER_NOT_INITIALIZED, opts.message ?? "imessage.errors.adapterNotInitialized", pickOpts(opts) ); } } /** Zod schema rejected the config. */ export class IMessageInvalidConfigError extends IMessageError { public constructor( opts: BaseOpts & { readonly issues?: ReadonlyArray>>; } = {} ) { const ctx: Record = { ...(opts.context ?? {}) }; if (opts.issues !== undefined) ctx["issues"] = opts.issues; super( IMESSAGE_CODES.INVALID_CONFIG, opts.message ?? "imessage.errors.invalidConfig", { ...pickOpts(opts), context: ctx } ); } } /** poll() / start() aborted by AbortSignal. */ export class IMessagePollAbortedError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.POLL_ABORTED, opts.message ?? "imessage.errors.pollAborted", pickOpts(opts) ); } } /** poll() failed for a reason other than abort. */ export class IMessagePollFailedError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.POLL_FAILED, opts.message ?? "imessage.errors.pollFailed", pickOpts(opts) ); } } /** Attachment row could not be parsed. */ export class IMessageAttachmentParseError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.ATTACHMENT_PARSE_FAILED, opts.message ?? "imessage.errors.attachmentParseFailed", pickOpts(opts) ); } } /** Apple-epoch timestamp could not be decoded. */ export class IMessageAppleDateParseError extends IMessageError { public constructor(opts: BaseOpts = {}) { super( IMESSAGE_CODES.APPLE_DATE_PARSE_FAILED, opts.message ?? "imessage.errors.appleDateParseFailed", pickOpts(opts) ); } }