File size: 7,743 Bytes
c09f67c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 | /**
* @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}`);
}
}
|