errawr.ts
1 import rawr from './rawr'; 2 import { isError } from './helpers'; 3 4 export type Gettable = Array<any> | Record<string, any>; 5 export type Interpolator = (data: Gettable) => string; 6 export type Options = { info?: Gettable; code?: any; cause?: any; topFrame?: Function }; 7 export type InvariantOptions = Options & { ctor?: Function }; 8 9 // TODO: how to handle AggregateErrors 10 11 export default class Errawr extends Error { 12 info: Record<string, any>; 13 14 static get rawr() { 15 return rawr; 16 } 17 18 static print(err: Error): string { 19 let str = ''; 20 let first = true; 21 for (const cause of Errawr.chain(err)) { 22 if (!first) { 23 str += '\nCaused by: '; 24 } 25 26 const header = `${cause.name}: ${cause.message}`; 27 str += header; 28 29 if (cause.stack) { 30 let stack = cause.stack; 31 if (stack.startsWith(header)) { 32 stack = stack.slice(header.length + 1); 33 } 34 35 str += '\n' + stack; 36 } 37 38 first = false; 39 } 40 return str; 41 } 42 43 static info(err: Error): Record<string, any> { 44 // Should I be worried about name shadowing? How much? 45 return [...Errawr.chain(err)].reverse().reduce((info, cause) => { 46 Object.assign(info, (cause as any).info); 47 return info; 48 }, {}); 49 } 50 51 static chain(err: Error): Iterable<Error> { 52 return { 53 *[Symbol.iterator]() { 54 let cause: unknown = err; 55 while (true) { 56 if (isError(cause)) { 57 yield cause; 58 } else { 59 break; 60 } 61 62 cause = typeof cause.cause === 'function' ? cause.cause() : cause.cause; 63 } 64 }, 65 }; 66 } 67 68 static invariant(condition: false, reason: string | Interpolator, info?: Gettable): never; 69 static invariant( 70 condition: any, 71 reason: string | Interpolator, 72 info?: Gettable, 73 ): asserts condition; 74 static invariant(condition: unknown, reason: string | Interpolator, info?: Gettable) { 75 if (!condition) { 76 // i.e. TypeError.invariant(...) or invariant.call(TypeError, ...) 77 const ctor: any = typeof this === 'function' ? this : Errawr; 78 79 throw new ctor(reason, { info }); 80 } 81 } 82 83 constructor(reason: string | Interpolator, options?: Options) { 84 const { info, cause, code, topFrame } = options || {}; 85 86 let reason_ = typeof reason === 'function' ? reason(info) : reason; 87 88 // @ts-ignore 89 super(reason_); 90 91 Object.defineProperty(this, 'cause', { 92 value: cause, 93 writable: true, 94 enumerable: false, 95 configurable: true, 96 }); 97 Object.defineProperty(this, 'code', { 98 value: code, 99 writable: true, 100 enumerable: false, 101 configurable: true, 102 }); 103 Object.defineProperty(this, 'info', { 104 value: info, 105 writable: true, 106 enumerable: false, 107 configurable: true, 108 }); 109 110 Error.captureStackTrace(this, topFrame || this.constructor); 111 } 112 113 chain(): Iterable<Error> { 114 return Errawr.chain(this); 115 } 116 }