diff --git a/README.md b/README.md index 1f16242..1b62c6b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ VuexORM.use(VuexORMAxios, { 'Accept': 'application/json', 'Content-Type': 'application/json' } + }, + // Include an HTTPConf for each entity + modelHttpConfigs: { + 'todo': TodoHTTPConf } }) .. @@ -30,6 +34,46 @@ export default () => new Vuex.Store({ ``` +### HTTPConf (for each model) +``` js +export const TodoHTTPConf = { + http: { + url: '/batch' + }, + methods: { + $fetch: { + name: 'fetch', + http: { + url: '', + method: 'get' + } + }, + $get: { + name: 'get', + http: { + url: '/:id', + method: 'get', + }, + }, + $update: { + name: 'update', + http: { + url: '/:id/mark/', + method: 'put' + } + }, + $delete: { + name: 'delete', + http: { + url: '/:id', + method: 'delete' + } + } + } +} +``` + + # Axios Request Config ``` js @@ -203,7 +247,7 @@ User.$get({ params: { id: 1 } -}); +}); /** * @uri `/users` diff --git a/package-lock.json b/package-lock.json index e850493..0a7fee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2844,6 +2844,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.0.tgz", + "integrity": "sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", diff --git a/package.json b/package.json index fe58ada..2ecd5cc 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dependencies": { "@babel/polyfill": "^7.2.5", "@vuex-orm/core": "^0.26.2", + "deepmerge": "^3.2.0", "lodash-es": "^4.17.11" }, "devDependencies": { diff --git a/src/actions/Action.js b/src/actions/Action.js index 0adf2a2..3c1c2ed 100644 --- a/src/actions/Action.js +++ b/src/actions/Action.js @@ -1,4 +1,5 @@ -import merge from 'lodash-es/merge' +// import merge from 'lodash-es/merge' +import * as deepmerge from 'deepmerge' import Context from '../common/context' import { ModuleConfig, ModelConfig } from '../support/interfaces' @@ -8,7 +9,7 @@ export default class Action { * @param {object} model */ static transformModule(module) { - return merge({}, ModuleConfig, module) + return deepmerge.all([{}, ModuleConfig, module]) } /** @@ -17,8 +18,8 @@ export default class Action { */ static transformModel(model) { const context = Context.getInstance() - ModelConfig.http = merge({}, ModelConfig.http, context.options.http) - model.methodConf = merge({}, ModelConfig, model.methodConf) + ModelConfig.http = deepmerge.all([{}, ModelConfig.http, context.options.http]) + model.methodConf = deepmerge.all([{}, ModelConfig, model.methodConf]) model.methodConf.http.url = model.methodConf.http.url === '/' ? `/${model.entity}` : model.methodConf.http.url /** @@ -26,7 +27,7 @@ export default class Action { */ model.getFields = () => { if (!model.cachedFields) { - model.cachedFields = merge( + model.cachedFields = deepmerge.all([ {}, { $id: model.attr(undefined), @@ -36,7 +37,7 @@ export default class Action { $deleteErrors: model.attr([]), }, model.fields() - ) + ]) } return model.cachedFields @@ -48,15 +49,21 @@ export default class Action { /** * Transform Params and Return Endpoint * @param {string} type - * @param {object} model - * @param {object} config + * @param {object} HttpConf + * @param {object} config e.g. {params: {id:1}, query: {limit: 10, offset: 0}} */ - static transformParams(type, model, config = {}) { - let endpoint = `${model.methodConf.http.url}${model.methodConf.methods[type].http.url}` + static transformParams(type, HttpConf, config = {}) { + // e.g. endpoint = `/batch/:id/mark` + let endpoint = `${HttpConf.http.url}${HttpConf.methods[type].http.url}` + + // Create array of matching `/:id` params from endpoint, remove `/`. + // e.g. params = [":id"] let params = (endpoint.match(/(\/?)(:)([A-z]*)/gm) || []).map(param => { return param.replace('/', '') }) + // For each extracted param (e.g. `:id`) then try replace it with a value + // from config.params. params.forEach(param => { let paramName = param.replace(':', '') const paramValue = paramName in config.params ? config.params[paramName] : '' @@ -66,10 +73,15 @@ export default class Action { const context = Context.getInstance() let suffix = context.options.http.suffix if (suffix) endpoint += suffix + + // If any keys in the `config.query` dictionary then encode them correctly + // and attach to the end of the string. if (config.query) endpoint += `?${Object.keys(config.query) .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(config.query[k])}`) .join('&')}` + + console.log(`endpoint: ${endpoint}`) return endpoint } } diff --git a/src/actions/Fetch.js b/src/actions/Fetch.js index 4375cfe..23fcf3c 100644 --- a/src/actions/Fetch.js +++ b/src/actions/Fetch.js @@ -1,6 +1,8 @@ +import * as deepmerge from 'deepmerge' import Axios from '../orm/axios' import Context from '../common/context' import Action from './Action' +import { ModelConfig } from '../support/interfaces' export default class Fetch extends Action { /** @@ -11,12 +13,22 @@ export default class Fetch extends Action { static async call({ state, commit }, params = {}) { const context = Context.getInstance() const model = context.getModelFromState(state) - const endpoint = Action.transformParams('$fetch', model, params) - const axios = new Axios(model.methodConf.http) + // Merge default http settings + ModelConfig.http = deepmerge.all([{}, ModelConfig.http, context.options.http]) + + // Get HTTPConf for this model + const HttpConf = context.getHttpConfigForModel(model.entity) + const endpoint = Action.transformParams('$fetch', HttpConf, params) + + // New axios instance (with settings) + const axios = new Axios(ModelConfig.http) const request = axios.get(endpoint) this.onRequest(commit) - request.then(data => this.onSuccess(commit, model, data)).catch(error => this.onError(commit, error)) + + request + .then(data => this.onSuccess(commit, model, data)) + .catch(error => this.onError(commit, error)) return request } @@ -31,15 +43,15 @@ export default class Fetch extends Action { /** * On Successful Request Method + * NOTE: At this point the `data` property is just the raw response from the + * server (other axios response properties have been stripped). * @param {object} commit * @param {object} model * @param {object} data */ static onSuccess(commit, model, data) { commit('onSuccess') - model.insertOrUpdate({ - data, - }) + model.insertOrUpdate({data}) } /** diff --git a/src/actions/Get.js b/src/actions/Get.js index 4cce61f..bed1b73 100644 --- a/src/actions/Get.js +++ b/src/actions/Get.js @@ -1,6 +1,8 @@ +import * as deepmerge from 'deepmerge' import Axios from '../orm/axios' import Context from '../common/context' import Action from './Action' +import { ModelConfig } from '../support/interfaces' export default class Get extends Action { /** @@ -11,12 +13,22 @@ export default class Get extends Action { static async call({ state, commit }, params = {}) { const context = Context.getInstance() const model = context.getModelFromState(state) - const endpoint = Action.transformParams('$get', model, params) - const axios = new Axios(model.methodConf.http) + // Merge default http settings + ModelConfig.http = deepmerge.all([{}, ModelConfig.http, context.options.http]) + + // Get HTTPConf for this model + const HttpConf = context.getHttpConfigForModel(model.entity) + const endpoint = Action.transformParams('$get', HttpConf, params) + + // New axios instance (with settings) + const axios = new Axios(ModelConfig.http) const request = axios.get(endpoint) this.onRequest(commit) - request.then(data => this.onSuccess(commit, model, data)).catch(error => this.onError(commit, error)) + + request + .then(data => this.onSuccess(commit, model, data)) + .catch(error => this.onError(commit, error)) return request } @@ -31,15 +43,15 @@ export default class Get extends Action { /** * On Successful Request Method + * NOTE: At this point the `data` property is just the raw response from the + * server (other axios response properties have been stripped). * @param {object} commit * @param {object} model * @param {object} data */ static onSuccess(commit, model, data) { commit('onSuccess') - model.insertOrUpdate({ - data, - }) + model.insertOrUpdate({data}) } /** diff --git a/src/common/context.js b/src/common/context.js index d3b5752..b096134 100644 --- a/src/common/context.js +++ b/src/common/context.js @@ -1,4 +1,5 @@ -import merge from 'lodash-es/merge' +// import merge from 'lodash-es/merge' +import * as deepmerge from 'deepmerge' import { VuexOrmPluginConfig } from '../support/interfaces' export default class Context { @@ -11,9 +12,14 @@ export default class Context { */ constructor(components, options) { this.components = components - this.options = merge({}, VuexOrmPluginConfig, options) + this.options = deepmerge.all([{}, VuexOrmPluginConfig, options]) this.database = options.database + if (!options.modelHttpConfigs) { + throw new Error('At least one modelHttpConf required') + } + this.modelHttpConfigs = options.modelHttpConfigs + if (!options.database) { throw new Error('database option is required to initialise!') } @@ -45,4 +51,16 @@ export default class Context { getModelFromState(state) { return this.database.entities.find(({ name }) => name == state.$name).model } + + /** + * Returns an HTTPConf object for a given entity name + * @param {string} entityName + */ + getHttpConfigForModel(entityName) { + if (this.modelHttpConfigs.hasOwnProperty(entityName)) { + return this.modelHttpConfigs[entityName] + } else { + throw new Error(`No HTTPconfig found for entity: ${entityName}`) + } + } } diff --git a/src/orm/axios.js b/src/orm/axios.js index 072395f..80f9a43 100644 --- a/src/orm/axios.js +++ b/src/orm/axios.js @@ -2,19 +2,18 @@ import axios from 'axios' export default class Axios { constructor(http) { - this.instance = axios.create(http); - this.setAuthentication(http.access_token); + this.instance = axios.create(http) + this.setAuthentication(http.access_token) this.instance.interceptors.request.use( config => http.onRequest(config), error => http.onError(error), - ); + ) - - this.instance.interceptors.request.use( - config => http.onRequest(config), + this.instance.interceptors.response.use( + config => http.onResponse(config), error => http.onError(error), - ); + ) return this.instance; } diff --git a/src/vuex-orm-axios.js b/src/vuex-orm-axios.js index a1cab64..fa12f93 100644 --- a/src/vuex-orm-axios.js +++ b/src/vuex-orm-axios.js @@ -43,7 +43,8 @@ export default class VuexOrmAxios { */ context.database.entities.map(entity => { entity.module = Action.transformModule(entity.module) - entity.model = Action.transformModel(entity.model) + // NOTE: This has been removed because it causes issues (IE11) + // entity.model = Action.transformModel(entity.model) return entity })