UNPKG

@taraai/read-write

Version:

Synchronous NoSQL/Firestore for React

212 lines (195 loc) 6.37 kB
import { filter, isObject, map, get, forEach, set, has, some } from 'lodash' import { isString } from './index' /** * @private * @param {string|object} str - String or Object to standardize into populate object * @returns {object} Populate object */ export function getPopulateObj(str) { if (!isString(str)) { return str } const strArray = str.split(':') // TODO: Handle childParam return { child: strArray[0], root: strArray[1] } } /** * @private * @param {string|object} child - Value at child parameter * @returns {string} Type of child */ export function getChildType(child) { if (isString(child)) { return 'string' } if (Array.isArray(child)) { return 'array' } if (isObject(child)) { return 'object' } return 'other' } /** * @private * @param {string|object} arr - String or Object to standardize into populate object * @returns {Array} List of populate objects */ export function getPopulateObjs(arr) { if (!Array.isArray(arr)) { return arr } return arr.map((o) => (isObject(o) ? o : getPopulateObj(o))) } /** * @private * @param {Array} queryParams - Query parameters from which to get populates * @returns {Array} Array of populate settings */ export function getPopulates(queryParams) { const populates = filter( queryParams, (param) => param.indexOf('populate') !== -1 || (isObject(param) && param.populates) ).map((p) => p.split('=')[1]) // No populates if (!populates.length) { return null } return populates.map(getPopulateObj) } /** * @private * @param {object} firebase - Internal firebase object * @param {object} populate - Object containing root to be populate * @param {object} populate.root - Firebase root path from which to load populate item * @param {string} id - String id * @returns {Promise} Resolves with populate child */ export function getPopulateChild(firebase, populate, id) { const childPath = populate.childParam ? `/${populate.childParam}` : '' const path = `${populate.root}/${id}${childPath}` return firebase .database() .ref() .child(path) .once('value') .then((snap) => // Return id if population value does not exist snap.val() ) } /** * @private * @param {object} firebase - Internal firebase object * @param {object} list - Object to have parameter populated * @param {object} p - Object containing populate information * @param {object} results - Object containing results of population from other populates * @returns {Promise} Resovles with populated list */ export function populateList(firebase, list, p, results) { // Handle root not being defined if (!results[p.root]) { set(results, p.root, {}) } return Promise.all( map(list, (id, childKey) => { // handle list of keys const populateKey = id === true || p.populateByKey ? childKey : id return getPopulateChild(firebase, p, populateKey).then((pc) => { if (pc) { // write child to result object under root name if it is found return set(results, `${p.root}.${populateKey}`, pc) } return results }) }) ) } /** * @private * @param {object} firebase - Internal firebase object * @param {string} dataKey - Object to have parameter populated * @param {object} originalData - Data before population * @param {Function|object} populatesIn - Populate configs or function returning configs * @returns {Promise} Promise which resolves after populate data is loaded */ export function promisesForPopulate( firebase, dataKey, originalData, populatesIn ) { // TODO: Handle selecting of parameter to populate with (i.e. displayName of users/user) const promisesArray = [] const results = {} // test if data is a single object, try generating populates and looking for the child const populatesForData = getPopulateObjs( typeof populatesIn === 'function' ? populatesIn(dataKey, originalData) : populatesIn ) const dataHasPopulateChilds = some(populatesForData, (populate) => has(originalData, populate.child) ) if (dataHasPopulateChilds) { // Data is a single object, resolve populates directly forEach(populatesForData, (p) => { if (isString(get(originalData, p.child))) { return promisesArray.push( getPopulateChild(firebase, p, get(originalData, p.child)).then( (v) => { // write child to result object under root name if it is found if (v) { set(results, `${p.root}.${get(originalData, p.child)}`, v) } } ) ) } // Single Parameter is list return promisesArray.push( populateList(firebase, get(originalData, p.child), p, results) ) }) } else { // Data is a list of objects, each value has parameters to be populated // { '1': {someobject}, '2': {someobject} } forEach(originalData, (d, key) => { // generate populates for this data item if a fn was passed const populatesForDataItem = getPopulateObj( typeof populatesIn === 'function' ? populatesIn(key, d) : populatesIn ) // resolve each populate for this data item forEach(populatesForDataItem, (p) => { // get value of parameter to be populated (key or list of keys) const idOrList = get(d, p.child) // Parameter/child of list item does not exist if (!idOrList) { return } // Parameter of each list item is single ID if (isString(idOrList)) { return promisesArray.push( getPopulateChild(firebase, p, idOrList).then((v) => { // write child to result object under root name if it is found if (v) { set(results, `${p.root}.${idOrList}`, v) } return results }) ) } // Parameter of each list item is a list of ids if (Array.isArray(idOrList) || isObject(idOrList)) { // Create single promise that includes a promise for each child return promisesArray.push( populateList(firebase, idOrList, p, results) ) } }) }) } // Return original data after population promises run return Promise.all(promisesArray).then(() => results) }