UNPKG

reference-fetcher

Version:

Simple and easy entity references fetcher

252 lines (209 loc) 9.57 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _lodash = require('lodash'); var _warning = require('./util/warning'); var _warning2 = _interopRequireDefault(_warning); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // import 'babel-polyfill' var rootFetchCalled = {}; var refsRetrieved = {}; var idsFailed = []; var fetchSubRef = function fetchSubRef() {}; var fetchEnhanced = function fetchEnhanced() {}; var getEntity = function getEntity(entity, id) { if (refsRetrieved[entity] && refsRetrieved[entity][id]) return Object.assign({}, refsRetrieved[entity][id]); return null; }; var registerNewEntity = function registerNewEntity(entity, id) { var value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (!getEntity(entity, id)) refsRetrieved[entity] = Object.assign({}, refsRetrieved[entity], _defineProperty({}, id, value)); }; /* * Function to retrieve a list of uniques ids from the parent relations */ var retrieveUniquesIds = function retrieveUniquesIds(parent, relation) { var optional = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; return parent.reduce(function (acc, object) { var relationId = object[relation]; if (!relationId && !optional) { (0, _warning2.default)('the relation ' + relation + ' could not be found in object ' + object.id, true); } else if (acc.indexOf(relationId) === -1 && relationId != null) { // Keep the list unique acc.push(relationId); } return acc; }, []); }; /* * Function to retrieve the ids to fetch and the objects already fetched */ var crossIdsWithCache = function crossIdsWithCache(entity, ids, noCache) { // If no cache, do not attempt to check the cache if (noCache) return { idsToFetch: ids, alreadyFetched: [] // Check if already present in cache or in failed ids and create the resulting object };return ids.reduce(function (acc, id) { var inCache = getEntity(entity, id); if (inCache) acc.alreadyFetched.push(inCache);else if (idsFailed.indexOf(id) === -1) acc.idsToFetch.push(id); return acc; }, { idsToFetch: [], alreadyFetched: [] }); }; /* * fetchSubRefs simply loops on the subRefs array and calls fetchSubRef */ var fetchSubRefs = function fetchSubRefs(subRefs, parentObject) { subRefs.forEach(function (ref) { fetchSubRef(ref, parentObject); }); }; var fetchSides = function fetchSides(sides, result) { // Call side if present with the result if (sides && (0, _lodash.isArray)(sides)) { sides.forEach(function (side) { if (typeof side.fetch !== 'function') { (0, _warning2.default)('the side fetch for entity ' + side.entity + ' is not a function'); } else { // Uniq ids of parent to fetch var uniqIds = result.map(function (res) { return res.id; }); // Filter the list of ids with what ids need to be fetch // and what objects has already been fetched var _crossIdsWithCache = crossIdsWithCache(side.entity, uniqIds), idsToFetch = _crossIdsWithCache.idsToFetch; if (idsToFetch.length > 0) { side.fetch(idsToFetch).then(function (values) { // If the fetch did not returned values, warn the client if (!values) (0, _warning2.default)('the side fetch for entity ' + side.entity + ' returned no result'); // Register the new objects in our cache for future use if (values && (0, _lodash.isArray)(values)) values.forEach(function (value) { return registerNewEntity(side.entity, value.id, value); }); // Search for and register ids that wasn't retrieved idsToFetch.forEach(function (id) { if ((0, _lodash.findIndex)(values, { id: id }) === -1 && idsFailed.indexOf(id) === -1) idsFailed.push(id); }); }, function (reason) { (0, _warning2.default)('the side fetch for entity ' + side.entity + ' returned an error: ' + reason); }); } } }); } }; /* * Wrap the fetch function with checks and * call fetchSides if needed */ fetchEnhanced = function fetchEnhanced(fetch, entity, sides, alreadyFetched) { return new Promise(function (resolve, reject) { var alreadyFetchedCopy = alreadyFetched ? [].concat(_toConsumableArray(alreadyFetched)) : []; fetch().then(function (result) { // Verify if the entity attribute actually gave something to work on if (!result) { (0, _warning2.default)('the fetch for entity ' + entity + ' returned no result'); resolve(null); } var funcSignature = fetch.toString(); // Register our fetch result in order to avoid unecessary recall later one rootFetchCalled[funcSignature] = result; // Fetch sides if present fetchSides(sides, [].concat(_toConsumableArray(result), _toConsumableArray(alreadyFetchedCopy))); resolve(result); }).catch(function (reason) { (0, _warning2.default)('the fetch for entity ' + entity + ' returned an error: ' + reason); reject(reason); }); }); }; /* * Take the ref and the parentObject to fetch on the referenced entity. * Will also fetch subRefs if presents */ fetchSubRef = function fetchSubRef(ref, parentObject) { // Deconstruct the refs structure to retrieve the fetch promise, the entity to target and the sub structure if present var fetch = ref.fetch, entity = ref.entity, relationName = ref.relationName, subRefs = ref.refs, batch = ref.batch, noCache = ref.noCache, optional = ref.optional, sides = ref.sides; // The name of the relation in the parent object var relation = relationName || entity; // If the returned object is not an array, // transform it for generic usage if (!(0, _lodash.isArray)(parentObject) && (0, _lodash.isObject)(parentObject)) { parentObject = [parentObject]; } // Retrieve the list of ids to fetch var uniqIds = retrieveUniquesIds(parentObject, relation, optional); // Filter the list of ids with what ids need to be fetch // and what objects has already been fetched var _crossIdsWithCache2 = crossIdsWithCache(entity, uniqIds, noCache), idsToFetch = _crossIdsWithCache2.idsToFetch, alreadyFetched = _crossIdsWithCache2.alreadyFetched; if (idsToFetch.length === 0) { // If we have nothing to fetch, just continue with underneath references if (subRefs && subRefs.length > 0) fetchSubRefs(subRefs, alreadyFetched); // Fetch sides if present fetchSides(sides, alreadyFetched); } else { // Else call the fetch function with the batch of ids or one by one var fetchEnhancedCall = function fetchEnhancedCall() { // If we want a batch, only one request is thrown if (batch) return fetch(idsToFetch); // Else we need to wait for each request response to go further else return Promise.all(idsToFetch.map(fetch)); }; Promise.resolve(fetchEnhanced(fetchEnhancedCall, entity, sides, alreadyFetched)).then(function (values) { // If the fetch did not returned values, warn the client if (!values) (0, _warning2.default)('the fetch for entity ' + entity + ' returned no values'); // Register the new objects in our cache for future use if (values && (0, _lodash.isArray)(values)) values.forEach(function (value) { return registerNewEntity(entity, value.id, value); }); // Search for and register ids that wasn't retrieved idsToFetch.forEach(function (id) { if ((0, _lodash.findIndex)(values, { id: id }) === -1 && idsFailed.indexOf(id) === -1) idsFailed.push(id); }); // Continue with underneath references with our fetched and cached values if (values && subRefs) fetchSubRefs(subRefs, [].concat(_toConsumableArray(values), _toConsumableArray(alreadyFetched))); }); } }; var fetchRefs = function fetchRefs(structure) { var fetch = structure.fetch, entity = structure.entity, subRefs = structure.refs, _structure$rootNoCach = structure.rootNoCache, rootNoCache = _structure$rootNoCach === undefined ? false : _structure$rootNoCach, sides = structure.sides; if (typeof fetch !== 'function') { (0, _warning2.default)('the fetch of entity ' + entity + ' is not a function'); return; } // One way to identify surely, without assumption on the name, a function var funcSignature = fetch.toString(); // funcResult is present if function already called var funcResult = rootFetchCalled[funcSignature]; // If fetch already called if (funcResult && !rootNoCache) { if (subRefs && subRefs.length > 0) fetchSubRefs(subRefs, funcResult); // Fetch sides if present fetchSides(sides, funcResult); return; } if (subRefs && subRefs.length > 0) { // Get the result entity fetchEnhanced(fetch, entity, sides).then(function (result) { return fetchSubRefs(subRefs, result); }); } else { fetchEnhanced(fetch, entity, sides); } }; exports.default = fetchRefs;