UNPKG

js-data-http

Version:

HTTP (XHR) adapter for js-data in the browser.

1,302 lines (1,212 loc) 39.3 kB
import { utils } from 'js-data' import axios from '../node_modules/axios/dist/axios' import { Adapter, noop, noop2 } from '../node_modules/js-data-adapter/src/index' let hasFetch = false try { hasFetch = window && window.fetch } catch (e) {} function isValidString (value) { return (value != null && value !== '') } function join (items, separator) { separator || (separator = '') return items.filter(isValidString).join(separator) } function makePath (...args) { let result = join(args, '/') return result.replace(/([^:/]|^)\/{2,}/g, '$1/') } function encode (val) { return encodeURIComponent(val) .replace(/%40/gi, '@') .replace(/%3A/gi, ':') .replace(/%24/g, '$') .replace(/%2C/gi, ',') .replace(/%20/g, '+') .replace(/%5B/gi, '[') .replace(/%5D/gi, ']') } function buildUrl (url, params) { if (!params) { return url } const parts = [] utils.forOwn(params, function (val, key) { if (val === null || typeof val === 'undefined') { return } if (!utils.isArray(val)) { val = [val] } val.forEach(function (v) { if (toString.call(v) === '[object Date]') { v = v.toISOString() } else if (utils.isObject(v)) { v = utils.toJson(v) } parts.push(`${encode(key)}=${encode(v)}`) }) }) if (parts.length > 0) { url += (url.indexOf('?') === -1 ? '?' : '&') + parts.join('&') } return url } const DEFAULTS = { /** * Set a base path in order to use absolute URLs instead of relative URLs. * * @example * const httpAdapter = new HttpAdapter({ * basePath: 'https://mydomain.com' * }); * * @name HttpAdapter#basePath * @type {string} * @since 3.0.0 */ basePath: '', /** * Ensure that the request url has a trailing forward slash. * * @name HttpAdapter#forceTrailingSlash * @type {boolean} * @default false * @since 3.0.0 */ forceTrailingSlash: false, hasFetch: hasFetch, /** * The HTTP function that actually makes the HTTP request. By default this is * `axios`. * * @name HttpAdapter#http * @type {function} * @since 3.0.0 * @see http://www.js-data.io/docs/js-data-http#using-a-custom-http-library */ http: axios, /** * Default configuration options to be mixed into the `config` argument passed * to {@link HttpAdapter#http}. * * @name HttpAdapter#httpConfig * @type {object} * @since 3.0.0 */ httpConfig: {}, /** * Add a suffix to the request url, e.g. ".json". * * @name HttpAdapter#suffix * @type {string} * @since 3.0.0 */ suffix: '', /** * Use `window.fetch` if available. * * @name HttpAdapter#useFetch * @type {boolean} * @default false * @since 3.0.0 * @see http://www.js-data.io/docs/js-data-http#using-windowfetch */ useFetch: false } /** * HttpAdapter class. * * @example * import { DataStore } from 'js-data'; * import { HttpAdapter } from 'js-data-http'; * * const httpAdapter = new HttpAdapter(); * const store = new DataStore(); * * store.registerAdapter('http', httpAdapter, { 'default': true }); * * store.defineMapper('school'); * store.defineMapper('student'); * * // GET /school/1 * store.find('school', 1).then((school) => { * console.log('school'); * }); * * @class HttpAdapter * @extends Adapter * @param {object} [opts] Configuration options. * @param {string} [opts.basePath=''] See {@link HttpAdapter#basePath}. * @param {boolean} [opts.debug=false] See {@link HttpAdapter#debug}. * @param {boolean} [opts.forceTrailingSlash=false] See {@link HttpAdapter#forceTrailingSlash}. * @param {object} [opts.http=axios] See {@link HttpAdapter#http}. * @param {object} [opts.httpConfig={}] See {@link HttpAdapter#httpConfig}. * @param {string} [opts.suffix=''] See {@link HttpAdapter#suffix}. * @param {boolean} [opts.useFetch=false] See {@link HttpAdapter#useFetch}. * @see http://www.js-data.io/docs/js-data-http */ export function HttpAdapter (opts) { utils.classCallCheck(this, HttpAdapter) opts || (opts = {}) // Fill in any missing options with the defaults utils.fillIn(opts, DEFAULTS) Adapter.call(this, opts) } /** * @name module:js-data-http.HttpAdapter * @see HttpAdapter */ Adapter.extend({ constructor: HttpAdapter, /** * @name HttpAdapter#afterDEL * @method * @param {string} url * @param {object} config * @param {object} opts * @param {object} response */ afterDEL: noop2, /** * @name HttpAdapter#afterGET * @method * @param {string} url * @param {object} config * @param {object} opts * @param {object} response */ afterGET: noop2, /** * @name HttpAdapter#afterHTTP * @method * @param {object} config * @param {object} opts * @param {object} response */ afterHTTP: noop2, /** * @name HttpAdapter#afterPOST * @method * @param {string} url * @param {object} data * @param {object} config * @param {object} opts * @param {object} response */ afterPOST: noop2, /** * @name HttpAdapter#afterPUT * @method * @param {string} url * @param {object} data * @param {object} config * @param {object} opts * @param {object} response */ afterPUT: noop2, /** * @name HttpAdapter#beforeDEL * @method * @param {object} url * @param {object} config * @param {object} opts */ beforeDEL: noop, /** * @name HttpAdapter#beforeGET * @method * @param {object} url * @param {object} config * @param {object} opts */ beforeGET: noop, /** * @name HttpAdapter#beforeHTTP * @method * @param {object} config * @param {object} opts */ beforeHTTP: noop, /** * @name HttpAdapter#beforePOST * @method * @param {object} url * @param {object} data * @param {object} config * @param {object} opts */ beforePOST: noop, /** * @name HttpAdapter#beforePUT * @method * @param {object} url * @param {object} data * @param {object} config * @param {object} opts */ beforePUT: noop, _count (mapper, query, opts) { return this.GET( this.getPath('count', mapper, opts.params, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _create (mapper, props, opts) { return this.POST( this.getPath('create', mapper, props, opts), this.serialize(mapper, props, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _createMany (mapper, props, opts) { return this.POST( this.getPath('createMany', mapper, null, opts), this.serialize(mapper, props, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _destroy (mapper, id, opts) { return this.DEL( this.getPath('destroy', mapper, id, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _destroyAll (mapper, query, opts) { return this.DEL( this.getPath('destroyAll', mapper, null, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _end (mapper, opts, response) { return [this.deserialize(mapper, response, opts), response] }, _find (mapper, id, opts) { return this.GET( this.getPath('find', mapper, id, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _findAll (mapper, query, opts) { return this.GET( this.getPath('findAll', mapper, opts.params, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _sum (mapper, field, query, opts) { return this.GET( this.getPath('sum', mapper, opts.params, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _update (mapper, id, props, opts) { return this.PUT( this.getPath('update', mapper, id, opts), this.serialize(mapper, props, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _updateAll (mapper, props, query, opts) { return this.PUT( this.getPath('updateAll', mapper, null, opts), this.serialize(mapper, props, opts), opts ).then((response) => this._end(mapper, opts, response)) }, _updateMany (mapper, records, opts) { return this.PUT( this.getPath('updateMany', mapper, null, opts), this.serialize(mapper, records, opts), opts ).then((response) => this._end(mapper, opts, response)) }, /** * Retrieve the number of records that match the selection `query`. * * @name HttpAdapter#count * @method * @param {object} mapper The mapper. * @param {object} query Selection query. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ count (mapper, query, opts) { query || (query = {}) opts || (opts = {}) opts.params = this.getParams(opts) opts.params.count = true opts.suffix = this.getSuffix(mapper, opts) utils.deepMixIn(opts.params, query) opts.params = this.queryTransform(mapper, opts.params, opts) return Adapter.prototype.count.call(this, mapper, query, opts) }, /** * Create a new the record from the provided `props`. * * @name HttpAdapter#create * @method * @param {object} mapper The mapper. * @param {object} props Properties to send as the payload. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ create (mapper, props, opts) { opts || (opts = {}) opts.params = this.getParams(opts) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.create.call(this, mapper, props, opts) }, /** * Create multiple new records in batch. * * @name HttpAdapter#createMany * @method * @param {object} mapper The mapper. * @param {array} props Array of property objects to send as the payload. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ createMany (mapper, props, opts) { opts || (opts = {}) opts.params = this.getParams(opts) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.createMany.call(this, mapper, props, opts) }, /** * Make an Http request to `url` according to the configuration in `config`. * * @name HttpAdapter#DEL * @method * @param {string} url Url for the request. * @param {object} [config] Http configuration that will be passed to * {@link HttpAdapter#HTTP}. * @param {object} [opts] Configuration options. * @return {Promise} */ DEL (url, config, opts) { let op config || (config = {}) opts || (opts = {}) config.url = url || config.url config.method = config.method || 'delete' // beforeDEL lifecycle hook op = opts.op = 'beforeDEL' return utils.resolve(this[op](url, config, opts)) .then((_config) => { // Allow re-assignment from lifecycle hook config = _config === undefined ? config : _config op = opts.op = 'DEL' this.dbg(op, url, config, opts) return this.HTTP(config, opts) }) .then((response) => { // afterDEL lifecycle hook op = opts.op = 'afterDEL' return utils.resolve(this[op](url, config, opts, response)) .then((_response) => _response === undefined ? response : _response) }) }, /** * Transform the server response object into the payload that will be returned * to JSData. * * @name HttpAdapter#deserialize * @method * @param {object} mapper The mapper used for the operation. * @param {object} response Response object from {@link HttpAdapter#HTTP}. * @param {object} opts Configuration options. * @return {(object|array)} Deserialized data. */ deserialize (mapper, response, opts) { opts || (opts = {}) if (utils.isFunction(opts.deserialize)) { return opts.deserialize(mapper, response, opts) } if (utils.isFunction(mapper.deserialize)) { return mapper.deserialize(mapper, response, opts) } if (response && response.hasOwnProperty('data')) { return response.data } return response }, /** * Destroy the record with the given primary key. * * @name HttpAdapter#destroy * @method * @param {object} mapper The mapper. * @param {(string|number)} id Primary key of the record to destroy. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ destroy (mapper, id, opts) { opts || (opts = {}) opts.params = this.getParams(opts) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.destroy.call(this, mapper, id, opts) }, /** * Destroy the records that match the selection `query`. * * @name HttpAdapter#destroyAll * @method * @param {object} mapper The mapper. * @param {object} query Selection query. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ destroyAll (mapper, query, opts) { query || (query = {}) opts || (opts = {}) opts.params = this.getParams(opts) utils.deepMixIn(opts.params, query) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.destroyAll.call(this, mapper, query, opts) }, /** * Log an error. * * @name HttpAdapter#error * @method * @param {...*} [args] Arguments to log. */ error (...args) { if (console) { console[typeof console.error === 'function' ? 'error' : 'log'](...args) } }, /** * Make an Http request using `window.fetch`. * * @name HttpAdapter#fetch * @method * @param {object} config Request configuration. * @param {object} config.data Payload for the request. * @param {string} config.method Http method for the request. * @param {object} config.headers Headers for the request. * @param {object} config.params Querystring for the request. * @param {string} config.url Url for the request. */ fetch (config) { const requestConfig = { method: config.method, // turn the plain headers object into the Fetch Headers object headers: new Headers(config.headers || {}) } if (config.data) { requestConfig.body = utils.toJson(config.data) } return fetch(buildUrl(config.url, config.params), requestConfig) .then((response) => { response.config = { method: config.method, url: config.url } return response.json() .then((data) => { response.data = data return response }) }) }, /** * Retrieve the record with the given primary key. * * @name HttpAdapter#find * @method * @param {object} mapper The mapper. * @param {(string|number)} id Primary key of the record to retrieve. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ find (mapper, id, opts) { opts || (opts = {}) opts.params = this.getParams(opts) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.find.call(this, mapper, id, opts) }, /** * Retrieve the records that match the selection `query`. * * @name HttpAdapter#findAll * @method * @param {object} mapper The mapper. * @param {object} query Selection query. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ findAll (mapper, query, opts) { query || (query = {}) opts || (opts = {}) opts.params = this.getParams(opts) opts.suffix = this.getSuffix(mapper, opts) utils.deepMixIn(opts.params, query) opts.params = this.queryTransform(mapper, opts.params, opts) return Adapter.prototype.findAll.call(this, mapper, query, opts) }, /** * Make a GET request. * * @name HttpAdapter#GET * @method * @param {string} url The url for the request. * @param {object} config Request configuration options. * @param {object} [opts] Configuration options. * @return {Promise} */ GET (url, config, opts) { let op config || (config = {}) opts || (opts = {}) config.url = url || config.url config.method = config.method || 'get' // beforeGET lifecycle hook op = opts.op = 'beforeGET' return utils.resolve(this[op](url, config, opts)) .then((_config) => { // Allow re-assignment from lifecycle hook config = _config === undefined ? config : _config op = opts.op = 'GET' this.dbg(op, url, config, opts) return this.HTTP(config, opts) }) .then((response) => { // afterGET lifecycle hook op = opts.op = 'afterGET' return utils.resolve(this[op](url, config, opts, response)) .then((_response) => _response === undefined ? response : _response) }) }, /** * @name HttpAdapter#getEndpoint * @method * @param {object} mapper The Mapper. * @param {*} id The primary key, if any. * @param {boolean} opts Configuration options. * @return {string} Full path. */ getEndpoint (mapper, id, opts) { opts || (opts = {}) opts.params = utils.isUndefined(opts.params) ? {} : opts.params const relationList = mapper.relationList || [] let endpoint = utils.isUndefined(opts.endpoint) ? (utils.isUndefined(mapper.endpoint) ? mapper.name : mapper.endpoint) : opts.endpoint relationList.forEach((def) => { if (def.type !== 'belongsTo' || !def.parent) { return } let item const parentKey = def.foreignKey const parentDef = def.getRelation() let parentId = opts.params[parentKey] if (parentId === false || !parentKey || !parentDef) { if (parentId === false) { delete opts.params[parentKey] } return false } else { delete opts.params[parentKey] if (utils.isObject(id)) { item = id } if (item) { parentId = parentId || def.getForeignKey(item) || (def.getLocalField(item) ? utils.get(def.getLocalField(item), parentDef.idAttribute) : null) } if (parentId) { delete opts.endpoint const _opts = {} utils.forOwn(opts, (value, key) => { _opts[key] = value }) utils._(_opts, parentDef) endpoint = makePath(this.getEndpoint(parentDef, parentId, _opts), parentId, endpoint) return false } } }) return endpoint }, /** * @name HttpAdapter#getPath * @method * @param {string} method The method being executed. * @param {object} mapper The Mapper. * @param {(string|number)?} id The primary key, if any. * @param {object} opts Configuration options. */ getPath (method, mapper, id, opts) { opts || (opts = {}) const args = [ opts.basePath === undefined ? (mapper.basePath === undefined ? this.basePath : mapper.basePath) : opts.basePath, this.getEndpoint(mapper, (utils.isString(id) || utils.isNumber(id) || method === 'create') ? id : null, opts) ] if (method === 'find' || method === 'update' || method === 'destroy') { args.push(id) } return makePath.apply(utils, args) }, getParams (opts) { opts || (opts = {}) if (opts.params === undefined) { return {} } return utils.copy(opts.params) }, getSuffix (mapper, opts) { opts || (opts = {}) if (opts.suffix === undefined) { if (mapper.suffix === undefined) { return this.suffix } return mapper.suffix } return opts.suffix }, /** * Make an Http request. * * @name HttpAdapter#HTTP * @method * @param {object} config Request configuration options. * @param {object} [opts] Configuration options. * @return {Promise} */ HTTP (config, opts) { const start = new Date() opts || (opts = {}) const payload = config.data const cache = config.cache const timeout = config.timeout const params = config.params config = utils.copy(config, null, null, null, ['data', 'cache', 'timeout', 'params']) // params could have data, cache, timeout config.params = utils.copy(params) config = utils.deepMixIn(config, this.httpConfig) config.data = payload config.cache = cache if (timeout !== undefined) { config.timeout = timeout } if (this.forceTrailingSlash && config.url[config.url.length - 1] !== '/') { config.url += '/' } config.method = config.method.toUpperCase() const suffix = config.suffix || opts.suffix || this.suffix if (suffix && config.url.substr(config.url.length - suffix.length) !== suffix) { config.url += suffix } const logResponse = (data) => { const str = `${start.toUTCString()} - ${config.method.toUpperCase()} ${config.url} - ${data.status} ${(new Date().getTime() - start.getTime())}ms` if (data.status >= 200 && data.status < 300) { if (this.log) { this.dbg(str, data) } return data } else { if (this.error) { this.error(`'FAILED: ${str}`, data) } return utils.reject(data) } } if (!this.http) { if ((this.useFetch || opts.useFetch)) { if (!hasFetch) { throw new Error('Attempting to use window.fetch, but it is not available!') } } else { throw new Error('You have not configured this adapter with an http library!') } } return utils.resolve(this.beforeHTTP(config, opts)) .then((_config) => { config = _config || config if (hasFetch && (this.useFetch || opts.useFetch || !this.http)) { return this.fetch(config, opts).then(logResponse, logResponse) } const httpConfig = utils.plainCopy(config) delete httpConfig.adapter return this.http(httpConfig).then(logResponse, logResponse) .catch((err) => this.responseError(err, config, opts)) }) .then((response) => { return utils.resolve(this.afterHTTP(config, opts, response)) .then((_response) => _response === undefined ? response : _response) }) }, /** * Make a POST request. * * @name HttpAdapter#POST * @method * @param {*} url The url for the request. * @param {object} data Payload for the request. * @param {object} config Request configuration options. * @param {object} [opts] Configuration options. * @return {Promise} */ POST (url, data, config, opts) { let op config || (config = {}) opts || (opts = {}) config.url = url || config.url config.data = data || config.data config.method = config.method || 'post' // beforePOST lifecycle hook op = opts.op = 'beforePOST' return utils.resolve(this[op](url, data, config, opts)) .then((_config) => { // Allow re-assignment from lifecycle hook config = _config === undefined ? config : _config op = opts.op = 'POST' this.dbg(op, url, data, config, opts) return this.HTTP(config, opts) }) .then((response) => { // afterPOST lifecycle hook op = opts.op = 'afterPOST' return utils.resolve(this[op](url, data, config, opts, response)) .then((_response) => _response === undefined ? response : _response) }) }, /** * Make a PUT request. * * @name HttpAdapter#PUT * @method * @param {*} url The url for the request. * @param {object} data Payload for the request. * @param {object} config Request configuration options. * @param {object} [opts] Configuration options. * @return {Promise} */ PUT (url, data, config, opts) { let op config || (config = {}) opts || (opts = {}) config.url = url || config.url config.data = data || config.data config.method = config.method || 'put' // beforePUT lifecycle hook op = opts.op = 'beforePUT' return utils.resolve(this[op](url, data, config, opts)) .then((_config) => { // Allow re-assignment from lifecycle hook config = _config === undefined ? config : _config op = opts.op = 'PUT' this.dbg(op, url, data, config, opts) return this.HTTP(config, opts) }) .then((response) => { // afterPUT lifecycle hook op = opts.op = 'afterPUT' return utils.resolve(this[op](url, data, config, opts, response)) .then((_response) => _response === undefined ? response : _response) }) }, /** * Transform the querystring object before it is serialized. This doesn't do * anything by default. * * @name HttpAdapter#queryTransform * @method * @param {object} mapper The Mapper that triggered the request. * @param {*} params The querystring object. * @param {*} opts Configuration options * @return {*} Transformed params. */ queryTransform (mapper, params, opts) { opts || (opts = {}) if (utils.isFunction(opts.queryTransform)) { return opts.queryTransform(mapper, params, opts) } if (utils.isFunction(mapper.queryTransform)) { return mapper.queryTransform(mapper, params, opts) } return params }, /** * Error handler invoked when the promise returned by {@link HttpAdapter#http} * is rejected. Default implementation is to just return the error wrapped in * a rejected Promise, aka rethrow the error. {@link HttpAdapter#http} is * called by {@link HttpAdapter#HTTP}. * * @name HttpAdapter#responseError * @method * @param {*} err The error that {@link HttpAdapter#http} rejected with. * @param {object} config The `config` argument that was passed to {@link HttpAdapter#HTTP}. * @param {*} opts The `opts` argument that was passed to {@link HttpAdapter#HTTP}. * @return {Promise} */ responseError (err, config, opts) { return utils.reject(err) }, /** * Serialize request data. This doesn't do anything by default. * * @name HttpAdapter#serialize * @method * @param {object} mapper The Mapper that triggered the request. * @param {object} data The request payload. * @param {*} opts Configuration options. * @return {*} Serialized data. */ serialize (mapper, data, opts) { opts || (opts = {}) if (utils.isFunction(opts.serialize)) { return opts.serialize(mapper, data, opts) } if (utils.isFunction(mapper.serialize)) { return mapper.serialize(mapper, data, opts) } return data }, /** * Retrieve the sum of the field of the records that match the selection query. * * @name HttpAdapter#sum * @method * @param {object} mapper The mapper. * @param {string} field The field to sum. * @param {object} query Selection query. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ sum (mapper, field, query, opts) { query || (query = {}) opts || (opts = {}) if (!utils.isString(field)) { throw new Error('field must be a string!') } opts.params = this.getParams(opts) opts.params.sum = field opts.suffix = this.getSuffix(mapper, opts) utils.deepMixIn(opts.params, query) opts.params = this.queryTransform(mapper, opts.params, opts) return Adapter.prototype.sum.call(this, mapper, field, query, opts) }, /** * Perform an update. Makes a PUT request by default. * * @name HttpAdapter#update * @method * @param {object} mapper The Mapper for the request. * @param {*} id The primary key of the record being updated. * @param {*} props The update payload. * @param {object} [opts] Configuration options. * @return {Promise} */ update (mapper, id, props, opts) { opts || (opts = {}) opts.params = this.getParams(opts) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.update.call(this, mapper, id, props, opts) }, /** * Perform an update against records that match the selection query. Makes a * PUT request by default. * * @name HttpAdapter#updateAll * @method * @param {object} mapper The Mapper for the request. * @param {object} props The update payload. * @param {object} query The selection query. See {@link http://www.js-data.io/docs/query-syntax}. * @param {object} [opts] Configuration options. * @return {Promise} */ updateAll (mapper, props, query, opts) { query || (query = {}) opts || (opts = {}) opts.params = this.getParams(opts) utils.deepMixIn(opts.params, query) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.updateAll.call(this, mapper, props, query, opts) }, /** * Update multiple individual records in a batch. * * @name HttpAdapter#updateMany * @method * @param {object} mapper The Mapper for the request. * @param {array} records Array of property objects to send as the payload. * Each must contain the primary key of the record to be updated. * @param {object} [opts] Configuration options. * @param {string} [opts.params] Querystring parameters. * @param {string} [opts.suffix={@link HttpAdapter#suffix}] See {@link HttpAdapter#suffix}. * @return {Promise} */ updateMany (mapper, records, opts) { opts || (opts = {}) opts.params = this.getParams(opts) opts.params = this.queryTransform(mapper, opts.params, opts) opts.suffix = this.getSuffix(mapper, opts) return Adapter.prototype.updateMany.call(this, mapper, records, opts) } }) /** * Add an Http actions to a mapper. * * @example * // CommonJS * var JSData = require('js-data'); * // It is recommended to use DataStore in the browser * var DataStore = JSData.DataStore; * * var JSDataHttp = require('js-data-http'); * var HttpAdapter = JSDataHttp.HttpAdapter; * var addAction = JSDataHttp.addAction; * * var httpAdapter = new HttpAdapter(); * var store = new DataStore(); * * store.registerAdapter('http', httpAdapter, { 'default': true }); * store.defineMapper('school'); * * // GET /reports/schools/:school_id/teachers * addAction('getTeacherReports', { * endpoint: 'reports/schools', * pathname: 'teachers', * method: 'GET' * })(store.getMapper('school')); * * // /reports/schools/1234/teachers * store.getMapper('school').getTeacherReports(1234).then((response) => { * // ... * }); * * @name module:js-data-http.addAction * @method * @param {string} name Name of the new action. * @param {object} [opts] Action configuration * @param {string} [opts.adapter="http"] The name of the adapter to use. * @param {string} [opts.pathname] Set the action's pathname. * @param {function} [opts.request] Specify a request handler to be executed * before the request is made. * @param {function} [opts.response] Specify a response handler to be executed * after the response is received. * @param {function} [opts.responseError] Specify an error handler to be * executed on error. * @return {function} Decoration function, which should be passed the mapper to * decorate when invoked. */ export function addAction (name, opts) { if (!name || !utils.isString(name)) { throw new TypeError('action(name[, opts]): Expected: string, Found: ' + typeof name) } return function (mapper) { if (mapper[name]) { throw new Error('action(name[, opts]): ' + name + ' already exists on target!') } opts.request = opts.request || function (config) { return config } opts.response = opts.response || function (response) { return response } opts.responseError = opts.responseError || function (err) { return utils.reject(err) } mapper[name] = function (id, _opts) { _opts = _opts || {} if (utils.isObject(id)) { _opts = id } utils.fillIn(_opts, opts) let adapter = this.getAdapter(_opts.adapter || this.defaultAdapter || 'http') const config = {} config.mapper = this.name utils.deepMixIn(config, _opts) config.method = config.method || 'GET' if (typeof _opts.getEndpoint === 'function') { config.url = _opts.getEndpoint(this, _opts) } else { let args = [ _opts.basePath || this.basePath || adapter.basePath, adapter.getEndpoint(this, id, _opts) ] if (utils.isSorN(id)) { args.push(id) } args.push(opts.pathname || name) config.url = makePath.apply(null, args) } return utils.resolve(config) .then(_opts.request) .then((config) => adapter.HTTP(config)) .then((data) => { if (data && data.config) { data.config.mapper = this.name } return data }) .then(_opts.response, _opts.responseError) } return mapper } } /** * Add multiple Http actions to a mapper. See {@link HttpAdapter.addAction} for * action configuration options. * * @example * // CommonJS * var JSData = require('js-data'); * // It is recommended to use DataStore in the browser * var DataStore = JSData.DataStore; * * var JSDataHttp = require('js-data-http'); * var HttpAdapter = JSDataHttp.HttpAdapter; * var addActions = JSDataHttp.addActions; * * var httpAdapter = new HttpAdapter(); * var store = new DataStore(); * * store.registerAdapter('http', httpAdapter, { 'default': true }); * store.defineMapper('school'); * * addActions({ * // GET /reports/schools/:school_id/teachers * getTeacherReports: { * basePath: 'reports/schools', * pathname: 'teachers', * method: 'GET' * } * })(store.getMapper('school')); * * // /reports/schools/1234/teachers * store.getMapper('school').getTeacherReports(1234).then((response) => { * // ... * }); * * @name module:js-data-http.addActions * @method * @param {object.<string, object>} opts Object where the key is an action name * and the value is the configuration for the action. * @return {function} Decoration function, which should be passed the mapper to * decorate when invoked. */ export function addActions (opts) { opts || (opts = {}) return function (mapper) { utils.forOwn(opts, function (value, key) { addAction(key, value)(mapper) }) return mapper } } /** * Details of the current version of the `js-data-http` module. * * @name module:js-data-http.version * @type {object} * @property {string} version.full The full semver value. * @property {number} version.major The major version number. * @property {number} version.minor The minor version number. * @property {number} version.patch The patch version number. * @property {(string|boolean)} version.alpha The alpha version value, * otherwise `false` if the current version is not alpha. * @property {(string|boolean)} version.beta The beta version value, * otherwise `false` if the current version is not beta. */ export const version = '<%= version %>' /** * Registered as `js-data-http` in NPM and Bower. The build of `js-data-http` * that works on Node.js is registered in NPM as `js-data-http-node`. The build * of `js-data-http` that does not bundle `axios` is registered in NPM and Bower * as `js-data-fetch`. * * @example <caption>Script tag</caption> * var HttpAdapter = window.JSDataHttp.HttpAdapter; * var httpAdapter = new HttpAdapter(); * * @example <caption>CommonJS</caption> * var HttpAdapter = require('js-data-Http').HttpAdapter; * var httpAdapter = new HttpAdapter(); * * @example <caption>ES2015 Modules</caption> * import { HttpAdapter } from 'js-data-Http'; * const httpAdapter = new HttpAdapter(); * * @example <caption>AMD</caption> * define('myApp', ['js-data-Http'], function (JSDataHttp) { * var HttpAdapter = JSDataHttp.HttpAdapter; * var httpAdapter = new HttpAdapter(); * * // ... * }); * * @module js-data-http */ /** * Create a subclass of this HttpAdapter: * @example <caption>HttpAdapter.extend</caption> * // Normally you would do: import { HttpAdapter } from 'js-data-http'; * // or: import { HttpAdapter } from 'js-data-http-node'; * const JSDataHttp = require('js-data-http-node'); * const { HttpAdapter } = JSDataHttp; * console.log('Using JSDataHttp v' + JSDataHttp.version.full); * * // Extend the class using ES2015 class syntax. * class CustomHttpAdapterClass extends HttpAdapter { * foo () { return 'bar'; } * static beep () { return 'boop'; } * } * const customHttpAdapter = new CustomHttpAdapterClass(); * console.log(customHttpAdapter.foo()); * console.log(CustomHttpAdapterClass.beep()); * * // Extend the class using alternate method. * const OtherHttpAdapterClass = HttpAdapter.extend({ * foo () { return 'bar'; } * }, { * beep () { return 'boop'; } * }) * const otherHttpAdapter = new OtherHttpAdapterClass(); * console.log(otherHttpAdapter.foo()); * console.log(OtherHttpAdapterClass.beep()); * * // Extend the class, providing a custom constructor. * function AnotherHttpAdapterClass () { * HttpAdapter.call(this); * this.created_at = new Date().getTime(); * } * HttpAdapter.extend({ * constructor: AnotherHttpAdapterClass, * foo () { return 'bar'; } * }, { * beep () { return 'boop'; } * }) * const anotherHttpAdapter = new AnotherHttpAdapterClass(); * console.log(anotherHttpAdapter.created_at); * console.log(anotherHttpAdapter.foo()); * console.log(AnotherHttpAdapterClass.beep()); * * @method HttpAdapter.extend * @param {object} [props={}] Properties to add to the prototype of the * subclass. * @param {object} [props.constructor] Provide a custom constructor function * to be used as the subclass itself. * @param {object} [classProps={}] Static properties to add to the subclass. * @returns {Constructor} Subclass of this HttpAdapter class. * @since 3.0.0 */