Initial Commit
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user