UNPKG

@nyteshade/lattice-legacy

Version:

OO Underpinnings for ease of GraphQL Implementation

331 lines (286 loc) 9.92 kB
/** @namespace utils */ // @flow import fs from 'fs' import util from 'util' import { typeOf } from 'ne-types' import { sync as readPkg } from 'read-pkg-up' import { merge } from 'lodash' export { dedent as joinLines } from 'ne-tag-fns' const { Stats } = fs; /** * Deferred is modeled after jQuery's deferred object. It inverts a promise * such that its resolve and reject methods can be invoked without wrapping * all of the related code within a Promise's function. * * @memberof utils * @class Deferred */ export class Deferred { /** * This property holds a `resolve` function from within the promise this * deferred inverts. * * @type {Function} * @memberof Deferred * @instance */ resolve: Function; /** * This property holds a `reject` function from within the promise this * deferred inverts * * @type {Function} * @memberof Deferred * @instance */ reject: Function; /** * This is the promise wrapped by and inverted in this deferred instance * * @type {Promise} * @memberof Deferred * @instance */ promise: any; /** * An at a glance boolean property that denotes whether or not this * deferred has been resolved or rejected yet. * * @type {boolean} * @memberof Deferred * @instance */ complete: boolean; /** * Creates an object with four properties of note; promise, resolve, reject * and a flag complete that will be set once either resolve or reject have * been called. A Deferred is considered to be pending while complete is set * to false. * * Once constructed, resolve and reject can be called later, at which point, * the promise is completed. The promise property is the promise resolved * or rejected by the associated properties and can be used with other * async/await or Promise based code. * * @instance * @memberof Deferred * @method ⎆⠀constructor * * @param {any} resolveWith a deferred resolved as Promise.resolve() might do * @param {any} rejectWith a deferred rejected as Promise.reject() might do */ constructor(resolveWith: any, rejectWith: any) { this.promise = new Promise((resolve, reject) => { this.complete = false; this.resolve = (...args) => { this.complete = true; return resolve(...args); }; this.reject = (...args) => { this.complete = true; return reject(...args); }; if (resolveWith && !rejectWith) { this.resolve(resolveWith) } if (rejectWith && !resolveWith) { this.reject(rejectWith) } }); } /** * Shorthand getter that denotes true if the deferred is not yet complete. * * @instance * @memberof Deferred * @method ⬇︎⠀pending * * @return {boolean} true if the promise is not yet complete; false otherwise */ get pending(): boolean { return !this.complete } /** * Promises are great but if the code never resolves or rejects a deferred, * then things will become eternal; in a bad way. This makes that less likely * of an event. * * If the number of milliseconds elapses before a resolve or reject occur, * then the deferred is rejected. * * @static * @memberof Deferred * @method ⌾⠀TimedDeferred * * @param {Number} timeOut a number of milliseconds to wait before rejecting * the deferred. * @param {Promise} proxyPromise a promise to proxy then/catch through to the * deferreds resolve/reject. * @return {Deferred} an instance of deferred that will timeout after * `timeOut` milliseconds have elapsed. If `proxyPromise` is a `Promise` * then the deferred's reject and resolve will be tied to the Promise's * catch() and then() methods, respectively. */ static TimedDeferred(timeOut: Number, proxyPromise: ?any): Deferred { const deferred = new Deferred(); if (proxyPromise && typeOf(proxyPromise) === Promise.name) { proxyPromise.then((...args) => deferred.resolve(...args)) proxyPromise.catch(reason => deferred.reject(reason)) } setTimeout(() => deferred.reject(new Error('Deferred timed out'), timeOut)) return deferred; } } /** * A simply promisify style function that returns an async function wrapped * around a supplied function designed for the standard callback methodology. * If the callback is the last parameter, and that callback is in the form of * (error, ...results) then this wrapper will do the trick for you. * * @method utils~⌾⠀promisify * @since 2.7.0 * * @param {Function} method a function to wrap in an asynchronous function * @param {mixed} context an optional `this` object for use with the supplied * function. * @return {Function} an asynchronous function, i.e. one that returns a promise * containing the contents the callback results, that wraps the supplied * function. */ export function promisify(method: Function, context?: mixed): Function { return async function(...args) { return new Promise((resolve, reject) => { args.push(function(error, ...callbackArgs) { if (error) { reject(error); } else { resolve(...callbackArgs); } }); method.apply(context, args); }) } } /** * It may be necessary to read GraphQL Lattice preferences from the nearest * `package.json` object to the excuting code. `getLatticePrefs()` does this * and merges any subsequently found options in said file on top of the * default values specified here in this file. * * @method utils~⌾⠀getLatticePrefs * @since 2.13.0 * * @return {Object} an object containing at least the defaults plus any other * values specified in `package.json` */ export function getLatticePrefs(readPkgUpOpts: ?Object): Object { let { pkg } = readPkg(readPkgUpOpts) let options = { ModuleParser: { extensions: ['.js', '.jsx', '.ts', '.tsx'], failOnError: false } } if (pkg.lattice) { merge(options, pkg.lattice || {}) } return options; } /** * A small near pass-thru facility for logging within Lattice such that error * objects supplied get mapped to their message unless `LATTICE_ERRORS=STACK` * is set in `process.env`. * * Note the order of log levels for Lattice may be somewhat non-standard. Info * has been taken out of flow and placed above error to solve issues with jest * logging. * * @memberof utils * @type Object * @static */ export const LatticeLogs = { get LOG(): string { return 'log' }, get WARN(): string { return 'warn' }, get ERROR(): string { return 'error' }, get INFO(): string { return 'info' }, get TRACE(): string { return 'trace' }, /** * Ordering of log levels for LatticeLogs. `INFO` is a non error log level * that is non-crucial and appears if LATTICE_LOGLEVEL is set to `INFO` or * `TRACE` */ get LEVELS(): Array<string> { const ll = LatticeLogs return [ll.LOG, ll.WARN, ll.ERROR, ll.INFO, ll.TRACE] }, equalOrBelow(testedLevel: string, lessThan: string = 'error') { const ll = LatticeLogs return ll.LEVELS.indexOf(testedLevel) <= ll.LEVELS.indexOf(lessThan) }, atLeast(testedLevel: string, atLeastLevel: string): boolean { const ll = LatticeLogs return ll.LEVELS.indexOf(testedLevel) >= ll.LEVELS.indexOf(atLeastLevel) }, /** * All arguments of any logging function in `LatticeLogs` get passed through * this function first to modify or alter the type of value being logged. * * @param {mixed} arg the argument being passed to the `map()` function * @param {number} index the index in the array of arguments * @param {Array<mixed>} array the array containing this element */ argMapper(arg: mixed, index: number, array: Array<mixed>): mixed { let isError = typeOf(arg) === Error.name let showStack = /\bSTACK\b/i.test(process.env.LATTICE_ERRORS || '') // $FlowFixMe return !isError ? arg : (showStack ? arg : arg.message) }, /** A function that, when it returns true, will cause logging to be skipped */ failFast(logLevel: ?string, lessThan: ?string) { const ll = LatticeLogs if (logLevel) { let compareTo = lessThan || process.env.LATTICE_LOGLEVEL || ll.ERROR if (!ll.equalOrBelow(logLevel, compareTo)) return true } return /\b(NONE|OFF|NO|0)\b/i.test(process.env.LATTICE_ERRORS || '') }, /** Pass-thru to console.log; arguments parsed via `argMapper` */ log(...args: Array<mixed>) { if (LatticeLogs.failFast(LatticeLogs.LOG)) return; console.log(...args.map(LatticeLogs.argMapper)) }, /** Pass-thru to console.warn; arguments parsed via `argMapper` */ warn(...args: Array<mixed>) { if (LatticeLogs.failFast(LatticeLogs.WARN)) return; console.warn(...args.map(LatticeLogs.argMapper)) }, /** Pass-thru to console.error; arguments parsed via `argMapper` */ error(...args: Array<mixed>) { if (LatticeLogs.failFast(LatticeLogs.ERROR)) return; console.error(...args.map(LatticeLogs.argMapper)) }, /** Pass-thru to console.info; arguments parsed via `argMapper` */ info(...args: Array<mixed>) { if (LatticeLogs.failFast(LatticeLogs.INFO)) return; console.info(...args.map(LatticeLogs.argMapper)) }, /** Pass-thru to console.trace; arguments parsed via `argMapper` */ trace(...args: Array<mixed>) { if (LatticeLogs.failFast(LatticeLogs.TRACE)) return; console.trace(...args.map(LatticeLogs.argMapper)) }, outWrite( chunk: string|Uint8Array|Buffer, encoding: ?string, callback: ?Function ) { if (LatticeLogs.failFast(LatticeLogs.LOG)) return // $FlowFixMe process.stdout.write(chunk, encoding, callback) }, errWrite( chunk: string|Uint8Array|Buffer, encoding: ?string, callback: ?Function ) { if (LatticeLogs.failFast(LatticeLogs.ERROR)) return // $FlowFixMe process.stderr.write(chunk, encoding, callback) } }