ghunti-devour-client
Version:
A lightweight, framework agnostic, flexible JSON API client
364 lines (309 loc) • 9.95 kB
JavaScript
const axios = require('axios')
const pluralize = require('pluralize')
const _ = require('lodash')
const Promise = require('es6-promise').Promise
const deserialize = require('./middleware/json-api/_deserialize')
const serialize = require('./middleware/json-api/_serialize')
const Minilog = require('minilog')
/*
* == JsonApiMiddleware
*
* Here we construct the middleware stack that will handle building and making
* requests, as well as serializing and deserializing our payloads. Users can
* easily construct their own middleware layers that adhere to different
* standards.
*
*/
const jsonApiHttpBasicAuthMiddleware = require('./middleware/json-api/req-http-basic-auth')
const jsonApiPostMiddleware = require('./middleware/json-api/req-post')
const jsonApiPatchMiddleware = require('./middleware/json-api/req-patch')
const jsonApiDeleteMiddleware = require('./middleware/json-api/req-delete')
const jsonApiGetMiddleware = require('./middleware/json-api/req-get')
const jsonApiHeadersMiddleware = require('./middleware/json-api/req-headers')
const railsParamsSerializer = require('./middleware/json-api/rails-params-serializer')
const sendRequestMiddleware = require('./middleware/request')
const deserializeResponseMiddleware = require('./middleware/json-api/res-deserialize')
const processErrors = require('./middleware/json-api/res-errors')
let jsonApiMiddleware = [
jsonApiHttpBasicAuthMiddleware,
jsonApiPostMiddleware,
jsonApiPatchMiddleware,
jsonApiDeleteMiddleware,
jsonApiGetMiddleware,
jsonApiHeadersMiddleware,
railsParamsSerializer,
sendRequestMiddleware,
processErrors,
deserializeResponseMiddleware
]
class JsonApi {
constructor (options = {}) {
if (!(arguments.length === 2 && _.isString(arguments[0]) && _.isArray(arguments[1])) && !(arguments.length === 1 && (_.isPlainObject(arguments[0]) || _.isString(arguments[0])))) {
throw new Error('Invalid argument, initialize Devour with an object.')
}
let defaults = {
middleware: jsonApiMiddleware,
logger: true,
resetBuilderOnCall: true,
auth: {}
}
let deprecatedConstructos = (args) => {
return (args.length === 2 || (args.length === 1 && _.isString(args[0])))
}
if (deprecatedConstructos(arguments)) {
defaults.apiUrl = arguments[0]
if (arguments.length === 2) {
defaults.middleware = arguments[1]
}
}
options = _.assign(defaults, options)
let middleware = options.middleware
this._originalMiddleware = middleware.slice(0)
this.middleware = middleware.slice(0)
this.headers = {}
this.axios = axios
this.auth = options.auth
this.apiUrl = options.apiUrl
this.models = {}
this.deserialize = deserialize
this.serialize = serialize
this.builderStack = []
this.resetBuilderOnCall = !!options.resetBuilderOnCall
this.logger = Minilog('devour')
options.logger ? Minilog.enable() : Minilog.disable()
if (deprecatedConstructos(arguments)) {
this.logger.warn('Constructor (apiUrl, middleware) has been deprecated, initialize Devour with an object.')
}
}
enableLogging (enabled = true) {
enabled ? Minilog.enable() : Minilog.disable()
}
one (model, id) {
this.builderStack.push({model: model, id: id, path: this.resourcePathFor(model, id)})
return this
}
all (model) {
this.builderStack.push({model: model, path: this.collectionPathFor(model)})
return this
}
resetBuilder () {
this.builderStack = []
}
buildPath () {
return _.map(this.builderStack, 'path').join('/')
}
buildUrl () {
return `${this.apiUrl}/${this.buildPath()}`
}
get (params = {}) {
let req = {
method: 'GET',
url: this.urlFor(),
data: {},
params
}
if (this.resetBuilderOnCall) {
this.resetBuilder()
}
return this.runMiddleware(req)
}
post (payload, params = {}) {
let lastRequest = _.chain(this.builderStack).last()
let req = {
method: 'POST',
url: this.urlFor(),
model: lastRequest.get('model').value(),
data: payload,
params
}
if (this.resetBuilderOnCall) {
this.resetBuilder()
}
return this.runMiddleware(req)
}
patch (payload, params = {}) {
let lastRequest = _.chain(this.builderStack).last()
let req = {
method: 'PATCH',
url: this.urlFor(),
model: lastRequest.get('model').value(),
data: payload,
params
}
if (this.resetBuilderOnCall) {
this.resetBuilder()
}
return this.runMiddleware(req)
}
destroy () {
if (arguments.length === 2) {
let req = {
method: 'DELETE',
url: this.urlFor({model: arguments[0], id: arguments[1]}),
model: arguments[0],
data: {}
}
return this.runMiddleware(req)
} else {
let lastRequest = _.chain(this.builderStack).last()
let req = {
method: 'DELETE',
url: this.urlFor(),
model: lastRequest.get('model').value(),
data: arguments[0] || {}
}
if (this.resetBuilderOnCall) {
this.resetBuilder()
}
return this.runMiddleware(req)
}
}
insertMiddlewareBefore (middlewareName, newMiddleware) {
this.insertMiddleware(middlewareName, 'before', newMiddleware)
}
insertMiddlewareAfter (middlewareName, newMiddleware) {
this.insertMiddleware(middlewareName, 'after', newMiddleware)
}
insertMiddleware (middlewareName, direction, newMiddleware) {
let middleware = this.middleware.filter(middleware => (middleware.name === middlewareName))
if (middleware.length > 0) {
let index = this.middleware.indexOf(middleware[0])
if (direction === 'after') {
index = index + 1
}
this.middleware.splice(index, 0, newMiddleware)
}
}
replaceMiddleware (middlewareName, newMiddleware) {
let index = _.findIndex(this.middleware, ['name', middlewareName])
this.middleware[index] = newMiddleware
}
define (modelName, attributes, options = {}) {
this.models[modelName] = {
attributes: attributes,
options: options
}
}
resetMiddleware () {
this.middleware = this._originalMiddleware.slice(0)
}
applyRequestMiddleware (promise) {
let requestMiddlewares = this.middleware.filter(middleware => middleware.req)
requestMiddlewares.forEach((middleware) => {
promise = promise.then(middleware.req)
})
return promise
}
applyResponseMiddleware (promise) {
let responseMiddleware = this.middleware.filter(middleware => middleware.res)
responseMiddleware.forEach((middleware) => {
promise = promise.then(middleware.res)
})
return promise
}
applyErrorMiddleware (promise) {
let errorsMiddleware = this.middleware.filter(middleware => middleware.error)
errorsMiddleware.forEach((middleware) => {
promise = promise.then(middleware.error)
})
return promise
}
runMiddleware (req) {
let payload = {req: req, jsonApi: this}
let requestPromise = Promise.resolve(payload)
requestPromise = this.applyRequestMiddleware(requestPromise)
return requestPromise
.then((res) => {
payload.res = res
let responsePromise = Promise.resolve(payload)
return this.applyResponseMiddleware(responsePromise)
})
.catch((err) => {
this.logger.error(err)
let errorPromise = Promise.resolve(err)
return this.applyErrorMiddleware(errorPromise).then(err => {
return Promise.reject(err)
})
})
}
request (url, method = 'GET', params = {}, data = {}) {
let req = { url, method, params, data }
return this.runMiddleware(req)
}
find (modelName, id, params = {}) {
let req = {
method: 'GET',
url: this.urlFor({model: modelName, id: id}),
model: modelName,
data: {},
params: params
}
return this.runMiddleware(req)
}
findAll (modelName, params = {}) {
let req = {
method: 'GET',
url: this.urlFor({model: modelName}),
model: modelName,
params: params,
data: {}
}
return this.runMiddleware(req)
}
create (modelName, payload) {
let req = {
method: 'POST',
url: this.urlFor({model: modelName}),
model: modelName,
data: payload
}
return this.runMiddleware(req)
}
update (modelName, payload) {
let req = {
method: 'PATCH',
url: this.urlFor({model: modelName, id: payload.id}),
model: modelName,
data: payload
}
return this.runMiddleware(req)
}
modelFor (modelName) {
return this.models[modelName]
}
collectionPathFor (modelName) {
let collectionPath = _.get(this.models[modelName], 'options.collectionPath') || pluralize(modelName)
return `${collectionPath}`
}
resourcePathFor (modelName, id) {
let collectionPath = this.collectionPathFor(modelName)
return `${collectionPath}/${encodeURIComponent(id)}`
}
collectionUrlFor (modelName) {
let collectionPath = this.collectionPathFor(modelName)
return `${this.apiUrl}/${collectionPath}`
}
resourceUrlFor (modelName, id) {
let resourcePath = this.resourcePathFor(modelName, id)
return `${this.apiUrl}/${resourcePath}`
}
urlFor (options = {}) {
if (!_.isUndefined(options.model) && !_.isUndefined(options.id)) {
return this.resourceUrlFor(options.model, options.id)
} else if (!_.isUndefined(options.model)) {
return this.collectionUrlFor(options.model)
} else {
return this.buildUrl()
}
}
pathFor (options = {}) {
if (!_.isUndefined(options.model) && !_.isUndefined(options.id)) {
return this.resourcePathFor(options.model, options.id)
} else if (!_.isUndefined(options.model)) {
return this.collectionPathFor(options.model)
} else {
return this.buildPath()
}
}
}
module.exports = JsonApi