@axway/api-builder-plugin-dc-mbs
Version:
Mobile Backend Services connector
144 lines (136 loc) • 4.95 kB
JavaScript
const {
getTranslatedError,
translateWhere,
validateWhereOption
} = require('../utils');
function fetchAll(connector, Model, options, next) {
let count = 0;
// the `function handler` is to achieve recursion
connector.logger.trace('MBS fetching', { options, model: Model.name });
const pkName = Model.getPrimaryKeyName();
connector.db.customObjectsQuery(options, function handler(err, response) {
if (err) {
// Do not expose `err` via callback (information disclosure). Also,
// we do not have to handle the 'Failed: wrong class name' here as
// the class name already exists if we got this far.
return next(getTranslatedError(connector, err));
}
if (response.body.meta.code === 200) {
const items = response.body.response[Model.name];
if (!(items instanceof Array)) {
// this prevents an infinite loop if a 200 response is not
// what we think it is.
connector.logger.trace('Unexpected MBS response', response.body.response);
return next(new Error('Internal error'));
}
count += items.length;
if (items.length < 1000) {
// no more items
connector.logger.trace(`got ${items.length}, no more items`);
return next(null, count);
}
const lastItem = items.pop();
// Keep fetching. It is necessary to present a unique options
// object to the iterative query so that simple-mock can spy them.
const nextOpts = Object.assign({}, options);
// Next options are the user's query, and query $gt last PK id.
nextOpts.where = {
$and: [
options.where,
{
[pkName]: {
$gt: lastItem[pkName]
}
}
]
};
connector.logger.trace('MBS next fetch', { nextOpts });
return connector.db.customObjectsQuery(nextOpts, handler);
}
connector.logger.trace('MBS count was unsuccessful', response.body.meta);
return next(new Error('Count was unsuccessful'));
});
}
function count(Model, options, next) {
if (typeof options === 'function' && !next) {
next = options;
options = {};
}
this.logger.trace('MBS.count', { options, model: Model.name });
const hasWhere = options.where && Object.keys(options.where).length;
if (!hasWhere) {
this.db.customObjectsCount({ classname: Model.name }, (err, response) => {
if (err) {
if (err.body && err.body.meta
&& err.body.meta.message === 'Failed: wrong class name.') {
// The model has no data in MBS yet. MBS returns an error when
// trying to do a count. This is not an error.
return next(null, 0);
}
// Do not expose `err` via callback (information disclosure)
return next(getTranslatedError(this, err));
}
if (response.body.meta.code === 200) {
this.logger.trace('customObjectsCount count returned', response.body.meta);
return next(null, response.body.meta.count);
}
this.logger.trace('MBS count was unsuccessful', response.body.meta);
return next(new Error('Count was unsuccessful'));
});
} else {
// Things get complicated. MBS does not support `where` using count.
// The good news is, we can use query with `count: true`. The bad
// news is that this will only work up to 5000 records, after which,
// it returns "5000+". If that happens, we have to fall-back to
// querying *all* objects, and counting them individually.
// https://docs.appcelerator.com/arrowdb/latest/#!/guide/search_query
try {
validateWhereOption(options.where);
} catch (ex) {
// safe exception
this.logger.trace('MBS failed to validate where', ex);
return next(ex);
}
const pkName = Model.getPrimaryKeyName();
const opts = {
classname: Model.name,
count: true,
sel: { all: [ pkName ] },
limit: 1,
where: translateWhere(options.where)
};
this.db.customObjectsQuery(opts, (err, response) => {
if (err) {
if (err.body && err.body.meta
&& err.body.meta.message === 'Failed: wrong class name.') {
// The model has no data in MBS yet. MBS returns an error when
// trying to do a count. This is not an error.
this.logger.trace('MBS no collection found');
return next(null, 0);
}
// Do not expose `err` via callback (information disclosure)
return next(getTranslatedError(this, err));
}
if (response.body.meta.code === 200) {
if (response.body.meta.count !== '5000+') {
// otherwise, it's a number and less than 5000
this.logger.trace('MBS customObjectsQuery returned', response.body.meta);
return next(null, response.body.meta.count);
} else {
this.logger.trace('MBS collection has more than 5000 records');
// else, can-o-worms
const fetchOpts = Object.assign({}, opts);
delete fetchOpts.count;
fetchOpts.limit = 1000;
fetchOpts.order = pkName;
return fetchAll(this, Model, fetchOpts, next);
}
}
this.logger.trace('MBS count was unsuccessful', response.body.meta);
return next(new Error('Count was unsuccessful'));
});
}
}
module.exports = {
count
};