162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
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<IMessageErrorCode> {}
|
|
|
|
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;
|
|
}
|
|
|
|
/** 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<Readonly<Record<string, unknown>>>;
|
|
} = {}
|
|
) {
|
|
const ctx: Record<string, unknown> = { ...(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)
|
|
);
|
|
}
|
|
}
|