From c0ff8d5798ff1bf11bc1d61ce7a4b8d4640ba4cd Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Mon, 30 Mar 2020 18:07:43 -0700 Subject: [PATCH] Fix error handling logic to preserve custom error objects --- API_DOCS.md | 10 +- __tests__/implementation.test.js | 230 +++++++++++++++++-------- examples/swapi/swapi-loaders.js | 132 +++++++++++++-- examples/swapi/yarn.lock | 277 +------------------------------ src/codegen.ts | 9 +- src/implementation.ts | 24 +-- src/runtimeHelpers.ts | 9 + 7 files changed, 322 insertions(+), 369 deletions(-) diff --git a/API_DOCS.md b/API_DOCS.md index d3ce6d6..f80b3ea 100644 --- a/API_DOCS.md +++ b/API_DOCS.md @@ -38,12 +38,14 @@ getLoaders(resources[, options]) - **`errorHandler`** - (Optional) Provide a function to wrap the underlying resource call. Useful if you want to handle 'expected' errors (e.g. 4xxs, 5xxs) before handing over to the resolver method. + (Optional) Provide a function to wrap the underlying resource call. Useful if you want to handle 'expected' errors or rejected promises from the resource function (e.g. 4xxs, 5xxs) before handing over to the resolver method. + + Must return an Error object. **Interface:** ```js - errorHandler(resourcePath: Array, error: Error): Promise + (resourcePath: $ReadOnlyArray, error: any): Promise, ``` - **`resourceMiddleware`** @@ -58,7 +60,7 @@ getLoaders(resources[, options]) **Interface**: ```js - before(resourcePath: Array, resourceArgs: T): Promise + (resourcePath: $ReadOnlyArray, resourceArgs: T): Promise ``` - **`after`** @@ -68,7 +70,7 @@ getLoaders(resources[, options]) **Interface**: ```js - after(resourcePath: Array, response: T): Promise + (resourcePath: $ReadOnlyArray, response: T): Promise ``` ### Example diff --git a/__tests__/implementation.test.js b/__tests__/implementation.test.js index 03d7fe9..9a37ac3 100644 --- a/__tests__/implementation.test.js +++ b/__tests__/implementation.test.js @@ -292,69 +292,6 @@ test('batch endpoint (multiple requests)', async () => { }); }); -test('batch endpoint that throws errors', async () => { - const config = { - resources: { - foo: { - isBatchResource: true, - docsLink: 'example.com/docs/bar', - batchKey: 'foo_ids', - newKey: 'foo_id', - }, - }, - }; - - const resources = { - foo: ({ foo_ids, include_extra_info }) => { - if (_.isEqual(foo_ids, [1, 3])) { - expect(include_extra_info).toBe(false); - throw new Error('yikes'); - } - - if (_.isEqual(foo_ids, [2, 4, 5])) { - expect(include_extra_info).toBe(true); - return Promise.resolve([ - { - foo_id: 2, - foo_value: 'greetings', - extra_stuff: 'lorem ipsum', - }, - { - foo_id: 4, - foo_value: 'greetings', - extra_stuff: 'lorem ipsum', - }, - { - foo_id: 5, - foo_value: 'greetings', - extra_stuff: 'lorem ipsum', - }, - ]); - } - }, - }; - - await createDataLoaders(config, async getLoaders => { - const loaders = getLoaders(resources); - - const results = await loaders.foo.loadMany([ - { foo_id: 1, include_extra_info: false }, - { foo_id: 2, include_extra_info: true }, - { foo_id: 3, include_extra_info: false }, - { foo_id: 4, include_extra_info: true }, - { foo_id: 5, include_extra_info: true }, - ]); - - expect(results).toMatchObject([ - expect.toBeError(/yikes/), - { foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, - expect.toBeError(/yikes/), - { foo_id: 4, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, - { foo_id: 5, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, - ]); - }); -}); - test('batch endpoint that rejects', async () => { const config = { resources: { @@ -408,6 +345,7 @@ test('batch endpoint that rejects', async () => { { foo_id: 5, include_extra_info: true }, ]); + // NonError comes from the default error handler which uses ensure-error expect(results).toMatchObject([ expect.toBeError(/yikes/, 'NonError'), { foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, @@ -434,7 +372,7 @@ test('batch endpoint (multiple requests, default error handling)', async () => { foo: ({ foo_ids, include_extra_info }) => { if (_.isEqual(foo_ids, [1, 3])) { expect(include_extra_info).toBe(false); - return new Error('yikes'); + throw new Error('yikes'); } if (_.isEqual(foo_ids, [2, 4, 5])) { @@ -482,8 +420,9 @@ test('batch endpoint (multiple requests, default error handling)', async () => { }); test('batch endpoint (multiple requests, custom error handling)', async () => { - function errorHandler(resourcePath, error) { + async function errorHandler(resourcePath, error) { expect(resourcePath).toEqual(['foo']); + expect(error.message).toBe('yikes'); return new Error('hello from custom error handler'); } @@ -502,7 +441,7 @@ test('batch endpoint (multiple requests, custom error handling)', async () => { foo: ({ foo_ids, include_extra_info }) => { if (_.isEqual(foo_ids, [1, 3])) { expect(include_extra_info).toBe(false); - return new Error('yikes'); + throw new Error('yikes'); } if (_.isEqual(foo_ids, [2, 4, 5])) { @@ -999,3 +938,162 @@ test('middleware can transform the request args and the resource response', asyn ]); }); }); + +test('returning custom errors from error handler is supported', async () => { + class MyCustomError extends Error { + constructor(...args) { + super(...args); + this.name = this.constructor.name; + this.foo = 'bar'; + Error.captureStackTrace(this, MyCustomError); + } + } + + function errorHandler(resourcePath, error) { + expect(resourcePath).toEqual(['foo']); + expect(error.message).toBe('yikes'); + return new MyCustomError('hello from custom error object'); + } + + const config = { + resources: { + foo: { + isBatchResource: true, + docsLink: 'example.com/docs/bar', + batchKey: 'foo_ids', + newKey: 'foo_id', + }, + }, + }; + + const resources = { + foo: ({ foo_ids, include_extra_info }) => { + if (_.isEqual(foo_ids, [1, 3])) { + expect(include_extra_info).toBe(false); + throw new Error('yikes'); + } + + if (_.isEqual(foo_ids, [2, 4, 5])) { + expect(include_extra_info).toBe(true); + return Promise.resolve([ + { + foo_id: 2, + foo_value: 'greetings', + extra_stuff: 'lorem ipsum', + }, + { + foo_id: 4, + foo_value: 'greetings', + extra_stuff: 'lorem ipsum', + }, + { + foo_id: 5, + foo_value: 'greetings', + extra_stuff: 'lorem ipsum', + }, + ]); + } + }, + }; + + await createDataLoaders(config, async getLoaders => { + const loaders = getLoaders(resources, { errorHandler }); + + const results = await loaders.foo.loadMany([ + { foo_id: 1, include_extra_info: false }, + { foo_id: 2, include_extra_info: true }, + { foo_id: 3, include_extra_info: false }, + { foo_id: 4, include_extra_info: true }, + { foo_id: 5, include_extra_info: true }, + ]); + + expect(results).toMatchObject([ + expect.toBeError(/hello from custom error object/, 'MyCustomError'), + { foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, + expect.toBeError(/hello from custom error object/, 'MyCustomError'), + { foo_id: 4, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, + { foo_id: 5, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, + ]); + + expect(results[0]).toHaveProperty('foo', 'bar'); + expect(results[2]).toHaveProperty('foo', 'bar'); + }); +}); + +test('bail if errorHandler does not return an error', async () => { + class MyCustomError extends Error { + constructor(...args) { + super(...args); + this.name = this.constructor.name; + this.foo = 'bar'; + Error.captureStackTrace(this, MyCustomError); + } + } + + function errorHandler(resourcePath, error) { + expect(resourcePath).toEqual(['foo']); + expect(error.message).toBe('yikes'); + return 'not an Error object'; + } + + const config = { + resources: { + foo: { + isBatchResource: true, + docsLink: 'example.com/docs/bar', + batchKey: 'foo_ids', + newKey: 'foo_id', + }, + }, + }; + + const resources = { + foo: ({ foo_ids, include_extra_info }) => { + if (_.isEqual(foo_ids, [1, 3])) { + expect(include_extra_info).toBe(false); + throw new Error('yikes'); + } + + if (_.isEqual(foo_ids, [2, 4, 5])) { + expect(include_extra_info).toBe(true); + return Promise.resolve([ + { + foo_id: 2, + foo_value: 'greetings', + extra_stuff: 'lorem ipsum', + }, + { + foo_id: 4, + foo_value: 'greetings', + extra_stuff: 'lorem ipsum', + }, + { + foo_id: 5, + foo_value: 'greetings', + extra_stuff: 'lorem ipsum', + }, + ]); + } + }, + }; + + await createDataLoaders(config, async getLoaders => { + const loaders = getLoaders(resources, { errorHandler }); + + const results = await loaders.foo.loadMany([ + { foo_id: 1, include_extra_info: false }, + { foo_id: 2, include_extra_info: true }, + { foo_id: 3, include_extra_info: false }, + { foo_id: 4, include_extra_info: true }, + { foo_id: 5, include_extra_info: true }, + ]); + + expect(results).toMatchObject([ + expect.toBeError(/errorHandler did not return an Error object. Instead, got string: 'not an Error object'/), + { foo_id: 2, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, + expect.toBeError(/errorHandler did not return an Error object. Instead, got string: 'not an Error object'/), + { foo_id: 4, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, + { foo_id: 5, foo_value: 'greetings', extra_stuff: 'lorem ipsum' }, + ]); + }); +}); diff --git a/examples/swapi/swapi-loaders.js b/examples/swapi/swapi-loaders.js index 0c40ad9..7d878e5 100644 --- a/examples/swapi/swapi-loaders.js +++ b/examples/swapi/swapi-loaders.js @@ -4,16 +4,19 @@ * !!! THIS FILE IS AUTO-GENERATED. CHANGES MAY BE OVERWRITTEN !!! */ +import util from 'util'; import _ from 'lodash'; import invariant from 'assert'; import DataLoader from 'dataloader'; import { - CaughtResourceError, + BatchItemNotFoundError, cacheKeyOptions, + CaughtResourceError, + defaultErrorHandler, partitionItems, + resultsDictToList, sortByKeys, unPartitionResults, - resultsDictToList, } from 'dataloader-codegen/lib/runtimeHelpers'; /** @@ -34,9 +37,10 @@ type ExtractArg = ([(Arg) => Ret]) => Arg; type ExtractPromisedReturnValue = ((...A) => Promise) => R; export type DataLoaderCodegenOptions = {| + errorHandler?: (resourcePath: $ReadOnlyArray, error: any) => Promise, resourceMiddleware?: {| - before?: (resourcePath: $ReadOnlyArray, resourceArgs: T) => T, - after?: (resourcePath: $ReadOnlyArray, response: T) => T, + before?: (resourcePath: $ReadOnlyArray, resourceArgs: T) => Promise, + after?: (resourcePath: $ReadOnlyArray, response: T) => Promise, |}, |}; @@ -284,13 +288,42 @@ export default function getLoaders(resources: ResourcesType, options?: DataLoade resourceArgs = await options.resourceMiddleware.before(['getPlanets'], resourceArgs); } - // Finally, call the resource! - let response = await resources.getPlanets(...resourceArgs); + let response; + try { + // Finally, call the resource! + response = await resources.getPlanets(...resourceArgs); + } catch (error) { + const errorHandler = + options && typeof options.errorHandler === 'function' + ? options.errorHandler + : defaultErrorHandler; + + /** + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. + * + * If we let errors here go unhandled here, it will bubble up and DataLoader will return an error for all + * keys requested. We can do slightly better by returning the error object for just the keys in this batch request. + */ + response = await errorHandler(['getPlanets'], error); + + // Check that errorHandler actually returned an Error object, and turn it into one if not. + if (!(response instanceof Error)) { + response = new Error( + [ + `[dataloader-codegen :: getPlanets] Caught an error, but errorHandler did not return an Error object.`, + `Instead, got ${typeof response}: ${util.inspect(response)}`, + ].join(' '), + ); + } + } if (options && options.resourceMiddleware && options.resourceMiddleware.after) { response = await options.resourceMiddleware.after(['getPlanets'], response); } + if (!(response instanceof Error)) { + } + if (!(response instanceof Error)) { if (!Array.isArray(response)) { response = new Error( @@ -313,12 +346,17 @@ export default function getLoaders(resources: ResourcesType, options?: DataLoade /** * We must return errors for all keys in this group :( */ - response = new Error( + response = new BatchItemNotFoundError( [ `[dataloader-codegen :: getPlanets] Resource returned ${response.length} items, but we requested ${requests.length} items.`, 'Add reorderResultsByKey to the config for this resource to be able to handle a partial response.', ].join(' '), ); + + // Tell flow that BatchItemNotFoundError extends Error. + // It's an issue with flowgen package, but not an issue with Flow. + // @see https://github.com/Yelp/dataloader-codegen/pull/35#discussion_r394777533 + invariant(response instanceof Error, 'expected BatchItemNotFoundError to be an Error'); } } @@ -511,13 +549,42 @@ export default function getLoaders(resources: ResourcesType, options?: DataLoade resourceArgs = await options.resourceMiddleware.before(['getPeople'], resourceArgs); } - // Finally, call the resource! - let response = await resources.getPeople(...resourceArgs); + let response; + try { + // Finally, call the resource! + response = await resources.getPeople(...resourceArgs); + } catch (error) { + const errorHandler = + options && typeof options.errorHandler === 'function' + ? options.errorHandler + : defaultErrorHandler; + + /** + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. + * + * If we let errors here go unhandled here, it will bubble up and DataLoader will return an error for all + * keys requested. We can do slightly better by returning the error object for just the keys in this batch request. + */ + response = await errorHandler(['getPeople'], error); + + // Check that errorHandler actually returned an Error object, and turn it into one if not. + if (!(response instanceof Error)) { + response = new Error( + [ + `[dataloader-codegen :: getPeople] Caught an error, but errorHandler did not return an Error object.`, + `Instead, got ${typeof response}: ${util.inspect(response)}`, + ].join(' '), + ); + } + } if (options && options.resourceMiddleware && options.resourceMiddleware.after) { response = await options.resourceMiddleware.after(['getPeople'], response); } + if (!(response instanceof Error)) { + } + if (!(response instanceof Error)) { if (!Array.isArray(response)) { response = new Error( @@ -540,12 +607,17 @@ export default function getLoaders(resources: ResourcesType, options?: DataLoade /** * We must return errors for all keys in this group :( */ - response = new Error( + response = new BatchItemNotFoundError( [ `[dataloader-codegen :: getPeople] Resource returned ${response.length} items, but we requested ${requests.length} items.`, 'Add reorderResultsByKey to the config for this resource to be able to handle a partial response.', ].join(' '), ); + + // Tell flow that BatchItemNotFoundError extends Error. + // It's an issue with flowgen package, but not an issue with Flow. + // @see https://github.com/Yelp/dataloader-codegen/pull/35#discussion_r394777533 + invariant(response instanceof Error, 'expected BatchItemNotFoundError to be an Error'); } } @@ -738,13 +810,42 @@ export default function getLoaders(resources: ResourcesType, options?: DataLoade resourceArgs = await options.resourceMiddleware.before(['getVehicles'], resourceArgs); } - // Finally, call the resource! - let response = await resources.getVehicles(...resourceArgs); + let response; + try { + // Finally, call the resource! + response = await resources.getVehicles(...resourceArgs); + } catch (error) { + const errorHandler = + options && typeof options.errorHandler === 'function' + ? options.errorHandler + : defaultErrorHandler; + + /** + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. + * + * If we let errors here go unhandled here, it will bubble up and DataLoader will return an error for all + * keys requested. We can do slightly better by returning the error object for just the keys in this batch request. + */ + response = await errorHandler(['getVehicles'], error); + + // Check that errorHandler actually returned an Error object, and turn it into one if not. + if (!(response instanceof Error)) { + response = new Error( + [ + `[dataloader-codegen :: getVehicles] Caught an error, but errorHandler did not return an Error object.`, + `Instead, got ${typeof response}: ${util.inspect(response)}`, + ].join(' '), + ); + } + } if (options && options.resourceMiddleware && options.resourceMiddleware.after) { response = await options.resourceMiddleware.after(['getVehicles'], response); } + if (!(response instanceof Error)) { + } + if (!(response instanceof Error)) { if (!Array.isArray(response)) { response = new Error( @@ -767,12 +868,17 @@ export default function getLoaders(resources: ResourcesType, options?: DataLoade /** * We must return errors for all keys in this group :( */ - response = new Error( + response = new BatchItemNotFoundError( [ `[dataloader-codegen :: getVehicles] Resource returned ${response.length} items, but we requested ${requests.length} items.`, 'Add reorderResultsByKey to the config for this resource to be able to handle a partial response.', ].join(' '), ); + + // Tell flow that BatchItemNotFoundError extends Error. + // It's an issue with flowgen package, but not an issue with Flow. + // @see https://github.com/Yelp/dataloader-codegen/pull/35#discussion_r394777533 + invariant(response instanceof Error, 'expected BatchItemNotFoundError to be an Error'); } } diff --git a/examples/swapi/yarn.lock b/examples/swapi/yarn.lock index 4f29d9b..a4d0d7b 100644 --- a/examples/swapi/yarn.lock +++ b/examples/swapi/yarn.lock @@ -167,11 +167,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.5.tgz#66103d2eddc543d44a04394abb7be52506d7f290" integrity sha512-KEjODidV4XYUlJBF3XdjSH5FWoMCtO0utnhtdLf1AgeuZLOrRbvmU/gaRCVg7ZaQDjVf3l84egiY0mRNe5xE4A== -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - ajv@^6.10.2: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" @@ -212,19 +207,6 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -440,11 +422,6 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -526,11 +503,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - convert-source-map@^1.1.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" @@ -581,13 +553,6 @@ debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -605,11 +570,6 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -639,21 +599,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - deprecation@^2.0.0: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -881,13 +831,6 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-minipass@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" - integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== - dependencies: - minipass "^2.6.0" - fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -921,20 +864,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" @@ -1034,11 +963,6 @@ has-to-string-tag-x@^1.2.0: dependencies: has-symbol-support-x "^1.4.1" -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -1089,20 +1013,6 @@ http-cache-semantics@3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== - dependencies: - minimatch "^3.0.4" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1116,11 +1026,6 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - interpret@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -1544,26 +1449,6 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" - integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" - integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== - dependencies: - minipass "^2.9.0" - mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -1572,7 +1457,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: +"mkdirp@>=0.5 0", mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -1584,11 +1469,6 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -1611,15 +1491,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -needle@^2.2.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.2.tgz#3342dea100b7160960a450dc8c22160ac712a528" - integrity sha512-DUzITvPVDUy6vczKKYTnWc/pBZ0EnjMJnQ3y+Jo5zfKFimJs7S3HFCxCRZYB9FUZcrzUQr3WsmvZgddMEIZv6w== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -1643,30 +1514,6 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -1688,27 +1535,6 @@ normalize-url@2.0.1: query-string "^5.0.1" sort-keys "^2.0.0" -npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== - dependencies: - npm-normalize-package-bin "^1.0.1" - -npm-normalize-package-bin@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" - integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== - -npm-packlist@^1.1.6: - version "1.4.8" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" - integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-normalize-package-bin "^1.0.1" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -1716,16 +1542,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -1789,11 +1605,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - os-locale@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" @@ -1811,19 +1622,6 @@ os-name@^3.1.0: macos-release "^2.2.0" windows-release "^3.1.0" -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" @@ -1981,16 +1779,6 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -2004,19 +1792,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^2.0.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -2095,14 +1870,14 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@2, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.6.2: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" -safe-buffer@^5.1.1, safe-buffer@^5.1.2: +safe-buffer@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -2119,22 +1894,12 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: +semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -2294,7 +2059,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: +string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -2360,11 +2125,6 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -2382,19 +2142,6 @@ table@^5.0.2: slice-ansi "^2.1.0" string-width "^3.0.0" -tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" - through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -2548,13 +2295,6 @@ which@^1.2.9, which@^1.3.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - windows-release@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" @@ -2580,11 +2320,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yallist@^3.0.0, yallist@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yargs-parser@^11.1.1: version "11.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" diff --git a/src/codegen.ts b/src/codegen.ts index dddfed9..ccdc6b8 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -52,18 +52,19 @@ export default function codegen( * !!! THIS FILE IS AUTO-GENERATED. CHANGES MAY BE OVERWRITTEN !!! */ + import util from 'util'; import _ from 'lodash'; import invariant from 'assert'; - import ensureError from 'ensure-error'; import DataLoader from 'dataloader'; import { BatchItemNotFoundError, - CaughtResourceError, cacheKeyOptions, + CaughtResourceError, + defaultErrorHandler, partitionItems, + resultsDictToList, sortByKeys, unPartitionResults, - resultsDictToList, } from '${runtimeHelpers}'; @@ -84,7 +85,7 @@ export default function codegen( type ExtractPromisedReturnValue = ((...A) => Promise) => R; export type DataLoaderCodegenOptions = {| - errorHandler?: (resourcePath: $ReadOnlyArray, error: Error) => Promise, + errorHandler?: (resourcePath: $ReadOnlyArray, error: any) => Promise, resourceMiddleware?: {| before?: (resourcePath: $ReadOnlyArray, resourceArgs: T) => Promise, after?: (resourcePath: $ReadOnlyArray, response: T) => Promise, diff --git a/src/implementation.ts b/src/implementation.ts index 71e2ea4..0d0e02f 100644 --- a/src/implementation.ts +++ b/src/implementation.ts @@ -148,23 +148,25 @@ function getBatchLoader(resourceConfig: BatchResourceConfig, resourcePath: Reado // Finally, call the resource! response = await ${resourceReference}(...resourceArgs); } catch (error) { + const errorHandler = (options && typeof options.errorHandler === 'function') ? options.errorHandler : defaultErrorHandler; + /** - * Apply some default error handling to catch and handle all errors/rejected promises. + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. * * If we let errors here go unhandled here, it will bubble up and DataLoader will return an error for all * keys requested. We can do slightly better by returning the error object for just the keys in this batch request. - * - * We use ensureError because 'error' might actually be a string or something in the case of a rejected promise. */ - response = ensureError(error); - } + response = await errorHandler(${JSON.stringify(resourcePath)}, error); - // If there's a custom error handler, do something with the error - if (options && options.errorHandler && response instanceof Error) { - response = await options.errorHandler( - ${JSON.stringify(resourcePath)}, - response - ); + // Check that errorHandler actually returned an Error object, and turn it into one if not. + if (!(response instanceof Error)) { + response = new Error([ + \`${errorPrefix( + resourcePath, + )} Caught an error, but errorHandler did not return an Error object.\`, + \`Instead, got \${typeof response}: \${util.inspect(response)}\`, + ].join(' ')); + } } if (options && options.resourceMiddleware && options.resourceMiddleware.after) { diff --git a/src/runtimeHelpers.ts b/src/runtimeHelpers.ts index 00e045d..76137de 100644 --- a/src/runtimeHelpers.ts +++ b/src/runtimeHelpers.ts @@ -5,6 +5,7 @@ import _ from 'lodash'; import AggregateError from 'aggregate-error'; +import ensureError from 'ensure-error'; import invariant from 'assert'; import objectHash from 'object-hash'; @@ -295,3 +296,11 @@ export function resultsDictToList( ), ); } + +/** + * If no errorHandler option is passed to dataloader-codegen, we will use this by default. + */ +export async function defaultErrorHandler(resourcePath: ReadonlyArray, error: any): Promise { + // The error handler must return an error object. Turn all rejected strings/objects into errors. + return ensureError(error); +}