offline-first, p2p synced, atproto enabled, feed reader
at main 2.7 kB view raw
1import {z} from 'zod/mini' 2 3const StatusCodes: Record<number, string> = { 4 400: 'Bad Request', 5 401: 'Unauthorized', 6 403: 'Forbidden', 7 404: 'Not Found', 8 408: 'Request Timeout', 9 409: 'Conflict', 10 429: 'Too Many Requests', 11 499: 'Client Closed Request', 12 500: 'Internal Server Error', 13} 14 15/** base error options interface */ 16export interface BaseErrorOpts { 17 /** the cause of the error */ 18 cause?: Error 19} 20 21/** 22 * common base class for skypod errors 23 * only difference is that we explicitly type cause to be Error 24 */ 25export class BaseError extends Error { 26 /** the cause of the error */ 27 declare cause?: Error 28 29 constructor(message: string, options?: BaseErrorOpts) { 30 super(message, options) 31 if (options?.cause) this.cause = normalizeError(options.cause) 32 } 33} 34 35/** common base class for websocket errors */ 36export class ProtocolError extends BaseError { 37 /** the HTTP status code representing this error */ 38 status: number 39 40 constructor(message: string, status: number, options?: BaseErrorOpts) { 41 const statusText = StatusCodes[status] || 'Unknown' 42 super(`${status} ${statusText}: ${message}`, options) 43 44 this.name = this.constructor.name 45 this.status = status 46 } 47} 48 49/** check if an error is a protocol error with optional status check */ 50export function isProtocolError(e: Error, status?: number): e is ProtocolError { 51 return e instanceof ProtocolError && (status === undefined || e.status == status) 52} 53 54/** 55 * normalizes the given error into a protocol error 56 * passes through input that is already protocol errors. 57 */ 58export function normalizeProtocolError(cause: unknown, status = 500): ProtocolError { 59 if (cause instanceof ProtocolError) return cause 60 if (cause instanceof z.core.$ZodError) return new ProtocolError(z.prettifyError(cause), 400, {cause}) 61 62 if (cause instanceof Error || cause instanceof DOMException) { 63 if (cause.name === 'TimeoutError') return new ProtocolError('operation timed out', 408, {cause}) 64 if (cause.name === 'AbortError') return new ProtocolError('operation was aborted', 499, {cause}) 65 66 return new ProtocolError(cause.message, status, {cause}) 67 } 68 69 // fallback, unknown 70 const options = cause == undefined ? undefined : {cause: normalizeError(cause)} 71 return new ProtocolError(`Error! ${cause}`, status, options) 72} 73 74/** error wrapper for unknown errors (not an Error?) */ 75export class NormalizedError extends BaseError {} 76 77/** 78 * wrap the given failure error into an error 79 * passes through input that is already an Error object. 80 */ 81export function normalizeError(failure: unknown): Error { 82 if (failure instanceof Error) return failure 83 84 return new NormalizedError(`unnormalized failure ${failure}`) 85}