/ lib / errawr.ts
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  }