UNPKG

feathers-graph-populate

Version:

Add lightning fast, GraphQL-like populates to your FeathersJS API.

140 lines (114 loc) 3.98 kB
import _get from 'lodash/get.js' import _set from 'lodash/set.js' import _has from 'lodash/has.js' import { assertIncludes, makeCumulatedRequest, makeRequestPerItem, mapDataWithId, setItems, shouldCatchOnError, } from '../utils/shallow-populate.utils' import type { HookContext } from '@feathersjs/feathers' import type { AnyData, CumulatedRequestResult, PopulateObject, ShallowPopulateOptions, } from '../types' const defaults: ShallowPopulateOptions = { include: undefined, catchOnError: false, } export function shallowPopulate( options: Partial<ShallowPopulateOptions> & Pick<ShallowPopulateOptions, 'include'>, ): (context: HookContext) => Promise<HookContext> { options = Object.assign({}, defaults, options) // Make an array of includes const includes: PopulateObject[] = [].concat(options.include || []) if (!includes.length) { throw new Error( 'shallowPopulate hook: You must provide one or more relationships in the `include` option.', ) } assertIncludes(includes) const cumulatedIncludes = includes.filter((include) => !include.requestPerItem) const includesByKeyHere = cumulatedIncludes.reduce((includes, include) => { if (_has(include, 'keyHere') && !includes[include.keyHere]) { includes[include.keyHere] = include } return includes }, {}) const keysHere = Object.keys(includesByKeyHere) const includesPerItem = includes.filter((include) => include.requestPerItem) return async function shallowPopulate(context: HookContext): Promise<HookContext> { const { app, type } = context let data: AnyData[] = type === 'before' ? context.data : context.method === 'find' ? context.result.data || context.result : context.result data = [].concat(data || []) if (!data.length) { return context } const dataMap: AnyData = data.reduce((byKeyHere: AnyData, current: AnyData) => { keysHere.forEach((key) => { byKeyHere[key] = byKeyHere[key] || {} const keyHere = _get(current, key) as string | string[] if (keyHere !== undefined) { if (Array.isArray(keyHere)) { if (!includesByKeyHere[key].asArray) { mapDataWithId(byKeyHere, key, keyHere[0], current) } else { keyHere.forEach((hereKey) => mapDataWithId(byKeyHere, key, hereKey, current)) } } else { mapDataWithId(byKeyHere, key, keyHere, current) } } }) return byKeyHere }, {}) const promisesCumulatedResults = cumulatedIncludes.map( async (include: PopulateObject): Promise<CumulatedRequestResult> => { let result: CumulatedRequestResult try { result = await makeCumulatedRequest(app, include, dataMap, context) } catch (err) { if (!shouldCatchOnError(options, include)) throw err return { include } } return result }, ) const cumulatedResults = await Promise.all(promisesCumulatedResults) cumulatedResults.forEach((result) => { if (!result) return const { include } = result if (!result.response) { data.forEach((item) => { _set(item, include.nameAs, include.asArray ? [] : {}) }) return } const { params, response } = result setItems(data, include, params, response) }) const promisesPerIncludeAndItem: Promise<unknown>[] = [] includesPerItem.forEach((include) => { const promisesPerItem = data.map(async (item) => { try { await makeRequestPerItem(item, app, include, context) } catch (err) { if (!shouldCatchOnError(options, include)) throw err _set(item, include.nameAs, include.asArray ? [] : {}) } }) promisesPerIncludeAndItem.push(...promisesPerItem) }) await Promise.all(promisesPerIncludeAndItem) return context } }