UNPKG

@axway/api-builder-plugin-dc-mbs

Version:

Mobile Backend Services connector

144 lines (136 loc) 4.95 kB
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 };