reference-fetcher
Version:
Simple and easy entity references fetcher
252 lines (209 loc) • 9.57 kB
JavaScript
;
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;