| /** | |
| * @fileoverview Structured error classes for inbox sync operations. | |
| * | |
| * This module provides type-safe error handling for email provider integrations. | |
| * Errors are classified into two categories: | |
| * - {@link InboxAuthError} - Authentication/authorization failures | |
| * - {@link InboxSyncError} - Transient sync issues (network, rate limits, etc.) | |
| * | |
| * @example | |
| * ```typescript | |
| * import { InboxAuthError, isInboxAuthError } from "@midday/inbox/errors"; | |
| * | |
| * try { | |
| * await connector.getAttachments(options); | |
| * } catch (error) { | |
| * if (isInboxAuthError(error)) { | |
| * if (error.requiresReauth) { | |
| * // User must reconnect their account | |
| * } | |
| * } | |
| * } | |
| * ``` | |
| */ | |
| /** | |
| * Error codes for authentication-related errors. | |
| * | |
| * | Code | Description | | |
| * |------|-------------| | |
| * | `token_expired` | Access token has expired | | |
| * | `token_invalid` | Access token is malformed or invalid | | |
| * | `refresh_token_expired` | Refresh token has expired (typically 90 days) | | |
| * | `refresh_token_invalid` | Refresh token is missing or invalid | | |
| * | `unauthorized` | General 401 unauthorized response | | |
| * | `forbidden` | Permission denied (403) | | |
| * | `consent_required` | User must re-consent to permissions (Outlook) | | |
| * | `mfa_required` | Multi-factor authentication required (Outlook) | | |
| */ | |
| export type InboxAuthErrorCode = | |
| | "token_expired" | |
| | "token_invalid" | |
| | "refresh_token_expired" | |
| | "refresh_token_invalid" | |
| | "unauthorized" | |
| | "forbidden" | |
| | "consent_required" | |
| | "mfa_required"; | |
| /** | |
| * Error codes for sync-related errors. | |
| * | |
| * | Code | Description | | |
| * |------|-------------| | |
| * | `fetch_failed` | General failure fetching data from provider | | |
| * | `rate_limited` | API rate limit exceeded (429) | | |
| * | `network_error` | Network connectivity issue | | |
| * | `provider_error` | Provider-specific error | | |
| */ | |
| export type InboxSyncErrorCode = | |
| | "fetch_failed" | |
| | "rate_limited" | |
| | "network_error" | |
| | "provider_error"; | |
| /** | |
| * Supported email providers. | |
| */ | |
| export type InboxProvider = "gmail" | "outlook"; | |
| /** | |
| * Options for constructing an {@link InboxAuthError}. | |
| */ | |
| interface InboxAuthErrorOptions { | |
| /** The specific error code identifying the auth failure type */ | |
| code: InboxAuthErrorCode; | |
| /** The email provider that generated this error */ | |
| provider: InboxProvider; | |
| /** Human-readable error message */ | |
| message: string; | |
| /** Whether user intervention is required to resolve this error */ | |
| requiresReauth: boolean; | |
| /** The original error that caused this error, if any */ | |
| cause?: Error; | |
| } | |
| /** | |
| * Structured error for authentication and authorization issues. | |
| * | |
| * Use the `requiresReauth` property to determine if user intervention is needed: | |
| * - `true`: User must reconnect their account (token revoked, expired, etc.) | |
| * - `false`: Error may be transient, retry might succeed | |
| * | |
| * @example | |
| * ```typescript | |
| * throw new InboxAuthError({ | |
| * code: "token_expired", | |
| * provider: "gmail", | |
| * message: "Access token has expired", | |
| * requiresReauth: true, | |
| * }); | |
| * ``` | |
| */ | |
| export class InboxAuthError extends Error { | |
| /** The specific error code */ | |
| readonly code: InboxAuthErrorCode; | |
| /** The email provider that generated this error */ | |
| readonly provider: InboxProvider; | |
| /** Whether the user needs to re-authenticate */ | |
| readonly requiresReauth: boolean; | |
| constructor(options: InboxAuthErrorOptions) { | |
| super(options.message); | |
| this.name = "InboxAuthError"; | |
| this.code = options.code; | |
| this.provider = options.provider; | |
| this.requiresReauth = options.requiresReauth; | |
| // Preserve the original error stack if available | |
| if (options.cause) { | |
| this.cause = options.cause; | |
| } | |
| // Ensure proper prototype chain for instanceof checks | |
| Object.setPrototypeOf(this, InboxAuthError.prototype); | |
| } | |
| /** | |
| * Check if this error indicates the user needs to reconnect their account. | |
| * @returns `true` if user must re-authenticate | |
| */ | |
| isReauthRequired(): boolean { | |
| return this.requiresReauth; | |
| } | |
| } | |
| /** | |
| * Options for constructing an {@link InboxSyncError}. | |
| */ | |
| interface InboxSyncErrorOptions { | |
| /** The specific error code identifying the sync failure type */ | |
| code: InboxSyncErrorCode; | |
| /** The email provider that generated this error */ | |
| provider: InboxProvider; | |
| /** Human-readable error message */ | |
| message: string; | |
| /** The original error that caused this error, if any */ | |
| cause?: Error; | |
| } | |
| /** | |
| * Structured error for sync-related issues (non-authentication). | |
| * | |
| * These errors are typically transient and may resolve on retry. | |
| * Use the `isRetryable()` method to check if retrying is recommended. | |
| * | |
| * @example | |
| * ```typescript | |
| * throw new InboxSyncError({ | |
| * code: "rate_limited", | |
| * provider: "outlook", | |
| * message: "API rate limit exceeded", | |
| * }); | |
| * ``` | |
| */ | |
| export class InboxSyncError extends Error { | |
| /** The specific error code */ | |
| readonly code: InboxSyncErrorCode; | |
| /** The email provider that generated this error */ | |
| readonly provider: InboxProvider; | |
| constructor(options: InboxSyncErrorOptions) { | |
| super(options.message); | |
| this.name = "InboxSyncError"; | |
| this.code = options.code; | |
| this.provider = options.provider; | |
| if (options.cause) { | |
| this.cause = options.cause; | |
| } | |
| Object.setPrototypeOf(this, InboxSyncError.prototype); | |
| } | |
| /** | |
| * Check if this error is likely transient and worth retrying. | |
| * @returns `true` for network errors and rate limits | |
| */ | |
| isRetryable(): boolean { | |
| return this.code === "network_error" || this.code === "rate_limited"; | |
| } | |
| } | |
| /** | |
| * Type guard to check if an error is an {@link InboxAuthError}. | |
| * | |
| * @param error - The error to check | |
| * @returns `true` if the error is an InboxAuthError | |
| * | |
| * @example | |
| * ```typescript | |
| * if (isInboxAuthError(error)) { | |
| * console.log(error.code); // TypeScript knows this is InboxAuthErrorCode | |
| * } | |
| * ``` | |
| */ | |
| export function isInboxAuthError(error: unknown): error is InboxAuthError { | |
| return error instanceof InboxAuthError; | |
| } | |
| /** | |
| * Type guard to check if an error is an {@link InboxSyncError}. | |
| * | |
| * @param error - The error to check | |
| * @returns `true` if the error is an InboxSyncError | |
| * | |
| * @example | |
| * ```typescript | |
| * if (isInboxSyncError(error)) { | |
| * console.log(error.isRetryable()); | |
| * } | |
| * ``` | |
| */ | |
| export function isInboxSyncError(error: unknown): error is InboxSyncError { | |
| return error instanceof InboxSyncError; | |
| } | |
| /** | |
| * Assertion function that narrows an error to {@link InboxAuthError}. | |
| * | |
| * Use after a type guard check to avoid manual type casting. | |
| * | |
| * @param error - The error to assert | |
| * @throws {TypeError} If the error is not an InboxAuthError | |
| * | |
| * @example | |
| * ```typescript | |
| * if (isInboxAuthError(error)) { | |
| * assertInboxAuthError(error); | |
| * // error is now typed as InboxAuthError without casting | |
| * console.log(error.requiresReauth); | |
| * } | |
| * ``` | |
| */ | |
| export function assertInboxAuthError( | |
| error: unknown, | |
| ): asserts error is InboxAuthError { | |
| if (!isInboxAuthError(error)) { | |
| throw new TypeError(`Expected InboxAuthError, got ${typeof error}`); | |
| } | |
| } | |
| /** | |
| * Assertion function that narrows an error to {@link InboxSyncError}. | |
| * | |
| * Use after a type guard check to avoid manual type casting. | |
| * | |
| * @param error - The error to assert | |
| * @throws {TypeError} If the error is not an InboxSyncError | |
| * | |
| * @example | |
| * ```typescript | |
| * if (isInboxSyncError(error)) { | |
| * assertInboxSyncError(error); | |
| * // error is now typed as InboxSyncError without casting | |
| * console.log(error.isRetryable()); | |
| * } | |
| * ``` | |
| */ | |
| export function assertInboxSyncError( | |
| error: unknown, | |
| ): asserts error is InboxSyncError { | |
| if (!isInboxSyncError(error)) { | |
| throw new TypeError(`Expected InboxSyncError, got ${typeof error}`); | |
| } | |
| } | |