mir-client
Version:
A Mir API Client
264 lines (240 loc) • 8.7 kB
JavaScript
import * as R from 'ramda'
/**
* @function CreateGetterFactory - Creates GetterFactory for a certain Mir API.
* @param {Object} axios - Initialized Axios instance.
* @param {string} resource - Resource name.
* @returns {GetterFactory} - A factory function that accepts a Mir API resource name
*/
function CreateGetterFactory(axios, resource) {
/**
* @typedef GetterFactory - A Query Builder object
* @type {function}
* @returns {QueryBuilder} - Object that acts as a query builder for Mir requests
*/
const GetterFactory = () => {
/**
* A Query Builder object
* @typedef QueryBuilder
* @type {Object}
* @property {Object} params - Stores parameters for the request
* @property {Object} headers - Stores headers for the request
* @property {Object} meta - Stores _meta from the preceding request, will be empty if no initial request has been made
* @property {function} projection - Updates params object with projection configuration
* @property {function} sort - Updates params object with sorting configuration
* @property {function} filter - Updates params object with filter configuration
* @property {function} page - Updates params object with page configuration
* @property {function} limit - Updates params object with maximum result count
* @property {function} next - Send a request for the next page, requires an initial request to have been made
* @property {function} previous - Send a request for the previous page, requires an inital request to have been made
* @property {function} first - Send a request for the first page, requires an initial to have been made
* @property {function} last - Send a request for the last page, requires an initial request to have been made
* @property {function} send - Send a request with the current stored headers and params configurations, resets meta object
*/
const QueryBuilder = {
params: {},
headers: {},
meta: {},
/**
* @function projection
* @param {string} attribute - Name of the resource attribute to apply projection
* @param {boolean} bool - Indicates whether the attribute should appear in the results
* @this QueryBuilder
*/
projection(attribute, bool) {
if(typeof bool != 'boolean') {
var err = new Error()
err.message = 'Bool param only accepts values of true or false.'
err.code = 400
throw err
}
const value = bool ? 1 : 0
this.params.projection = R.merge(this.params, R.objOf(attribute, value))
return this
},
/**
* @function sort
* @param {string} attribute - Name of the resource attribute to sort by
* @param {string} value - Either '+' or '-' for ascending or descending
* @this QueryBuilder
*/
sort(attribute, value) {
if(!((value == '+') || (value == '-'))) {
var err = new Error()
err.message = 'Value param only accepts values of \'+\' or \'-\'.'
err.code = 400
throw err
}
this.params.sort = this.params.sort
? `${this.params.sort},${value}${attribute}`
: `${value}${attribute}`
return this
},
/**
* @function filter
* @param {Object} filter_obj - A mongodb query object.
* @see {@link http://python-eve.org/features.html#filtering|Eve Filtering}
* @see {@link https://docs.mongodb.com/v3.2/reference/operator/query/|MongoDB Queries}
* @this QueryBuilder
*/
filter(filter_obj) {
// Query object examples:
//
// _id where greater than 'a'
// ----------------------------------
// {
// _id: {
// $gt: 'a'
// }
// }
//
// where quantity less than 20 or price is 10
// ----------------------------------
// {
// $or: [
// {
// quantity: { $lt: 20 }
// },
// {
// price: 10
// }
// ]
// }
// ----------------------------------
if(typeof filter_obj != 'object') {
var err = new Error()
err.message = 'Parameter filter_obj must be a JSON object.'
err.code = 400
throw err
}
this.params.where = filter_obj
return this
},
/**
*
* @function page
* @param {number} value - A page number
* @this QueryBuilder
*/
page(value) {
this.params.page = value
return this
},
/**
* Set the current limit value
* @function limit
* @param {number} value - Maximum number of results to return in the request
* @this QueryBuilder
*/
limit(value) {
this.params.max_results = value
return this
},
/**
* Send a GET request for the next page of results
* @function next
* @this QueryBuilder
* @returns {Promise} - Axios promise
*/
next() {
if(!this.meta.page) {
var err = new Error()
err.message = 'Next requires an initial request to have been made.'
err.code = 400
throw err
}
// Ensure page is not the last
var lastPage = this.meta.total != 0 ? this.meta.max_results * this.meta.page >= this.meta.total : true
if(lastPage) {
var err = new Error()
err.message = 'Next cannot be called on last page of results.'
err.code = 400
throw err
}
// Increment this.params.page and this.send() again, returning promise
this.params.page += 1
return this.send()
},
/**
* @function previous - Send GET for the previous page of results
* @this QueryBuilder
* @returns {Promise} - Axios promise
*/
previous() {
if(!this.meta.page) {
var err = new Error()
err.message = 'Previous requires an initial request to have been made.'
err.code = 400
throw err
}
// Ensure page is not the first
if(this.params.page == 1) {
var err = new Error()
err.message = 'Current page of 1 has no previous page.'
err.code = 400
throw err
}
// Decrement this.params.page and this.send() again, returning promise
this.params.page -= 1
return this.send()
},
/**
* @function first - Send GET for the first page of results
* @this QueryBuilder
* @returns {Promise} - Axios promise
*/
first() {
if(!this.meta.page) {
var err = new Error()
err.message = 'First requires an initial request to have been made.'
err.code = 400
throw err
}
// Reset this.params.page to 1 and this.send() again, returning promise
this.params.page = 1
return this.send()
},
/**
* @function last - Send GET for the last page of results
* @this QueryBuilder
* @returns {Promise} - Axios promise
*/
last() {
if(!this.meta.page) {
var err = new Error()
err.message = 'Last requires an initial request to have been made.'
err.code = 400
throw err
}
// Ensure page is not the last
var lastPage = this.meta.total != 0 ? this.meta.max_results * this.meta.page >= this.meta.total : true
// Increment this.params.page and this.send() again, returning promise
var totalPages = this.meta.total == 0 ? 1 : Math.ceil(this.meta.total / this.meta.max_results)
this.params.page = totalPages
return this.send()
},
/**
* @function send - Send GET with currently defined params and headers
* @param {function} hook - TODO: Not implemented, will just contain a function to alter returned results
* @this QueryBuilder
* @returns {Promise} - Axios promise
*/
send(hook) {
const params = this.params
const headers = this.headers
return axios.get(`/${resource}`, {
params,
headers,
}).then((result) => {
this.meta = result.data._meta
if(!this.params.page) {
this.params.page = this.meta.page // set this.params.page with default page size if not specified in request
}
return result
})
}
}
return QueryBuilder
}
return GetterFactory
}
export default CreateGetterFactory