UNPKG

@proofkit/fmodata

Version:
218 lines (182 loc) 5.79 kB
import type { StandardSchemaV1 } from "@standard-schema/spec"; /** * Base class for all fmodata errors */ export abstract class FMODataError extends Error { abstract readonly kind: string; readonly timestamp: Date; constructor(message: string, options?: ErrorOptions) { super(message, options); this.name = this.constructor.name; this.timestamp = new Date(); } } // ============================================ // HTTP Errors (with status codes) // ============================================ export class HTTPError extends FMODataError { readonly kind = "HTTPError" as const; readonly url: string; readonly status: number; readonly statusText: string; readonly response?: any; constructor(url: string, status: number, statusText: string, response?: any) { super(`HTTP ${status} ${statusText} for ${url}`); this.url = url; this.status = status; this.statusText = statusText; this.response = response; } // Helper methods for common status checks is4xx(): boolean { return this.status >= 400 && this.status < 500; } is5xx(): boolean { return this.status >= 500 && this.status < 600; } isNotFound(): boolean { return this.status === 404; } isUnauthorized(): boolean { return this.status === 401; } isForbidden(): boolean { return this.status === 403; } } // ============================================ // OData Specific Errors // ============================================ export class ODataError extends FMODataError { readonly kind = "ODataError" as const; readonly url: string; readonly code?: string; readonly details?: any; constructor(url: string, message: string, code?: string, details?: any) { super(`OData error: ${message}`); this.url = url; this.code = code; this.details = details; } } export class SchemaLockedError extends FMODataError { readonly kind = "SchemaLockedError" as const; readonly url: string; readonly code: string; readonly details?: any; constructor(url: string, message: string, details?: any) { super(`OData error: ${message}`); this.url = url; this.code = "303"; this.details = details; } } // ============================================ // Validation Errors // ============================================ export class ValidationError extends FMODataError { readonly kind = "ValidationError" as const; readonly field?: string; readonly issues: readonly StandardSchemaV1.Issue[]; readonly value?: unknown; constructor( message: string, issues: readonly StandardSchemaV1.Issue[], options?: { field?: string; value?: unknown; cause?: Error["cause"]; }, ) { super( message, options?.cause !== undefined ? { cause: options.cause } : undefined, ); this.field = options?.field; this.issues = issues; this.value = options?.value; } } export class ResponseStructureError extends FMODataError { readonly kind = "ResponseStructureError" as const; readonly expected: string; readonly received: any; constructor(expected: string, received: any) { super(`Invalid response structure: expected ${expected}`); this.expected = expected; this.received = received; } } export class RecordCountMismatchError extends FMODataError { readonly kind = "RecordCountMismatchError" as const; readonly expected: number | "one" | "at-most-one"; readonly received: number; constructor(expected: number | "one" | "at-most-one", received: number) { const expectedStr = typeof expected === "number" ? expected : expected; super(`Expected ${expectedStr} record(s), but received ${received}`); this.expected = expected; this.received = received; } } export class InvalidLocationHeaderError extends FMODataError { readonly kind = "InvalidLocationHeaderError" as const; readonly locationHeader?: string; constructor(message: string, locationHeader?: string) { super(message); this.locationHeader = locationHeader; } } // ============================================ // Type Guards // ============================================ export function isHTTPError(error: unknown): error is HTTPError { return error instanceof HTTPError; } export function isValidationError(error: unknown): error is ValidationError { return error instanceof ValidationError; } export function isODataError(error: unknown): error is ODataError { return error instanceof ODataError; } export function isSchemaLockedError( error: unknown, ): error is SchemaLockedError { return error instanceof SchemaLockedError; } export function isResponseStructureError( error: unknown, ): error is ResponseStructureError { return error instanceof ResponseStructureError; } export function isRecordCountMismatchError( error: unknown, ): error is RecordCountMismatchError { return error instanceof RecordCountMismatchError; } export function isFMODataError(error: unknown): error is FMODataError { return error instanceof FMODataError; } // ============================================ // Union type for all possible errors // ============================================ // Re-export ffetch errors (they'll be imported from @fetchkit/ffetch) export type { TimeoutError, AbortError, NetworkError, RetryLimitError, CircuitOpenError, } from "@fetchkit/ffetch"; export type FMODataErrorType = | import("@fetchkit/ffetch").TimeoutError | import("@fetchkit/ffetch").AbortError | import("@fetchkit/ffetch").NetworkError | import("@fetchkit/ffetch").RetryLimitError | import("@fetchkit/ffetch").CircuitOpenError | HTTPError | ODataError | SchemaLockedError | ValidationError | ResponseStructureError | RecordCountMismatchError | InvalidLocationHeaderError;