diff --git a/examples/swapi/swapi-loaders.js b/examples/swapi/swapi-loaders.js index 85e475b..1a42610 100644 --- a/examples/swapi/swapi-loaders.js +++ b/examples/swapi/swapi-loaders.js @@ -4,29 +4,29 @@ * !!! 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 util from "util"; +import _ from "lodash"; +import invariant from "assert"; +import DataLoader from "dataloader"; import { - BatchItemNotFoundError, - cacheKeyOptions, - CaughtResourceError, - defaultErrorHandler, - getBatchKeysForPartitionItems, - partitionItems, - resultsDictToList, - sortByKeys, - unPartitionResults, - unPartitionResultsByBatchKeyPartition, -} from 'dataloader-codegen/lib/runtimeHelpers'; + BatchItemNotFoundError, + cacheKeyOptions, + CaughtResourceError, + defaultErrorHandler, + getBatchKeysForPartitionItems, + partitionItems, + resultsDictToList, + sortByKeys, + unPartitionResults, + unPartitionResultsByBatchKeyPartition +} from "dataloader-codegen/lib/runtimeHelpers"; /** * =============================== * BEGIN: printResourceTypeImports() * =============================== */ -import type { SWAPIClientlibTypes } from './swapi'; +import type { SWAPIClientlibTypes } from "./swapi"; /** * =============================== @@ -39,15 +39,18 @@ type ExtractArg = ([(Arg) => Ret]) => Arg; type ExtractPromisedReturnValue = ((...A) => Promise) => R; export type DataLoaderCodegenOptions = {| - errorHandler?: ( - resourcePath: $ReadOnlyArray, - // $FlowFixMe: We don't know what type the resource might throw, so we have to type error to "any" :( - error: any, - ) => Promise, - resourceMiddleware?: {| - before?: (resourcePath: $ReadOnlyArray, resourceArgs: T) => Promise, - after?: (resourcePath: $ReadOnlyArray, response: T) => Promise, - |}, + errorHandler?: ( + resourcePath: $ReadOnlyArray, + // $FlowFixMe: We don't know what type the resource might throw, so we have to type error to "any" :( + error: any + ) => Promise, + resourceMiddleware?: {| + before?: ( + resourcePath: $ReadOnlyArray, + resourceArgs: T + ) => Promise, + after?: (resourcePath: $ReadOnlyArray, response: T) => Promise + |} |}; /** @@ -64,1611 +67,1812 @@ type ResourcesType = SWAPIClientlibTypes; */ export type LoadersType = $ReadOnly<{| - getPlanets: DataLoader< - {| - ...$Diff< - $Call]>, - { - planet_ids: $PropertyType< - $Call]>, - 'planet_ids', - >, - }, - >, - ...{| - planet_id: $ElementType< - $PropertyType<$Call]>, 'planet_ids'>, - 0, - >, - |}, - |}, - $ElementType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 0, - >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, - >, - getPeople: DataLoader< - {| - ...$Diff< - $Call]>, - { - people_ids: $PropertyType< - $Call]>, - 'people_ids', - >, - }, - >, - ...{| - person_id: $ElementType< - $PropertyType<$Call]>, 'people_ids'>, - 0, - >, - |}, - |}, - $ElementType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 0, + getPlanets: DataLoader< + {| + ...$Diff< + $Call]>, + { + planet_ids: $PropertyType< + $Call]>, + "planet_ids" + > + } + >, + ...{| + planet_id: $ElementType< + $NonMaybeType< + $PropertyType< + $Call]>, + "planet_ids" + > + >, + 0 + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, + $PropertyType + >, + 0 >, - getVehicles: DataLoader< - {| - ...$Diff< - $Call]>, - { - vehicle_ids: $PropertyType< - $Call]>, - 'vehicle_ids', - >, - }, - >, - ...{| - vehicle_id: $ElementType< - $PropertyType<$Call]>, 'vehicle_ids'>, - 0, - >, - |}, - |}, - $ElementType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 0, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >, + getPeople: DataLoader< + {| + ...$Diff< + $Call]>, + { + people_ids: $PropertyType< + $Call]>, + "people_ids" + > + } + >, + ...{| + person_id: $ElementType< + $NonMaybeType< + $PropertyType< + $Call]>, + "people_ids" + > + >, + 0 + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, + $PropertyType + >, + 0 >, - getFilms: DataLoader< - {| - ...$Diff< - $Call]>, - { - film_ids: $PropertyType<$Call]>, 'film_ids'>, - }, - >, - ...{| - film_id: $Call< - ExtractArg, - [ - $PropertyType< - $PropertyType<$Call]>, 'film_ids'>, - 'has', - >, - ], - >, - |}, - |}, - $ElementType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 0, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >, + getVehicles: DataLoader< + {| + ...$Diff< + $Call]>, + { + vehicle_ids: $PropertyType< + $Call]>, + "vehicle_ids" + > + } + >, + ...{| + vehicle_id: $ElementType< + $NonMaybeType< + $PropertyType< + $Call]>, + "vehicle_ids" + > + >, + 0 + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, + $PropertyType + >, + 0 >, - getFilmsV2: DataLoader< - {| - ...$Diff< - $Call]>, - { - film_ids: $PropertyType< - $Call]>, - 'film_ids', - >, - }, - >, - ...{| - film_id: $ElementType< - $PropertyType<$Call]>, 'film_ids'>, - 0, - >, - |}, - |}, - $ElementType< + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >, + getFilms: DataLoader< + {| + ...$Diff< + $Call]>, + { + film_ids: $PropertyType< + $Call]>, + "film_ids" + > + } + >, + ...{| + film_id: $Call< + ExtractArg, + [ $PropertyType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 'properties', - >, - 0, + $PropertyType< + $Call]>, + "film_ids" + >, + "has" + > + ] + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, + $PropertyType + >, + 0 >, - getRoot: DataLoader< - $Call]>, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >, + getFilmsV2: DataLoader< + {| + ...$Diff< + $Call]>, + { + film_ids: $PropertyType< + $Call]>, + "film_ids" + > + } + >, + ...{| + film_id: $ElementType< + $NonMaybeType< + $PropertyType< + $Call]>, + "film_ids" + > + >, + 0 + > + |} + |}, + $ElementType< + $PropertyType< $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, + "properties" + >, + 0 + >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >, + getRoot: DataLoader< + $Call]>, + $Call< + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + > |}>; -export default function getLoaders(resources: ResourcesType, options?: DataLoaderCodegenOptions): LoadersType { - return Object.freeze({ - getPlanets: new DataLoader< - {| - ...$Diff< - $Call]>, - { - planet_ids: $PropertyType< - $Call]>, - 'planet_ids', - >, - }, - >, - ...{| - planet_id: $ElementType< - $PropertyType<$Call]>, 'planet_ids'>, - 0, - >, - |}, - |}, - $ElementType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 0, +export default function getLoaders( + resources: ResourcesType, + options?: DataLoaderCodegenOptions +): LoadersType { + return Object.freeze({ + getPlanets: new DataLoader< + {| + ...$Diff< + $Call]>, + { + planet_ids: $PropertyType< + $Call]>, + "planet_ids" + > + } + >, + ...{| + planet_id: $ElementType< + $NonMaybeType< + $PropertyType< + $Call]>, + "planet_ids" + > >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, - >( + 0 + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType + >, + 0 + >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >( + /** + * =============================================================== + * Generated DataLoader: getPlanets + * =============================================================== + * + * Resource Config: + * + * ```json + * { + * "docsLink": "https://swapi.dev/documentation#planets", + * "isBatchResource": true, + * "batchKey": "planet_ids", + * "newKey": "planet_id" + * } + * ``` + */ + async keys => { + invariant( + typeof resources.getPlanets === "function", + [ + "[dataloader-codegen :: getPlanets] resources.getPlanets is not a function.", + 'Did you pass in an instance of getPlanets to "getLoaders"?' + ].join(" ") + ); + + /** + * Chunk up the "keys" array to create a set of "request groups". + * + * We're about to hit a batch resource. In addition to the batch + * key, the resource may take other arguments too. When batching + * up requests, we'll want to look out for where those other + * arguments differ, and send multiple requests so we don't get + * back the wrong info. + * + * In other words, we'll potentially want to send _multiple_ + * requests to the underlying resource batch method in this + * dataloader body. + * + * ~~~ Why? ~~~ + * + * Consider what happens when we get called with arguments where + * the non-batch keys differ. + * + * Example: + * + * ```js + * loaders.foo.load({ foo_id: 2, include_private_data: true }); + * loaders.foo.load({ foo_id: 3, include_private_data: false }); + * loaders.foo.load({ foo_id: 4, include_private_data: false }); + * ``` + * + * If we collected everything up and tried to send the one + * request to the resource as a batch request, how do we know + * what the value for "include_private_data" should be? We're + * going to have to group these up up and send two requests to + * the resource to make sure we're requesting the right stuff. + * + * e.g. We'd need to make the following set of underlying resource + * calls: + * + * ```js + * foo({ foo_ids: [ 2 ], include_private_data: true }); + * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); + * ``` + * + * ~~~ tl;dr ~~~ + * + * When we have calls to .load with differing non batch key args, + * we'll need to send multiple requests to the underlying + * resource to make sure we get the right results back. + * + * Let's create the request groups, where each element in the + * group refers to a position in "keys" (i.e. a call to .load) + * + * Example: + * + * ```js + * partitionItems('bar_id', [ + * { bar_id: 7, include_extra_info: true }, + * { bar_id: 8, include_extra_info: false }, + * { bar_id: 9, include_extra_info: true }, + * ]) + * ``` + * + * Returns: + * `[ [ 0, 2 ], [ 1 ] ]` + * + * We'll refer to each element in the group as a "request ID". + */ + const requestGroups = partitionItems("planet_id", keys); + + // Map the request groups to a list of Promises - one for each request + const groupedResults = await Promise.all( + requestGroups.map(async requestIDs => { /** - * =============================================================== - * Generated DataLoader: getPlanets - * =============================================================== + * Select a set of elements in "keys", where all non-batch + * keys should be identical. * - * Resource Config: - * - * ```json - * { - * "docsLink": "https://swapi.dev/documentation#planets", - * "isBatchResource": true, - * "batchKey": "planet_ids", - * "newKey": "planet_id" - * } - * ``` + * We're going to smoosh all these together into one payload to + * send to the resource as a batch request! */ - async (keys) => { - invariant( - typeof resources.getPlanets === 'function', - [ - '[dataloader-codegen :: getPlanets] resources.getPlanets is not a function.', - 'Did you pass in an instance of getPlanets to "getLoaders"?', - ].join(' '), + const requests = requestIDs.map(id => keys[id]); + + // For now, we assume that the dataloader key should be the first argument to the resource + // @see https://github.com/Yelp/dataloader-codegen/issues/56 + const resourceArgs = [ + { + ..._.omit(requests[0], "planet_id"), + ["planet_ids"]: requests.map(k => k["planet_id"]) + } + ]; + + let response = await (async _resourceArgs => { + // Make a re-assignable variable so flow/eslint doesn't complain + let __resourceArgs = _resourceArgs; + + if ( + options && + options.resourceMiddleware && + options.resourceMiddleware.before + ) { + __resourceArgs = await options.resourceMiddleware.before( + ["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; /** - * Chunk up the "keys" array to create a set of "request groups". - * - * We're about to hit a batch resource. In addition to the batch - * key, the resource may take other arguments too. When batching - * up requests, we'll want to look out for where those other - * arguments differ, and send multiple requests so we don't get - * back the wrong info. - * - * In other words, we'll potentially want to send _multiple_ - * requests to the underlying resource batch method in this - * dataloader body. - * - * ~~~ Why? ~~~ - * - * Consider what happens when we get called with arguments where - * the non-batch keys differ. - * - * Example: - * - * ```js - * loaders.foo.load({ foo_id: 2, include_private_data: true }); - * loaders.foo.load({ foo_id: 3, include_private_data: false }); - * loaders.foo.load({ foo_id: 4, include_private_data: false }); - * ``` + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. * - * If we collected everything up and tried to send the one - * request to the resource as a batch request, how do we know - * what the value for "include_private_data" should be? We're - * going to have to group these up up and send two requests to - * the resource to make sure we're requesting the right stuff. - * - * e.g. We'd need to make the following set of underlying resource - * calls: - * - * ```js - * foo({ foo_ids: [ 2 ], include_private_data: true }); - * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); - * ``` - * - * ~~~ tl;dr ~~~ - * - * When we have calls to .load with differing non batch key args, - * we'll need to send multiple requests to the underlying - * resource to make sure we get the right results back. - * - * Let's create the request groups, where each element in the - * group refers to a position in "keys" (i.e. a call to .load) - * - * Example: - * - * ```js - * partitionItems('bar_id', [ - * { bar_id: 7, include_extra_info: true }, - * { bar_id: 8, include_extra_info: false }, - * { bar_id: 9, include_extra_info: true }, - * ]) - * ``` - * - * Returns: - * `[ [ 0, 2 ], [ 1 ] ]` - * - * We'll refer to each element in the group as a "request ID". + * 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. */ - const requestGroups = partitionItems('planet_id', keys); - - // Map the request groups to a list of Promises - one for each request - const groupedResults = await Promise.all( - requestGroups.map(async (requestIDs) => { - /** - * Select a set of elements in "keys", where all non-batch - * keys should be identical. - * - * We're going to smoosh all these together into one payload to - * send to the resource as a batch request! - */ - const requests = requestIDs.map((id) => keys[id]); - - // For now, we assume that the dataloader key should be the first argument to the resource - // @see https://github.com/Yelp/dataloader-codegen/issues/56 - const resourceArgs = [ - { - ..._.omit(requests[0], 'planet_id'), - ['planet_ids']: requests.map((k) => k['planet_id']), - }, - ]; - - let response = await (async (_resourceArgs) => { - // Make a re-assignable variable so flow/eslint doesn't complain - let __resourceArgs = _resourceArgs; - - if (options && options.resourceMiddleware && options.resourceMiddleware.before) { - __resourceArgs = await options.resourceMiddleware.before( - ['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); - } - - return _response; - })(resourceArgs); - - if (!(response instanceof Error)) { - } - - if (!(response instanceof Error)) { - if (!Array.isArray(response)) { - response = new Error( - ['[dataloader-codegen :: getPlanets]', 'Expected response to be an array!'].join( - ' ', - ), - ); - } - } - - if (!(response instanceof Error)) { - /** - * Check to see the resource contains the same number - * of items that we requested. If not, since there's - * no "reorderResultsByKey" specified for this resource, - * we don't know _which_ key's response is missing. Therefore - * it's unsafe to return the response array back. - */ - if (response.length !== requests.length) { - /** - * We must return errors for all keys in this group :( - */ - 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'); - } - } - - /** - * If the resource returns an Error, we'll want to copy and - * return that error as the return value for every request in - * this group. - * - * This allow the error to be cached, and allows the rest of the - * requests made by this DataLoader to succeed. - * - * @see https://github.com/graphql/dataloader#caching-errors - */ - if (response instanceof Error) { - response = requestIDs.map((requestId) => { - /** - * Since we're returning an error object and not the - * expected return type from the resource, this element - * would be unsortable, since it wouldn't have the - * "reorderResultsByKey" attribute. - * - * Let's add it to the error object, as "reorderResultsByValue". - * - * (If we didn't specify that this resource needs - * sorting, then this will be "null" and won't be used.) - */ - const reorderResultsByValue = null; - - // Tell flow that "response" is actually an error object. - // (This is so we can pass it as 'cause' to CaughtResourceError) - invariant(response instanceof Error, 'expected response to be an error'); - - return new CaughtResourceError( - `[dataloader-codegen :: getPlanets] Caught error during call to resource. Error: ${response.stack}`, - response, - reorderResultsByValue, - ); - }); - } - - return response; - }), + _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 ); + } + + return _response; + })(resourceArgs); + + if (!(response instanceof Error)) { + } + + if (!(response instanceof Error)) { + if (!Array.isArray(response)) { + response = new Error( + [ + "[dataloader-codegen :: getPlanets]", + "Expected response to be an array!" + ].join(" ") + ); + } + } + + if (!(response instanceof Error)) { + /** + * Check to see the resource contains the same number + * of items that we requested. If not, since there's + * no "reorderResultsByKey" specified for this resource, + * we don't know _which_ key's response is missing. Therefore + * it's unsafe to return the response array back. + */ + if (response.length !== requests.length) { + /** + * We must return errors for all keys in this group :( + */ + 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" + ); + } + } - return unPartitionResults(requestGroups, groupedResults); - }, - { - ...cacheKeyOptions, - }, - ), - getPeople: new DataLoader< - {| - ...$Diff< - $Call]>, - { - people_ids: $PropertyType< - $Call]>, - 'people_ids', - >, - }, - >, - ...{| - person_id: $ElementType< - $PropertyType<$Call]>, 'people_ids'>, - 0, - >, - |}, - |}, - $ElementType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 0, - >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, - >( /** - * =============================================================== - * Generated DataLoader: getPeople - * =============================================================== + * If the resource returns an Error, we'll want to copy and + * return that error as the return value for every request in + * this group. * - * Resource Config: + * This allow the error to be cached, and allows the rest of the + * requests made by this DataLoader to succeed. * - * ```json - * { - * "docsLink": "https://swapi.dev/documentation#people", - * "isBatchResource": true, - * "batchKey": "people_ids", - * "newKey": "person_id" - * } - * ``` + * @see https://github.com/graphql/dataloader#caching-errors */ - async (keys) => { - invariant( - typeof resources.getPeople === 'function', - [ - '[dataloader-codegen :: getPeople] resources.getPeople is not a function.', - 'Did you pass in an instance of getPeople to "getLoaders"?', - ].join(' '), - ); - + if (response instanceof Error) { + response = requestIDs.map(requestId => { /** - * Chunk up the "keys" array to create a set of "request groups". - * - * We're about to hit a batch resource. In addition to the batch - * key, the resource may take other arguments too. When batching - * up requests, we'll want to look out for where those other - * arguments differ, and send multiple requests so we don't get - * back the wrong info. - * - * In other words, we'll potentially want to send _multiple_ - * requests to the underlying resource batch method in this - * dataloader body. + * Since we're returning an error object and not the + * expected return type from the resource, this element + * would be unsortable, since it wouldn't have the + * "reorderResultsByKey" attribute. * - * ~~~ Why? ~~~ + * Let's add it to the error object, as "reorderResultsByValue". * - * Consider what happens when we get called with arguments where - * the non-batch keys differ. - * - * Example: - * - * ```js - * loaders.foo.load({ foo_id: 2, include_private_data: true }); - * loaders.foo.load({ foo_id: 3, include_private_data: false }); - * loaders.foo.load({ foo_id: 4, include_private_data: false }); - * ``` - * - * If we collected everything up and tried to send the one - * request to the resource as a batch request, how do we know - * what the value for "include_private_data" should be? We're - * going to have to group these up up and send two requests to - * the resource to make sure we're requesting the right stuff. - * - * e.g. We'd need to make the following set of underlying resource - * calls: - * - * ```js - * foo({ foo_ids: [ 2 ], include_private_data: true }); - * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); - * ``` - * - * ~~~ tl;dr ~~~ - * - * When we have calls to .load with differing non batch key args, - * we'll need to send multiple requests to the underlying - * resource to make sure we get the right results back. - * - * Let's create the request groups, where each element in the - * group refers to a position in "keys" (i.e. a call to .load) - * - * Example: - * - * ```js - * partitionItems('bar_id', [ - * { bar_id: 7, include_extra_info: true }, - * { bar_id: 8, include_extra_info: false }, - * { bar_id: 9, include_extra_info: true }, - * ]) - * ``` - * - * Returns: - * `[ [ 0, 2 ], [ 1 ] ]` - * - * We'll refer to each element in the group as a "request ID". + * (If we didn't specify that this resource needs + * sorting, then this will be "null" and won't be used.) */ - const requestGroups = partitionItems('person_id', keys); - - // Map the request groups to a list of Promises - one for each request - const groupedResults = await Promise.all( - requestGroups.map(async (requestIDs) => { - /** - * Select a set of elements in "keys", where all non-batch - * keys should be identical. - * - * We're going to smoosh all these together into one payload to - * send to the resource as a batch request! - */ - const requests = requestIDs.map((id) => keys[id]); - - // For now, we assume that the dataloader key should be the first argument to the resource - // @see https://github.com/Yelp/dataloader-codegen/issues/56 - const resourceArgs = [ - { - ..._.omit(requests[0], 'person_id'), - ['people_ids']: requests.map((k) => k['person_id']), - }, - ]; - - let response = await (async (_resourceArgs) => { - // Make a re-assignable variable so flow/eslint doesn't complain - let __resourceArgs = _resourceArgs; - - if (options && options.resourceMiddleware && options.resourceMiddleware.before) { - __resourceArgs = await options.resourceMiddleware.before(['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); - } - - return _response; - })(resourceArgs); - - if (!(response instanceof Error)) { - } - - if (!(response instanceof Error)) { - if (!Array.isArray(response)) { - response = new Error( - ['[dataloader-codegen :: getPeople]', 'Expected response to be an array!'].join( - ' ', - ), - ); - } - } - - if (!(response instanceof Error)) { - /** - * Check to see the resource contains the same number - * of items that we requested. If not, since there's - * no "reorderResultsByKey" specified for this resource, - * we don't know _which_ key's response is missing. Therefore - * it's unsafe to return the response array back. - */ - if (response.length !== requests.length) { - /** - * We must return errors for all keys in this group :( - */ - 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'); - } - } - - /** - * If the resource returns an Error, we'll want to copy and - * return that error as the return value for every request in - * this group. - * - * This allow the error to be cached, and allows the rest of the - * requests made by this DataLoader to succeed. - * - * @see https://github.com/graphql/dataloader#caching-errors - */ - if (response instanceof Error) { - response = requestIDs.map((requestId) => { - /** - * Since we're returning an error object and not the - * expected return type from the resource, this element - * would be unsortable, since it wouldn't have the - * "reorderResultsByKey" attribute. - * - * Let's add it to the error object, as "reorderResultsByValue". - * - * (If we didn't specify that this resource needs - * sorting, then this will be "null" and won't be used.) - */ - const reorderResultsByValue = null; - - // Tell flow that "response" is actually an error object. - // (This is so we can pass it as 'cause' to CaughtResourceError) - invariant(response instanceof Error, 'expected response to be an error'); - - return new CaughtResourceError( - `[dataloader-codegen :: getPeople] Caught error during call to resource. Error: ${response.stack}`, - response, - reorderResultsByValue, - ); - }); - } - - return response; - }), + const reorderResultsByValue = null; + + // Tell flow that "response" is actually an error object. + // (This is so we can pass it as 'cause' to CaughtResourceError) + invariant( + response instanceof Error, + "expected response to be an error" ); - return unPartitionResults(requestGroups, groupedResults); - }, - { - ...cacheKeyOptions, - }, - ), - getVehicles: new DataLoader< - {| - ...$Diff< - $Call]>, - { - vehicle_ids: $PropertyType< - $Call]>, - 'vehicle_ids', - >, - }, - >, - ...{| - vehicle_id: $ElementType< - $PropertyType<$Call]>, 'vehicle_ids'>, - 0, - >, - |}, - |}, - $ElementType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 0, + return new CaughtResourceError( + `[dataloader-codegen :: getPlanets] Caught error during call to resource. Error: ${response.stack}`, + response, + reorderResultsByValue + ); + }); + } + + return response; + }) + ); + + return unPartitionResults(requestGroups, groupedResults); + }, + { + ...cacheKeyOptions + } + ), + getPeople: new DataLoader< + {| + ...$Diff< + $Call]>, + { + people_ids: $PropertyType< + $Call]>, + "people_ids" + > + } + >, + ...{| + person_id: $ElementType< + $NonMaybeType< + $PropertyType< + $Call]>, + "people_ids" + > >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, - >( + 0 + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType + >, + 0 + >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >( + /** + * =============================================================== + * Generated DataLoader: getPeople + * =============================================================== + * + * Resource Config: + * + * ```json + * { + * "docsLink": "https://swapi.dev/documentation#people", + * "isBatchResource": true, + * "batchKey": "people_ids", + * "newKey": "person_id" + * } + * ``` + */ + async keys => { + invariant( + typeof resources.getPeople === "function", + [ + "[dataloader-codegen :: getPeople] resources.getPeople is not a function.", + 'Did you pass in an instance of getPeople to "getLoaders"?' + ].join(" ") + ); + + /** + * Chunk up the "keys" array to create a set of "request groups". + * + * We're about to hit a batch resource. In addition to the batch + * key, the resource may take other arguments too. When batching + * up requests, we'll want to look out for where those other + * arguments differ, and send multiple requests so we don't get + * back the wrong info. + * + * In other words, we'll potentially want to send _multiple_ + * requests to the underlying resource batch method in this + * dataloader body. + * + * ~~~ Why? ~~~ + * + * Consider what happens when we get called with arguments where + * the non-batch keys differ. + * + * Example: + * + * ```js + * loaders.foo.load({ foo_id: 2, include_private_data: true }); + * loaders.foo.load({ foo_id: 3, include_private_data: false }); + * loaders.foo.load({ foo_id: 4, include_private_data: false }); + * ``` + * + * If we collected everything up and tried to send the one + * request to the resource as a batch request, how do we know + * what the value for "include_private_data" should be? We're + * going to have to group these up up and send two requests to + * the resource to make sure we're requesting the right stuff. + * + * e.g. We'd need to make the following set of underlying resource + * calls: + * + * ```js + * foo({ foo_ids: [ 2 ], include_private_data: true }); + * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); + * ``` + * + * ~~~ tl;dr ~~~ + * + * When we have calls to .load with differing non batch key args, + * we'll need to send multiple requests to the underlying + * resource to make sure we get the right results back. + * + * Let's create the request groups, where each element in the + * group refers to a position in "keys" (i.e. a call to .load) + * + * Example: + * + * ```js + * partitionItems('bar_id', [ + * { bar_id: 7, include_extra_info: true }, + * { bar_id: 8, include_extra_info: false }, + * { bar_id: 9, include_extra_info: true }, + * ]) + * ``` + * + * Returns: + * `[ [ 0, 2 ], [ 1 ] ]` + * + * We'll refer to each element in the group as a "request ID". + */ + const requestGroups = partitionItems("person_id", keys); + + // Map the request groups to a list of Promises - one for each request + const groupedResults = await Promise.all( + requestGroups.map(async requestIDs => { /** - * =============================================================== - * Generated DataLoader: getVehicles - * =============================================================== + * Select a set of elements in "keys", where all non-batch + * keys should be identical. * - * Resource Config: - * - * ```json - * { - * "docsLink": "https://swapi.dev/documentation#vehicles", - * "isBatchResource": true, - * "batchKey": "vehicle_ids", - * "newKey": "vehicle_id" - * } - * ``` + * We're going to smoosh all these together into one payload to + * send to the resource as a batch request! */ - async (keys) => { - invariant( - typeof resources.getVehicles === 'function', - [ - '[dataloader-codegen :: getVehicles] resources.getVehicles is not a function.', - 'Did you pass in an instance of getVehicles to "getLoaders"?', - ].join(' '), + const requests = requestIDs.map(id => keys[id]); + + // For now, we assume that the dataloader key should be the first argument to the resource + // @see https://github.com/Yelp/dataloader-codegen/issues/56 + const resourceArgs = [ + { + ..._.omit(requests[0], "person_id"), + ["people_ids"]: requests.map(k => k["person_id"]) + } + ]; + + let response = await (async _resourceArgs => { + // Make a re-assignable variable so flow/eslint doesn't complain + let __resourceArgs = _resourceArgs; + + if ( + options && + options.resourceMiddleware && + options.resourceMiddleware.before + ) { + __resourceArgs = await options.resourceMiddleware.before( + ["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; /** - * Chunk up the "keys" array to create a set of "request groups". - * - * We're about to hit a batch resource. In addition to the batch - * key, the resource may take other arguments too. When batching - * up requests, we'll want to look out for where those other - * arguments differ, and send multiple requests so we don't get - * back the wrong info. - * - * In other words, we'll potentially want to send _multiple_ - * requests to the underlying resource batch method in this - * dataloader body. - * - * ~~~ Why? ~~~ - * - * Consider what happens when we get called with arguments where - * the non-batch keys differ. - * - * Example: - * - * ```js - * loaders.foo.load({ foo_id: 2, include_private_data: true }); - * loaders.foo.load({ foo_id: 3, include_private_data: false }); - * loaders.foo.load({ foo_id: 4, include_private_data: false }); - * ``` + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. * - * If we collected everything up and tried to send the one - * request to the resource as a batch request, how do we know - * what the value for "include_private_data" should be? We're - * going to have to group these up up and send two requests to - * the resource to make sure we're requesting the right stuff. - * - * e.g. We'd need to make the following set of underlying resource - * calls: - * - * ```js - * foo({ foo_ids: [ 2 ], include_private_data: true }); - * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); - * ``` - * - * ~~~ tl;dr ~~~ - * - * When we have calls to .load with differing non batch key args, - * we'll need to send multiple requests to the underlying - * resource to make sure we get the right results back. - * - * Let's create the request groups, where each element in the - * group refers to a position in "keys" (i.e. a call to .load) - * - * Example: - * - * ```js - * partitionItems('bar_id', [ - * { bar_id: 7, include_extra_info: true }, - * { bar_id: 8, include_extra_info: false }, - * { bar_id: 9, include_extra_info: true }, - * ]) - * ``` + * 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 + ); + } + + return _response; + })(resourceArgs); + + if (!(response instanceof Error)) { + } + + if (!(response instanceof Error)) { + if (!Array.isArray(response)) { + response = new Error( + [ + "[dataloader-codegen :: getPeople]", + "Expected response to be an array!" + ].join(" ") + ); + } + } + + if (!(response instanceof Error)) { + /** + * Check to see the resource contains the same number + * of items that we requested. If not, since there's + * no "reorderResultsByKey" specified for this resource, + * we don't know _which_ key's response is missing. Therefore + * it's unsafe to return the response array back. + */ + if (response.length !== requests.length) { + /** + * We must return errors for all keys in this group :( + */ + 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" + ); + } + } + + /** + * If the resource returns an Error, we'll want to copy and + * return that error as the return value for every request in + * this group. + * + * This allow the error to be cached, and allows the rest of the + * requests made by this DataLoader to succeed. + * + * @see https://github.com/graphql/dataloader#caching-errors + */ + if (response instanceof Error) { + response = requestIDs.map(requestId => { + /** + * Since we're returning an error object and not the + * expected return type from the resource, this element + * would be unsortable, since it wouldn't have the + * "reorderResultsByKey" attribute. * - * Returns: - * `[ [ 0, 2 ], [ 1 ] ]` + * Let's add it to the error object, as "reorderResultsByValue". * - * We'll refer to each element in the group as a "request ID". + * (If we didn't specify that this resource needs + * sorting, then this will be "null" and won't be used.) */ - const requestGroups = partitionItems('vehicle_id', keys); - - // Map the request groups to a list of Promises - one for each request - const groupedResults = await Promise.all( - requestGroups.map(async (requestIDs) => { - /** - * Select a set of elements in "keys", where all non-batch - * keys should be identical. - * - * We're going to smoosh all these together into one payload to - * send to the resource as a batch request! - */ - const requests = requestIDs.map((id) => keys[id]); - - // For now, we assume that the dataloader key should be the first argument to the resource - // @see https://github.com/Yelp/dataloader-codegen/issues/56 - const resourceArgs = [ - { - ..._.omit(requests[0], 'vehicle_id'), - ['vehicle_ids']: requests.map((k) => k['vehicle_id']), - }, - ]; - - let response = await (async (_resourceArgs) => { - // Make a re-assignable variable so flow/eslint doesn't complain - let __resourceArgs = _resourceArgs; - - if (options && options.resourceMiddleware && options.resourceMiddleware.before) { - __resourceArgs = await options.resourceMiddleware.before( - ['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); - } - - return _response; - })(resourceArgs); - - if (!(response instanceof Error)) { - } - - if (!(response instanceof Error)) { - if (!Array.isArray(response)) { - response = new Error( - ['[dataloader-codegen :: getVehicles]', 'Expected response to be an array!'].join( - ' ', - ), - ); - } - } - - if (!(response instanceof Error)) { - /** - * Check to see the resource contains the same number - * of items that we requested. If not, since there's - * no "reorderResultsByKey" specified for this resource, - * we don't know _which_ key's response is missing. Therefore - * it's unsafe to return the response array back. - */ - if (response.length !== requests.length) { - /** - * We must return errors for all keys in this group :( - */ - 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'); - } - } - - /** - * If the resource returns an Error, we'll want to copy and - * return that error as the return value for every request in - * this group. - * - * This allow the error to be cached, and allows the rest of the - * requests made by this DataLoader to succeed. - * - * @see https://github.com/graphql/dataloader#caching-errors - */ - if (response instanceof Error) { - response = requestIDs.map((requestId) => { - /** - * Since we're returning an error object and not the - * expected return type from the resource, this element - * would be unsortable, since it wouldn't have the - * "reorderResultsByKey" attribute. - * - * Let's add it to the error object, as "reorderResultsByValue". - * - * (If we didn't specify that this resource needs - * sorting, then this will be "null" and won't be used.) - */ - const reorderResultsByValue = null; - - // Tell flow that "response" is actually an error object. - // (This is so we can pass it as 'cause' to CaughtResourceError) - invariant(response instanceof Error, 'expected response to be an error'); - - return new CaughtResourceError( - `[dataloader-codegen :: getVehicles] Caught error during call to resource. Error: ${response.stack}`, - response, - reorderResultsByValue, - ); - }); - } - - return response; - }), + const reorderResultsByValue = null; + + // Tell flow that "response" is actually an error object. + // (This is so we can pass it as 'cause' to CaughtResourceError) + invariant( + response instanceof Error, + "expected response to be an error" ); - return unPartitionResults(requestGroups, groupedResults); - }, - { - ...cacheKeyOptions, - }, - ), - getFilms: new DataLoader< - {| - ...$Diff< - $Call]>, - { - film_ids: $PropertyType< - $Call]>, - 'film_ids', - >, - }, - >, - ...{| - film_id: $Call< - ExtractArg, - [ - $PropertyType< - $PropertyType< - $Call]>, - 'film_ids', - >, - 'has', - >, - ], - >, - |}, - |}, - $ElementType< + return new CaughtResourceError( + `[dataloader-codegen :: getPeople] Caught error during call to resource. Error: ${response.stack}`, + response, + reorderResultsByValue + ); + }); + } + + return response; + }) + ); + + return unPartitionResults(requestGroups, groupedResults); + }, + { + ...cacheKeyOptions + } + ), + getVehicles: new DataLoader< + {| + ...$Diff< + $Call]>, + { + vehicle_ids: $PropertyType< + $Call]>, + "vehicle_ids" + > + } + >, + ...{| + vehicle_id: $ElementType< + $NonMaybeType< + $PropertyType< $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, + ExtractArg, + [$PropertyType] >, - 0, + "vehicle_ids" + > >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, - >( + 0 + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType + >, + 0 + >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >( + /** + * =============================================================== + * Generated DataLoader: getVehicles + * =============================================================== + * + * Resource Config: + * + * ```json + * { + * "docsLink": "https://swapi.dev/documentation#vehicles", + * "isBatchResource": true, + * "batchKey": "vehicle_ids", + * "newKey": "vehicle_id" + * } + * ``` + */ + async keys => { + invariant( + typeof resources.getVehicles === "function", + [ + "[dataloader-codegen :: getVehicles] resources.getVehicles is not a function.", + 'Did you pass in an instance of getVehicles to "getLoaders"?' + ].join(" ") + ); + + /** + * Chunk up the "keys" array to create a set of "request groups". + * + * We're about to hit a batch resource. In addition to the batch + * key, the resource may take other arguments too. When batching + * up requests, we'll want to look out for where those other + * arguments differ, and send multiple requests so we don't get + * back the wrong info. + * + * In other words, we'll potentially want to send _multiple_ + * requests to the underlying resource batch method in this + * dataloader body. + * + * ~~~ Why? ~~~ + * + * Consider what happens when we get called with arguments where + * the non-batch keys differ. + * + * Example: + * + * ```js + * loaders.foo.load({ foo_id: 2, include_private_data: true }); + * loaders.foo.load({ foo_id: 3, include_private_data: false }); + * loaders.foo.load({ foo_id: 4, include_private_data: false }); + * ``` + * + * If we collected everything up and tried to send the one + * request to the resource as a batch request, how do we know + * what the value for "include_private_data" should be? We're + * going to have to group these up up and send two requests to + * the resource to make sure we're requesting the right stuff. + * + * e.g. We'd need to make the following set of underlying resource + * calls: + * + * ```js + * foo({ foo_ids: [ 2 ], include_private_data: true }); + * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); + * ``` + * + * ~~~ tl;dr ~~~ + * + * When we have calls to .load with differing non batch key args, + * we'll need to send multiple requests to the underlying + * resource to make sure we get the right results back. + * + * Let's create the request groups, where each element in the + * group refers to a position in "keys" (i.e. a call to .load) + * + * Example: + * + * ```js + * partitionItems('bar_id', [ + * { bar_id: 7, include_extra_info: true }, + * { bar_id: 8, include_extra_info: false }, + * { bar_id: 9, include_extra_info: true }, + * ]) + * ``` + * + * Returns: + * `[ [ 0, 2 ], [ 1 ] ]` + * + * We'll refer to each element in the group as a "request ID". + */ + const requestGroups = partitionItems("vehicle_id", keys); + + // Map the request groups to a list of Promises - one for each request + const groupedResults = await Promise.all( + requestGroups.map(async requestIDs => { /** - * =============================================================== - * Generated DataLoader: getFilms - * =============================================================== - * - * Resource Config: + * Select a set of elements in "keys", where all non-batch + * keys should be identical. * - * ```json - * { - * "docsLink": "https://swapi.dev/documentation#films", - * "isBatchResource": true, - * "batchKey": "film_ids", - * "newKey": "film_id", - * "isBatchKeyASet": true - * } - * ``` + * We're going to smoosh all these together into one payload to + * send to the resource as a batch request! */ - async (keys) => { - invariant( - typeof resources.getFilms === 'function', - [ - '[dataloader-codegen :: getFilms] resources.getFilms is not a function.', - 'Did you pass in an instance of getFilms to "getLoaders"?', - ].join(' '), + const requests = requestIDs.map(id => keys[id]); + + // For now, we assume that the dataloader key should be the first argument to the resource + // @see https://github.com/Yelp/dataloader-codegen/issues/56 + const resourceArgs = [ + { + ..._.omit(requests[0], "vehicle_id"), + ["vehicle_ids"]: requests.map(k => k["vehicle_id"]) + } + ]; + + let response = await (async _resourceArgs => { + // Make a re-assignable variable so flow/eslint doesn't complain + let __resourceArgs = _resourceArgs; + + if ( + options && + options.resourceMiddleware && + options.resourceMiddleware.before + ) { + __resourceArgs = await options.resourceMiddleware.before( + ["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; /** - * Chunk up the "keys" array to create a set of "request groups". - * - * We're about to hit a batch resource. In addition to the batch - * key, the resource may take other arguments too. When batching - * up requests, we'll want to look out for where those other - * arguments differ, and send multiple requests so we don't get - * back the wrong info. - * - * In other words, we'll potentially want to send _multiple_ - * requests to the underlying resource batch method in this - * dataloader body. - * - * ~~~ Why? ~~~ - * - * Consider what happens when we get called with arguments where - * the non-batch keys differ. - * - * Example: - * - * ```js - * loaders.foo.load({ foo_id: 2, include_private_data: true }); - * loaders.foo.load({ foo_id: 3, include_private_data: false }); - * loaders.foo.load({ foo_id: 4, include_private_data: false }); - * ``` - * - * If we collected everything up and tried to send the one - * request to the resource as a batch request, how do we know - * what the value for "include_private_data" should be? We're - * going to have to group these up up and send two requests to - * the resource to make sure we're requesting the right stuff. - * - * e.g. We'd need to make the following set of underlying resource - * calls: - * - * ```js - * foo({ foo_ids: [ 2 ], include_private_data: true }); - * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); - * ``` + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. * - * ~~~ tl;dr ~~~ - * - * When we have calls to .load with differing non batch key args, - * we'll need to send multiple requests to the underlying - * resource to make sure we get the right results back. - * - * Let's create the request groups, where each element in the - * group refers to a position in "keys" (i.e. a call to .load) - * - * Example: - * - * ```js - * partitionItems('bar_id', [ - * { bar_id: 7, include_extra_info: true }, - * { bar_id: 8, include_extra_info: false }, - * { bar_id: 9, include_extra_info: true }, - * ]) - * ``` + * 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 + ); + } + + return _response; + })(resourceArgs); + + if (!(response instanceof Error)) { + } + + if (!(response instanceof Error)) { + if (!Array.isArray(response)) { + response = new Error( + [ + "[dataloader-codegen :: getVehicles]", + "Expected response to be an array!" + ].join(" ") + ); + } + } + + if (!(response instanceof Error)) { + /** + * Check to see the resource contains the same number + * of items that we requested. If not, since there's + * no "reorderResultsByKey" specified for this resource, + * we don't know _which_ key's response is missing. Therefore + * it's unsafe to return the response array back. + */ + if (response.length !== requests.length) { + /** + * We must return errors for all keys in this group :( + */ + 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" + ); + } + } + + /** + * If the resource returns an Error, we'll want to copy and + * return that error as the return value for every request in + * this group. + * + * This allow the error to be cached, and allows the rest of the + * requests made by this DataLoader to succeed. + * + * @see https://github.com/graphql/dataloader#caching-errors + */ + if (response instanceof Error) { + response = requestIDs.map(requestId => { + /** + * Since we're returning an error object and not the + * expected return type from the resource, this element + * would be unsortable, since it wouldn't have the + * "reorderResultsByKey" attribute. * - * Returns: - * `[ [ 0, 2 ], [ 1 ] ]` + * Let's add it to the error object, as "reorderResultsByValue". * - * We'll refer to each element in the group as a "request ID". + * (If we didn't specify that this resource needs + * sorting, then this will be "null" and won't be used.) */ - const requestGroups = partitionItems('film_id', keys); - - // Map the request groups to a list of Promises - one for each request - const groupedResults = await Promise.all( - requestGroups.map(async (requestIDs) => { - /** - * Select a set of elements in "keys", where all non-batch - * keys should be identical. - * - * We're going to smoosh all these together into one payload to - * send to the resource as a batch request! - */ - const requests = requestIDs.map((id) => keys[id]); - - // For now, we assume that the dataloader key should be the first argument to the resource - // @see https://github.com/Yelp/dataloader-codegen/issues/56 - const resourceArgs = [ - { - ..._.omit(requests[0], 'film_id'), - ['film_ids']: requests.map((k) => k['film_id']), - }, - ]; - - let response = await (async (_resourceArgs) => { - // Make a re-assignable variable so flow/eslint doesn't complain - let __resourceArgs = _resourceArgs; - - if (options && options.resourceMiddleware && options.resourceMiddleware.before) { - __resourceArgs = await options.resourceMiddleware.before(['getFilms'], __resourceArgs); - } - - let _response; - try { - // Finally, call the resource! - _response = await resources.getFilms(...__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(['getFilms'], 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 :: getFilms] 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(['getFilms'], _response); - } - - return _response; - })(resourceArgs); - - if (!(response instanceof Error)) { - } - - if (!(response instanceof Error)) { - if (!Array.isArray(response)) { - response = new Error( - ['[dataloader-codegen :: getFilms]', 'Expected response to be an array!'].join(' '), - ); - } - } - - if (!(response instanceof Error)) { - /** - * Check to see the resource contains the same number - * of items that we requested. If not, since there's - * no "reorderResultsByKey" specified for this resource, - * we don't know _which_ key's response is missing. Therefore - * it's unsafe to return the response array back. - */ - if (response.length !== requests.length) { - /** - * We must return errors for all keys in this group :( - */ - response = new BatchItemNotFoundError( - [ - `[dataloader-codegen :: getFilms] 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'); - } - } - - /** - * If the resource returns an Error, we'll want to copy and - * return that error as the return value for every request in - * this group. - * - * This allow the error to be cached, and allows the rest of the - * requests made by this DataLoader to succeed. - * - * @see https://github.com/graphql/dataloader#caching-errors - */ - if (response instanceof Error) { - response = requestIDs.map((requestId) => { - /** - * Since we're returning an error object and not the - * expected return type from the resource, this element - * would be unsortable, since it wouldn't have the - * "reorderResultsByKey" attribute. - * - * Let's add it to the error object, as "reorderResultsByValue". - * - * (If we didn't specify that this resource needs - * sorting, then this will be "null" and won't be used.) - */ - const reorderResultsByValue = null; - - // Tell flow that "response" is actually an error object. - // (This is so we can pass it as 'cause' to CaughtResourceError) - invariant(response instanceof Error, 'expected response to be an error'); - - return new CaughtResourceError( - `[dataloader-codegen :: getFilms] Caught error during call to resource. Error: ${response.stack}`, - response, - reorderResultsByValue, - ); - }); - } - - return response; - }), + const reorderResultsByValue = null; + + // Tell flow that "response" is actually an error object. + // (This is so we can pass it as 'cause' to CaughtResourceError) + invariant( + response instanceof Error, + "expected response to be an error" ); - return unPartitionResults(requestGroups, groupedResults); - }, - { - ...cacheKeyOptions, - }, - ), - getFilmsV2: new DataLoader< - {| - ...$Diff< - $Call]>, - { - film_ids: $PropertyType< - $Call]>, - 'film_ids', - >, - }, - >, - ...{| - film_id: $ElementType< - $PropertyType<$Call]>, 'film_ids'>, - 0, - >, - |}, - |}, - $ElementType< + return new CaughtResourceError( + `[dataloader-codegen :: getVehicles] Caught error during call to resource. Error: ${response.stack}`, + response, + reorderResultsByValue + ); + }); + } + + return response; + }) + ); + + return unPartitionResults(requestGroups, groupedResults); + }, + { + ...cacheKeyOptions + } + ), + getFilms: new DataLoader< + {| + ...$Diff< + $Call]>, + { + film_ids: $PropertyType< + $Call]>, + "film_ids" + > + } + >, + ...{| + film_id: $Call< + ExtractArg, + [ + $PropertyType< $PropertyType< - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - 'properties', + $Call]>, + "film_ids" >, - 0, - >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, - >( + "has" + > + ] + > + |} + |}, + $ElementType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType + >, + 0 + >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >( + /** + * =============================================================== + * Generated DataLoader: getFilms + * =============================================================== + * + * Resource Config: + * + * ```json + * { + * "docsLink": "https://swapi.dev/documentation#films", + * "isBatchResource": true, + * "batchKey": "film_ids", + * "newKey": "film_id", + * "isBatchKeyASet": true + * } + * ``` + */ + async keys => { + invariant( + typeof resources.getFilms === "function", + [ + "[dataloader-codegen :: getFilms] resources.getFilms is not a function.", + 'Did you pass in an instance of getFilms to "getLoaders"?' + ].join(" ") + ); + + /** + * Chunk up the "keys" array to create a set of "request groups". + * + * We're about to hit a batch resource. In addition to the batch + * key, the resource may take other arguments too. When batching + * up requests, we'll want to look out for where those other + * arguments differ, and send multiple requests so we don't get + * back the wrong info. + * + * In other words, we'll potentially want to send _multiple_ + * requests to the underlying resource batch method in this + * dataloader body. + * + * ~~~ Why? ~~~ + * + * Consider what happens when we get called with arguments where + * the non-batch keys differ. + * + * Example: + * + * ```js + * loaders.foo.load({ foo_id: 2, include_private_data: true }); + * loaders.foo.load({ foo_id: 3, include_private_data: false }); + * loaders.foo.load({ foo_id: 4, include_private_data: false }); + * ``` + * + * If we collected everything up and tried to send the one + * request to the resource as a batch request, how do we know + * what the value for "include_private_data" should be? We're + * going to have to group these up up and send two requests to + * the resource to make sure we're requesting the right stuff. + * + * e.g. We'd need to make the following set of underlying resource + * calls: + * + * ```js + * foo({ foo_ids: [ 2 ], include_private_data: true }); + * foo({ foo_ids: [ 3, 4 ], include_private_data: false }); + * ``` + * + * ~~~ tl;dr ~~~ + * + * When we have calls to .load with differing non batch key args, + * we'll need to send multiple requests to the underlying + * resource to make sure we get the right results back. + * + * Let's create the request groups, where each element in the + * group refers to a position in "keys" (i.e. a call to .load) + * + * Example: + * + * ```js + * partitionItems('bar_id', [ + * { bar_id: 7, include_extra_info: true }, + * { bar_id: 8, include_extra_info: false }, + * { bar_id: 9, include_extra_info: true }, + * ]) + * ``` + * + * Returns: + * `[ [ 0, 2 ], [ 1 ] ]` + * + * We'll refer to each element in the group as a "request ID". + */ + const requestGroups = partitionItems("film_id", keys); + + // Map the request groups to a list of Promises - one for each request + const groupedResults = await Promise.all( + requestGroups.map(async requestIDs => { /** - * =============================================================== - * Generated DataLoader: getFilmsV2 - * =============================================================== + * Select a set of elements in "keys", where all non-batch + * keys should be identical. * - * Resource Config: - * - * ```json - * { - * "docsLink": "https://swapi.dev/documentation#films", - * "isBatchResource": true, - * "batchKey": "film_ids", - * "newKey": "film_id", - * "nestedPath": "properties", - * "propertyBatchKey": "properties", - * "responseKey": "id" - * } - * ``` + * We're going to smoosh all these together into one payload to + * send to the resource as a batch request! */ - async (keys) => { - invariant( - typeof resources.getFilmsV2 === 'function', - [ - '[dataloader-codegen :: getFilmsV2] resources.getFilmsV2 is not a function.', - 'Did you pass in an instance of getFilmsV2 to "getLoaders"?', - ].join(' '), + const requests = requestIDs.map(id => keys[id]); + + // For now, we assume that the dataloader key should be the first argument to the resource + // @see https://github.com/Yelp/dataloader-codegen/issues/56 + const resourceArgs = [ + { + ..._.omit(requests[0], "film_id"), + ["film_ids"]: requests.map(k => k["film_id"]) + } + ]; + + let response = await (async _resourceArgs => { + // Make a re-assignable variable so flow/eslint doesn't complain + let __resourceArgs = _resourceArgs; + + if ( + options && + options.resourceMiddleware && + options.resourceMiddleware.before + ) { + __resourceArgs = await options.resourceMiddleware.before( + ["getFilms"], + __resourceArgs ); + } + + let _response; + try { + // Finally, call the resource! + _response = await resources.getFilms(...__resourceArgs); + } catch (error) { + const errorHandler = + options && typeof options.errorHandler === "function" + ? options.errorHandler + : defaultErrorHandler; /** - * When we have calls to .load with differing non batch key args, - * we'll need to send multiple requests to the underlying - * resource to make sure we get the right results back. - * - * Let's create the request groups, where each element in the - * group refers to a position in "keys" (i.e. a call to .load) + * Apply some error handling to catch and handle all errors/rejected promises. errorHandler must return an Error object. * - * Example: - * - * ```js - * partitionItems(['bar_id', 'properties'], [ - * { bar_id: 7, properties: ['property_1'], include_extra_info: true }, - * { bar_id: 8, properties: ['property_2'], include_extra_info: false }, - * { bar_id: 9, properties: ['property_3'], include_extra_info: true }, - * ]) - * ``` + * 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(["getFilms"], 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 :: getFilms] 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( + ["getFilms"], + _response + ); + } + + return _response; + })(resourceArgs); + + if (!(response instanceof Error)) { + } + + if (!(response instanceof Error)) { + if (!Array.isArray(response)) { + response = new Error( + [ + "[dataloader-codegen :: getFilms]", + "Expected response to be an array!" + ].join(" ") + ); + } + } + + if (!(response instanceof Error)) { + /** + * Check to see the resource contains the same number + * of items that we requested. If not, since there's + * no "reorderResultsByKey" specified for this resource, + * we don't know _which_ key's response is missing. Therefore + * it's unsafe to return the response array back. + */ + if (response.length !== requests.length) { + /** + * We must return errors for all keys in this group :( + */ + response = new BatchItemNotFoundError( + [ + `[dataloader-codegen :: getFilms] 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" + ); + } + } + + /** + * If the resource returns an Error, we'll want to copy and + * return that error as the return value for every request in + * this group. + * + * This allow the error to be cached, and allows the rest of the + * requests made by this DataLoader to succeed. + * + * @see https://github.com/graphql/dataloader#caching-errors + */ + if (response instanceof Error) { + response = requestIDs.map(requestId => { + /** + * Since we're returning an error object and not the + * expected return type from the resource, this element + * would be unsortable, since it wouldn't have the + * "reorderResultsByKey" attribute. * - * Returns: - * `[ [ 0, 2 ], [ 1 ] ]` + * Let's add it to the error object, as "reorderResultsByValue". * - * We'll refer to each element in the group as a "request ID". + * (If we didn't specify that this resource needs + * sorting, then this will be "null" and won't be used.) */ - const requestGroups = partitionItems(['film_id', 'properties'], keys); - - // Map the request groups to a list of Promises - one for each request - const groupedResults = await Promise.all( - requestGroups.map(async (requestIDs) => { - /** - * Select a set of elements in "keys", where all non-batch - * keys should be identical. - * - * We're going to smoosh all these together into one payload to - * send to the resource as a batch request! - */ - const requests = requestIDs.map((id) => keys[id]); - - // For now, we assume that the dataloader key should be the first argument to the resource - // @see https://github.com/Yelp/dataloader-codegen/issues/56 - const resourceArgs = [ - { - ..._.omit(requests[0], 'film_id'), - ['film_ids']: requests.map((k) => k['film_id']), - }, - ]; - - let response = await (async (_resourceArgs) => { - // Make a re-assignable variable so flow/eslint doesn't complain - let __resourceArgs = _resourceArgs; - - if (options && options.resourceMiddleware && options.resourceMiddleware.before) { - __resourceArgs = await options.resourceMiddleware.before( - ['getFilmsV2'], - __resourceArgs, - ); - } - - let _response; - try { - // Finally, call the resource! - _response = await resources.getFilmsV2(...__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(['getFilmsV2'], 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 :: getFilmsV2] 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(['getFilmsV2'], _response); - } - - return _response; - })(resourceArgs); - - if (!(response instanceof Error)) { - /** - * Un-nest the actual data from the resource return value. - * - * e.g. - * ```js - * { - * foos: [ - * { id: 1, value: 'hello' }, - * { id: 2, value: 'world' }, - * ] - * } - * ``` - * - * Becomes - * - * ```js - * [ - * { id: 1, value: 'hello' }, - * { id: 2, value: 'world' }, - * ] - * ``` - */ - response = _.get( - response, - 'properties', - new Error( - [ - '[dataloader-codegen :: getFilmsV2]', - 'Tried to un-nest the response from the resource, but', - ".get(response, 'properties')", - 'was empty!', - ].join(' '), - ), - ); - } - - if (!(response instanceof Error)) { - if (!Array.isArray(response)) { - response = new Error( - ['[dataloader-codegen :: getFilmsV2]', 'Expected response to be an array!'].join( - ' ', - ), - ); - } - } - - /** - * If the resource returns an Error, we'll want to copy and - * return that error as the return value for every request in - * this group. - * - * This allow the error to be cached, and allows the rest of the - * requests made by this DataLoader to succeed. - * - * @see https://github.com/graphql/dataloader#caching-errors - */ - if (response instanceof Error) { - response = requestIDs.map((requestId) => { - /** - * Since we're returning an error object and not the - * expected return type from the resource, this element - * would be unsortable, since it wouldn't have the - * "reorderResultsByKey" attribute. - * - * Let's add it to the error object, as "reorderResultsByValue". - * - * (If we didn't specify that this resource needs - * sorting, then this will be "null" and won't be used.) - */ - const reorderResultsByValue = null; - - // Tell flow that "response" is actually an error object. - // (This is so we can pass it as 'cause' to CaughtResourceError) - invariant(response instanceof Error, 'expected response to be an error'); - - return new CaughtResourceError( - `[dataloader-codegen :: getFilmsV2] Caught error during call to resource. Error: ${response.stack}`, - response, - reorderResultsByValue, - ); - }); - } - - return response; - }), + const reorderResultsByValue = null; + + // Tell flow that "response" is actually an error object. + // (This is so we can pass it as 'cause' to CaughtResourceError) + invariant( + response instanceof Error, + "expected response to be an error" + ); + + return new CaughtResourceError( + `[dataloader-codegen :: getFilms] Caught error during call to resource. Error: ${response.stack}`, + response, + reorderResultsByValue ); + }); + } + + return response; + }) + ); + + return unPartitionResults(requestGroups, groupedResults); + }, + { + ...cacheKeyOptions + } + ), + getFilmsV2: new DataLoader< + {| + ...$Diff< + $Call]>, + { + film_ids: $PropertyType< + $Call]>, + "film_ids" + > + } + >, + ...{| + film_id: $ElementType< + $NonMaybeType< + $PropertyType< + $Call]>, + "film_ids" + > + >, + 0 + > + |} + |}, + $ElementType< + $PropertyType< + $Call< + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType + >, + "properties" + >, + 0 + >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >( + /** + * =============================================================== + * Generated DataLoader: getFilmsV2 + * =============================================================== + * + * Resource Config: + * + * ```json + * { + * "docsLink": "https://swapi.dev/documentation#films", + * "isBatchResource": true, + * "batchKey": "film_ids", + * "newKey": "film_id", + * "nestedPath": "properties", + * "propertyBatchKey": "properties", + * "responseKey": "id" + * } + * ``` + */ + async keys => { + invariant( + typeof resources.getFilmsV2 === "function", + [ + "[dataloader-codegen :: getFilmsV2] resources.getFilmsV2 is not a function.", + 'Did you pass in an instance of getFilmsV2 to "getLoaders"?' + ].join(" ") + ); + + /** + * When we have calls to .load with differing non batch key args, + * we'll need to send multiple requests to the underlying + * resource to make sure we get the right results back. + * + * Let's create the request groups, where each element in the + * group refers to a position in "keys" (i.e. a call to .load) + * + * Example: + * + * ```js + * partitionItems(['bar_id', 'properties'], [ + * { bar_id: 7, properties: ['property_1'], include_extra_info: true }, + * { bar_id: 8, properties: ['property_2'], include_extra_info: false }, + * { bar_id: 9, properties: ['property_3'], include_extra_info: true }, + * ]) + * ``` + * + * Returns: + * `[ [ 0, 2 ], [ 1 ] ]` + * + * We'll refer to each element in the group as a "request ID". + */ + const requestGroups = partitionItems(["film_id", "properties"], keys); + + // Map the request groups to a list of Promises - one for each request + const groupedResults = await Promise.all( + requestGroups.map(async requestIDs => { + /** + * Select a set of elements in "keys", where all non-batch + * keys should be identical. + * + * We're going to smoosh all these together into one payload to + * send to the resource as a batch request! + */ + const requests = requestIDs.map(id => keys[id]); + + // For now, we assume that the dataloader key should be the first argument to the resource + // @see https://github.com/Yelp/dataloader-codegen/issues/56 + const resourceArgs = [ + { + ..._.omit(requests[0], "film_id"), + ["film_ids"]: requests.map(k => k["film_id"]) + } + ]; + + let response = await (async _resourceArgs => { + // Make a re-assignable variable so flow/eslint doesn't complain + let __resourceArgs = _resourceArgs; + + if ( + options && + options.resourceMiddleware && + options.resourceMiddleware.before + ) { + __resourceArgs = await options.resourceMiddleware.before( + ["getFilmsV2"], + __resourceArgs + ); + } + + let _response; + try { + // Finally, call the resource! + _response = await resources.getFilmsV2(...__resourceArgs); + } catch (error) { + const errorHandler = + options && typeof options.errorHandler === "function" + ? options.errorHandler + : defaultErrorHandler; /** - * The resource might contain less number of items that we requested. - * We need the value of batchKey and propertyBatchKey in requests group to help us split the results - * back up into the order that they were requested. + * 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. */ - const batchKeyPartition = getBatchKeysForPartitionItems('film_id', ['film_id', 'properties'], keys); - const propertyBatchKeyPartiion = getBatchKeysForPartitionItems( - 'properties', - ['film_id', 'properties'], - keys, + _response = await errorHandler(["getFilmsV2"], 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 :: getFilmsV2] 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( + ["getFilmsV2"], + _response ); - return unPartitionResultsByBatchKeyPartition( - 'film_id', - 'properties', - 'id', - batchKeyPartition, - propertyBatchKeyPartiion, - requestGroups, - groupedResults, + } + + return _response; + })(resourceArgs); + + if (!(response instanceof Error)) { + /** + * Un-nest the actual data from the resource return value. + * + * e.g. + * ```js + * { + * foos: [ + * { id: 1, value: 'hello' }, + * { id: 2, value: 'world' }, + * ] + * } + * ``` + * + * Becomes + * + * ```js + * [ + * { id: 1, value: 'hello' }, + * { id: 2, value: 'world' }, + * ] + * ``` + */ + response = _.get( + response, + "properties", + new Error( + [ + "[dataloader-codegen :: getFilmsV2]", + "Tried to un-nest the response from the resource, but", + ".get(response, 'properties')", + "was empty!" + ].join(" ") + ) + ); + } + + if (!(response instanceof Error)) { + if (!Array.isArray(response)) { + response = new Error( + [ + "[dataloader-codegen :: getFilmsV2]", + "Expected response to be an array!" + ].join(" ") ); - }, - { - ...cacheKeyOptions, - }, - ), - getRoot: new DataLoader< - $Call]>, - $Call< - ExtractPromisedReturnValue<[$Call]>]>, - $PropertyType, - >, - // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". - string, - >( + } + } + /** - * =============================================================== - * Generated DataLoader: getRoot - * =============================================================== + * If the resource returns an Error, we'll want to copy and + * return that error as the return value for every request in + * this group. * - * Resource Config: + * This allow the error to be cached, and allows the rest of the + * requests made by this DataLoader to succeed. * - * ```json - * { - * "docsLink": "https://swapi.dev/documentation#root", - * "isBatchResource": false - * } - * ``` + * @see https://github.com/graphql/dataloader#caching-errors */ - async (keys) => { - const responses = await Promise.all( - keys.map(async (key) => { - invariant( - typeof resources.getRoot === 'function', - [ - '[dataloader-codegen :: getRoot] resources.getRoot is not a function.', - 'Did you pass in an instance of getRoot to "getLoaders"?', - ].join(' '), - ); - - // For now, we assume that the dataloader key should be the first argument to the resource - // @see https://github.com/Yelp/dataloader-codegen/issues/56 - const resourceArgs = [key]; - - return await (async (_resourceArgs) => { - // Make a re-assignable variable so flow/eslint doesn't complain - let __resourceArgs = _resourceArgs; - - if (options && options.resourceMiddleware && options.resourceMiddleware.before) { - __resourceArgs = await options.resourceMiddleware.before(['getRoot'], __resourceArgs); - } - - let _response; - try { - // Finally, call the resource! - _response = await resources.getRoot(...__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(['getRoot'], 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 :: getRoot] 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(['getRoot'], _response); - } - - return _response; - })(resourceArgs); - }), + if (response instanceof Error) { + response = requestIDs.map(requestId => { + /** + * Since we're returning an error object and not the + * expected return type from the resource, this element + * would be unsortable, since it wouldn't have the + * "reorderResultsByKey" attribute. + * + * Let's add it to the error object, as "reorderResultsByValue". + * + * (If we didn't specify that this resource needs + * sorting, then this will be "null" and won't be used.) + */ + const reorderResultsByValue = null; + + // Tell flow that "response" is actually an error object. + // (This is so we can pass it as 'cause' to CaughtResourceError) + invariant( + response instanceof Error, + "expected response to be an error" ); - return responses; - }, - { - ...cacheKeyOptions, - }, - ), - }); + return new CaughtResourceError( + `[dataloader-codegen :: getFilmsV2] Caught error during call to resource. Error: ${response.stack}`, + response, + reorderResultsByValue + ); + }); + } + + return response; + }) + ); + + /** + * The resource might contain less number of items that we requested. + * We need the value of batchKey and propertyBatchKey in requests group to help us split the results + * back up into the order that they were requested. + */ + const batchKeyPartition = getBatchKeysForPartitionItems( + "film_id", + ["film_id", "properties"], + keys + ); + const propertyBatchKeyPartiion = getBatchKeysForPartitionItems( + "properties", + ["film_id", "properties"], + keys + ); + return unPartitionResultsByBatchKeyPartition( + "film_id", + "properties", + "id", + batchKeyPartition, + propertyBatchKeyPartiion, + requestGroups, + groupedResults + ); + }, + { + ...cacheKeyOptions + } + ), + getRoot: new DataLoader< + $Call]>, + $Call< + ExtractPromisedReturnValue< + [$Call]>] + >, + $PropertyType + >, + // This third argument is the cache key type. Since we use objectHash in cacheKeyOptions, this is "string". + string + >( + /** + * =============================================================== + * Generated DataLoader: getRoot + * =============================================================== + * + * Resource Config: + * + * ```json + * { + * "docsLink": "https://swapi.dev/documentation#root", + * "isBatchResource": false + * } + * ``` + */ + async keys => { + const responses = await Promise.all( + keys.map(async key => { + invariant( + typeof resources.getRoot === "function", + [ + "[dataloader-codegen :: getRoot] resources.getRoot is not a function.", + 'Did you pass in an instance of getRoot to "getLoaders"?' + ].join(" ") + ); + + // For now, we assume that the dataloader key should be the first argument to the resource + // @see https://github.com/Yelp/dataloader-codegen/issues/56 + const resourceArgs = [key]; + + return await (async _resourceArgs => { + // Make a re-assignable variable so flow/eslint doesn't complain + let __resourceArgs = _resourceArgs; + + if ( + options && + options.resourceMiddleware && + options.resourceMiddleware.before + ) { + __resourceArgs = await options.resourceMiddleware.before( + ["getRoot"], + __resourceArgs + ); + } + + let _response; + try { + // Finally, call the resource! + _response = await resources.getRoot(...__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(["getRoot"], 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 :: getRoot] 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( + ["getRoot"], + _response + ); + } + + return _response; + })(resourceArgs); + }) + ); + + return responses; + }, + { + ...cacheKeyOptions + } + ) + }); } diff --git a/examples/swapi/swapi-ts.ts b/examples/swapi/swapi-ts.ts new file mode 100644 index 0000000..c9005d8 --- /dev/null +++ b/examples/swapi/swapi-ts.ts @@ -0,0 +1,130 @@ +/** + * Typescript version clientlib for a subset of data in https://swapi.dev/ + */ + +import fetch from 'node-fetch'; +const SWAPI_URL = 'https://swapi.dev/api/'; + +export interface SWAPI_Planet { + readonly name: string; + readonly rotation_period: string; + readonly orbital_period: string; + readonly diameter: string; + readonly climate: string; + readonly gravity: string; + readonly terrain: string; + readonly surface_water: string; + readonly population: string; + readonly residents: readonly string[]; + readonly films: readonly string[]; + readonly created: string; + readonly edited: string; + readonly url: string; +} + +export interface SWAPI_Person { + readonly name: string, + readonly height: string, + readonly mass: string, + readonly hair_color: string, + readonly skin_color: string, + readonly eye_color: string, + readonly birth_year: string, + readonly gender: string, + readonly homeworld: string, + readonly films: readonly string[], + readonly species: readonly string[], + readonly vehicles: readonly string[], + readonly starships: readonly string[], + readonly created: string, + readonly edited: string, + readonly url: string, +} + +export interface SWAPI_Film { + readonly title: string; + readonly episode_id: number; + readonly opening_crawl: string; + readonly director: string; + readonly producer: string; + readonly release_date: string; + readonly species: readonly string[]; + readonly starships: readonly string[]; + readonly vehicles: readonly string[]; + readonly characters: readonly string[]; + readonly planets: readonly string[]; + readonly url: string; + readonly created: string; + readonly edited: string; +} + +export interface SWAPI_Film_V2 { + readonly properties: ReadonlyArray<{ + id: number; + title?: string; + episode_id?: number; + director?: string; + producer?: string; + }>; +} + +export interface SWAPI_Vehicle { + readonly name: string; + readonly key: string; +} + +interface SWAPI_Root { + readonly people: string; + readonly planets: string; + readonly films: string; + readonly species: string; + readonly vehicles: string; + readonly starships: string; +} + +export interface SWAPIClientlibTypes { + getPlanets: (params: { readonly planet_ids: readonly number[] }) => Promise; + getPeople: (params: { readonly people_ids: readonly number[] }) => Promise; + getVehicles: (params: { readonly vehicle_ids: readonly number[] }) => Promise; + getFilms: (params: { film_ids: Set }) => Promise; + getFilmsV2: (params: { + readonly film_ids: readonly number[]; + readonly properties: readonly string[]; + }) => Promise; + getRoot: (params: {}) => Promise; +} + +export default function (): SWAPIClientlibTypes { + return { + getPlanets: ({ planet_ids: planetIds }) => + Promise.all( + planetIds.map((id) => fetch(new URL(`planets/${id}`, SWAPI_URL)).then((res) => res.json())), + ), + getPeople: ({ people_ids: peopleIds }) => + Promise.all( + peopleIds.map((id) => fetch(new URL(`people/${id}`, SWAPI_URL)).then((res) => res.json())), + ), + getVehicles: ({ vehicle_ids: vehicleIds }) => + Promise.all( + vehicleIds.map((id) => fetch(new URL(`vehicles/${id}`, SWAPI_URL)).then((res) => res.json())), + ), + getFilms: ({ film_ids: filmIds }) => + Promise.all( + [...filmIds].map((id) => fetch(new URL(`films/${id}`, SWAPI_URL)).then((res) => res.json())), + ), + getFilmsV2: ({ film_ids: filmIds, properties }) => { + return Promise.resolve({ + properties: [ + { + id: 4, + director: 'George Lucas', + producer: 'Rick McCallum', + episode_id: 1, + title: 'The Phantom Menace', + }, + ], + }); + }, + getRoot: ({ }) => fetch(SWAPI_URL).then((res) => res.json()), + }; +}; diff --git a/package.json b/package.json index 3430f3c..cf29402 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@types/js-yaml": "^3.12.1", "@types/lodash": "^4.14.144", "@types/node": "^12.7.12", + "@types/node-fetch": "^2.6.9", "@types/object-hash": "^1.3.0", "@types/prettier": "^1.18.3", "@types/yargs": "^13.0.3", diff --git a/src/config.ts b/src/config.ts index a57f83d..77e0e47 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,9 +3,10 @@ import path from 'path'; import yaml from 'js-yaml'; import Ajv from 'ajv'; +export type LanguageOption = 'flow' | 'typescript'; export interface GlobalConfig { typings: { - language: 'flow'; + language: LanguageOption; embedResourcesType: { imports: string; ResourcesType: string; diff --git a/src/codegen.ts b/src/genTypeFlow/codegen.ts similarity index 96% rename from src/codegen.ts rename to src/genTypeFlow/codegen.ts index 7102225..284a1eb 100644 --- a/src/codegen.ts +++ b/src/genTypeFlow/codegen.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; import prettier from 'prettier'; -import { GlobalConfig, getResourcePaths } from './config'; -import { getLoaderType, getLoadersTypeMap, getResourceTypings } from './genTypeFlow'; -import getLoaderImplementation from './implementation'; +import { GlobalConfig, getResourcePaths } from '../config'; +import { getLoaderType, getLoadersTypeMap, getResourceTypings } from './genType'; +import getLoaderImplementation from '../genTypeFlow/implementation'; function getLoaders(config: object, paths: Array>, current: Array) { if (_.isEqual(paths, [[]])) { diff --git a/src/genTypeFlow.ts b/src/genTypeFlow/genType.ts similarity index 98% rename from src/genTypeFlow.ts rename to src/genTypeFlow/genType.ts index 7e865a0..c8ab5b9 100644 --- a/src/genTypeFlow.ts +++ b/src/genTypeFlow/genType.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; -import assert from './assert'; -import { GlobalConfig, ResourceConfig } from './config'; +import assert from '../assert'; +import { GlobalConfig, ResourceConfig, LanguageOption } from '../config'; function errorPrefix(resourcePath: ReadonlyArray): string { return `[dataloader-codegen :: ${resourcePath.join('.')}]`; diff --git a/src/implementation.ts b/src/genTypeFlow/implementation.ts similarity index 87% rename from src/implementation.ts rename to src/genTypeFlow/implementation.ts index 43287ef..0ad5eb7 100644 --- a/src/implementation.ts +++ b/src/genTypeFlow/implementation.ts @@ -1,7 +1,7 @@ -import { ResourceConfig, BatchResourceConfig, NonBatchResourceConfig } from './config'; -import assert from './assert'; -import { getLoaderTypeKey, getLoaderTypeVal } from './genTypeFlow'; -import { errorPrefix } from './runtimeHelpers'; +import { ResourceConfig, BatchResourceConfig, NonBatchResourceConfig } from '../config'; +import assert from '../assert'; +import { getLoaderTypeKey, getLoaderTypeVal } from './genType'; +import { errorPrefix } from '../runtimeHelpers'; function getLoaderComment(resourceConfig: ResourceConfig, resourcePath: ReadonlyArray): string { const configComment = JSON.stringify(resourceConfig, null, 2) @@ -61,8 +61,8 @@ function callResource(resourceConfig: ResourceConfig, resourcePath: ReadonlyArra if (!(_response instanceof Error)) { _response = new Error([ \`${errorPrefix( - resourcePath, - )} Caught an error, but errorHandler did not return an Error object.\`, + resourcePath, + )} Caught an error, but errorHandler did not return an Error object.\`, \`Instead, got \${typeof _response}: \${util.inspect(_response)}\`, ].join(' ')); } @@ -97,14 +97,14 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea const requests = requestIDs.map(id => keys[id]); ${(() => { - const { batchKey, newKey, commaSeparatedBatchKey } = resourceConfig; + const { batchKey, newKey, commaSeparatedBatchKey } = resourceConfig; - let batchKeyParam = `['${batchKey}']: requests.map(k => k['${newKey}'])`; - if (commaSeparatedBatchKey === true) { - batchKeyParam = `${batchKeyParam}.join(',')`; - } + let batchKeyParam = `['${batchKey}']: requests.map(k => k['${newKey}'])`; + if (commaSeparatedBatchKey === true) { + batchKeyParam = `${batchKeyParam}.join(',')`; + } - return ` + return ` // For now, we assume that the dataloader key should be the first argument to the resource // @see https://github.com/Yelp/dataloader-codegen/issues/56 const resourceArgs = [{ @@ -112,14 +112,14 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea ${batchKeyParam}, }]; `; - })()} + })()} let response = await ${callResource(resourceConfig, resourcePath)}(resourceArgs); if (!(response instanceof Error)) { ${(() => { - if (typeof resourceConfig.nestedPath === 'string') { - return ` + if (typeof resourceConfig.nestedPath === 'string') { + return ` /** * Un-nest the actual data from the resource return value. * @@ -155,16 +155,16 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea ), ); `; - } else { - return ''; - } - })()} + } else { + return ''; + } + })()} } if (!(response instanceof Error)) { ${(() => { - if (resourceConfig.isResponseDictionary === true) { - return ` + if (resourceConfig.isResponseDictionary === true) { + return ` if (typeof response !== 'object') { response = new Error( [ @@ -174,8 +174,8 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea ); } `; - } else { - return ` + } else { + return ` if (!Array.isArray(response)) { response = new Error( [ @@ -185,23 +185,23 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea ); } `; - } - })()} + } + })()} } ${(() => { - const { reorderResultsByKey, isResponseDictionary, propertyBatchKey } = resourceConfig; - if ( - !isResponseDictionary && - reorderResultsByKey == null && - /** - * When there's propertyBatchKey and propertyNewKey, the resource might - * contain less number of items that we requested. It's valid, so we - * should skip the check. - */ - !(typeof propertyBatchKey === 'string') - ) { - return ` + const { reorderResultsByKey, isResponseDictionary, propertyBatchKey } = resourceConfig; + if ( + !isResponseDictionary && + reorderResultsByKey == null && + /** + * When there's propertyBatchKey and propertyNewKey, the resource might + * contain less number of items that we requested. It's valid, so we + * should skip the check. + */ + !(typeof propertyBatchKey === 'string') + ) { + return ` if (!(response instanceof Error)) { /** * Check to see the resource contains the same number @@ -216,8 +216,8 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea */ response = new BatchItemNotFoundError([ \`${errorPrefix( - resourcePath, - )} Resource returned \${response.length} items, but we requested \${requests.length} items.\`, + resourcePath, + )} 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(' ')); @@ -228,15 +228,15 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea } } `; - } else { - return ''; - } - })()} + } else { + return ''; + } + })()} ${(() => { - const { newKey, isResponseDictionary } = resourceConfig; - if (isResponseDictionary === true) { - return ` + const { newKey, isResponseDictionary } = resourceConfig; + if (isResponseDictionary === true) { + return ` if (!(response instanceof Error)) { response = resultsDictToList( response, @@ -245,10 +245,10 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea ); } `; - } else { - return ''; - } - })()} + } else { + return ''; + } + })()} /** * If the resource returns an Error, we'll want to copy and @@ -273,11 +273,10 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea * (If we didn't specify that this resource needs * sorting, then this will be "null" and won't be used.) */ - const reorderResultsByValue = ${ - typeof resourceConfig.reorderResultsByKey === 'string' - ? `keys[requestId]['${resourceConfig.newKey}']` - : 'null' - } + const reorderResultsByValue = ${typeof resourceConfig.reorderResultsByKey === 'string' + ? `keys[requestId]['${resourceConfig.newKey}']` + : 'null' + } // Tell flow that "response" is actually an error object. // (This is so we can pass it as 'cause' to CaughtResourceError) @@ -285,8 +284,8 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea return new CaughtResourceError( \`${errorPrefix( - resourcePath, - )} Caught error during call to resource. Error: \${response.stack}\`, + resourcePath, + )} Caught error during call to resource. Error: \${response.stack}\`, response, reorderResultsByValue ); @@ -294,10 +293,10 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea } ${(() => { - const { reorderResultsByKey, newKey } = resourceConfig; + const { reorderResultsByKey, newKey } = resourceConfig; - if (typeof reorderResultsByKey === 'string') { - return ` + if (typeof reorderResultsByKey === 'string') { + return ` response = sortByKeys({ items: response, keys: requests.map(k => k['${newKey}']), @@ -305,10 +304,10 @@ function batchLoaderLogic(resourceConfig: BatchResourceConfig, resourcePath: Rea resourcePath: ${JSON.stringify(resourcePath)}, }) `; - } else { - return ''; - } - })()} + } else { + return ''; + } + })()} `; } @@ -415,7 +414,7 @@ function getBatchLoader(resourceConfig: BatchResourceConfig, resourcePath: Reado * TODO: Figure out why directly passing `cacheKeyOptions` causes * flow errors :( */ '' - } + } ...cacheKeyOptions } )`; @@ -513,7 +512,7 @@ function getPropertyBatchLoader(resourceConfig: BatchResourceConfig, resourcePat * TODO: Figure out why directly passing `cacheKeyOptions` causes * flow errors :( */ '' - } + } ...cacheKeyOptions } )`; diff --git a/src/index.ts b/src/index.ts index 2f46bca..27cac6f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import assert from 'assert'; import fs from 'fs'; import path from 'path'; import yargs from 'yargs'; -import codegen from './codegen'; +import codegenFlow from './genTypeFlow/codegen'; import { getConfig } from './config'; interface CLIArgs { @@ -14,7 +14,7 @@ interface CLIArgs { function writeLoaders(args: CLIArgs) { const config = getConfig(args.config); - const output = codegen(config); + const output = codegenFlow(config); assert(typeof args.config === 'string', 'expected args.config to be set!'); assert(typeof args.output === 'string', 'expected args.output to be set!'); diff --git a/tsconfig.json b/tsconfig.json index 954d26a..a01c14f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./lib" /* Redirect output structure to the directory. */, - "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + "rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ diff --git a/yarn.lock b/yarn.lock index 9d0d22f..e0568c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1127,6 +1127,14 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.144.tgz#12e57fc99064bce45e5ab3c8bc4783feb75eab8e" integrity sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg== +"@types/node-fetch@^2.6.9": + version "2.6.9" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.9.tgz#15f529d247f1ede1824f7e7acdaa192d5f28071e" + integrity sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node@*", "@types/node@^12.7.12": version "12.7.12" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.12.tgz#7c6c571cc2f3f3ac4a59a5f2bd48f5bdbc8653cc" @@ -1688,7 +1696,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2201,6 +2209,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"