├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── dist └── vuex-jsdata-plugin.js ├── examples └── simple │ ├── index.html │ ├── index.js │ ├── jsdata │ ├── index.js │ └── models │ │ ├── comment.js │ │ └── user.js │ └── vuex │ ├── actions.js │ ├── config.js │ ├── index.js │ └── plugins.js ├── index.js ├── package.json ├── rollup.config.js └── utils ├── helpers.js └── polyfill.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers", 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Alexandre Bonaventure Geissmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuex-jsdata-plugin 2 | A simple attempt to help using jsdata alongside Vue.js: 3 | This plugin syncing Vuex store with js-data 4 | After each injection made by js-data some silent mutations are triggered to ensure vuex store is kept in sync with your ressources (and trigger reactivity). 5 | 6 | Read more : https://github.com/js-data/js-data/issues/57 7 | 8 | # Dependencies 9 | This plugin is developed for 10 | ``` 11 | vuex@2.0.x 12 | js-data@2.9.x 13 | ``` 14 | 15 | You're welcome to contribute to help compatibility issues. 16 | 17 | # Usage 18 | With NPM 19 | ```npm install vuex-plugin-jsdata``` 20 | 21 | Then when you setup vuex: 22 | ``` 23 | import jsdataPlugin from 'vuex-plugin-jsdata' 24 | import yourJsDataStore from 'xxxx' 25 | 26 | const plugins = [ 27 | jsdataPlugin(yourJsDataStore), 28 | ... // other plugins 29 | ] 30 | 31 | new Vuex.Store({ 32 | // state, 33 | // actions, 34 | // mutations, 35 | plugins, 36 | }) 37 | 38 | ``` 39 | # How does it work ? 40 | Every change in a js-data ressource are made with the DSInject method. 41 | The plugin manage the state tree(vuex) under a DS module by listening to the afterInject hook (js-data) 42 | 43 | ## mutation 44 | vuex-plugin-jsdata fire only one silent mutation : 45 | ``REFRESH_DATASTORE`` 46 | 47 | ## getters 48 | Although all local ressources injected in the jsdata-store can be found in the vuex store under the namespaced module DS, the plugin provide automatic getters for every model. 49 | 50 | Ex: 51 | ``` 52 | // Register a model in js-data 53 | 54 | export const User = store.defineResource({ 55 | name: 'user', 56 | endpoint: 'users', 57 | }) 58 | ``` 59 | ``` 60 | // in a .vue component 61 | 77 | 78 | 79 | div.user 80 | pre {{ user | json }} 81 | 82 | 83 | ``` 84 | ## global helper 85 | This plugin provide a handy way to make a ressource available inside components. 86 | ### mapRessources 87 | mapRessources([ 88 | { nameOfTheGetter: [nameOfTheRessource:string, id_key:string]}, 89 | ... 90 | ]) 91 | mapRessources is a getter factory designed to get a single record which id is computed from $vm[id_key]. 92 | Its really useful for getting specific records dynamicly (eg: get user with id picked from router params) 93 | example: 94 | ``` 95 | // in store - DSUsers: { 1: { name: 'Alex' } } 96 | // component definition 97 | // using the object spread operator 98 | $vm = { 99 | data() { 100 | return { 101 | user_id: 1 102 | } 103 | }, 104 | computed: { 105 | ...mapRessources([ 106 | { user: ['User', 'user_id'] } 107 | { userFromRoute: ['User', '$route.params.id'] } // with vue-router 108 | ]), 109 | } 110 | } 111 | // Log 112 | $vm.user.name -> 'Alex' 113 | $vm.userFromRoute.name -> 'Alex' 114 | ``` 115 | 116 | # Example 117 | Clone the repo and run 118 | ``` 119 | npm install 120 | npm run example-simple 121 | => go to /examples/simple 122 | ``` 123 | 124 | more to come ... 125 | 126 | # TO-DO 127 | [ ] handle config options 128 | [ ] some examples 129 | 130 | # Contributions 131 | are welcome :) 132 | -------------------------------------------------------------------------------- /dist/vuex-jsdata-plugin.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue'), require('lodash.get')) : 3 | typeof define === 'function' && define.amd ? define(['exports', 'vue', 'lodash.get'], factory) : 4 | (factory((global.vuexjsdataplugin = global.vuexjsdataplugin || {}),global.vue,global.get)); 5 | }(this, (function (exports,vue,get) { 'use strict'; 6 | 7 | get = 'default' in get ? get['default'] : get; 8 | 9 | // Object.values polyfill (borrowed from https://github.com/tc39/proposal-object-values-entries/blob/master/polyfill.js) 10 | var reduce = Function.bind.call(Function.call, Array.prototype.reduce); 11 | var isEnumerable = Function.bind.call(Function.call, Object.prototype.propertyIsEnumerable); 12 | var concat = Function.bind.call(Function.call, Array.prototype.concat); 13 | var keys = Reflect.ownKeys; 14 | 15 | if (!Object.values) { 16 | Object.values = function values(O) { 17 | return reduce(keys(O), function (v, k) { 18 | return concat(v, typeof k === 'string' && isEnumerable(O, k) ? [O[k]] : []); 19 | }, []); 20 | }; 21 | } 22 | 23 | var asyncGenerator = function () { 24 | function AwaitValue(value) { 25 | this.value = value; 26 | } 27 | 28 | function AsyncGenerator(gen) { 29 | var front, back; 30 | 31 | function send(key, arg) { 32 | return new Promise(function (resolve, reject) { 33 | var request = { 34 | key: key, 35 | arg: arg, 36 | resolve: resolve, 37 | reject: reject, 38 | next: null 39 | }; 40 | 41 | if (back) { 42 | back = back.next = request; 43 | } else { 44 | front = back = request; 45 | resume(key, arg); 46 | } 47 | }); 48 | } 49 | 50 | function resume(key, arg) { 51 | try { 52 | var result = gen[key](arg); 53 | var value = result.value; 54 | 55 | if (value instanceof AwaitValue) { 56 | Promise.resolve(value.value).then(function (arg) { 57 | resume("next", arg); 58 | }, function (arg) { 59 | resume("throw", arg); 60 | }); 61 | } else { 62 | settle(result.done ? "return" : "normal", result.value); 63 | } 64 | } catch (err) { 65 | settle("throw", err); 66 | } 67 | } 68 | 69 | function settle(type, value) { 70 | switch (type) { 71 | case "return": 72 | front.resolve({ 73 | value: value, 74 | done: true 75 | }); 76 | break; 77 | 78 | case "throw": 79 | front.reject(value); 80 | break; 81 | 82 | default: 83 | front.resolve({ 84 | value: value, 85 | done: false 86 | }); 87 | break; 88 | } 89 | 90 | front = front.next; 91 | 92 | if (front) { 93 | resume(front.key, front.arg); 94 | } else { 95 | back = null; 96 | } 97 | } 98 | 99 | this._invoke = send; 100 | 101 | if (typeof gen.return !== "function") { 102 | this.return = undefined; 103 | } 104 | } 105 | 106 | if (typeof Symbol === "function" && Symbol.asyncIterator) { 107 | AsyncGenerator.prototype[Symbol.asyncIterator] = function () { 108 | return this; 109 | }; 110 | } 111 | 112 | AsyncGenerator.prototype.next = function (arg) { 113 | return this._invoke("next", arg); 114 | }; 115 | 116 | AsyncGenerator.prototype.throw = function (arg) { 117 | return this._invoke("throw", arg); 118 | }; 119 | 120 | AsyncGenerator.prototype.return = function (arg) { 121 | return this._invoke("return", arg); 122 | }; 123 | 124 | return { 125 | wrap: function (fn) { 126 | return function () { 127 | return new AsyncGenerator(fn.apply(this, arguments)); 128 | }; 129 | }, 130 | await: function (value) { 131 | return new AwaitValue(value); 132 | } 133 | }; 134 | }(); 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | var defineProperty = function (obj, key, value) { 149 | if (key in obj) { 150 | Object.defineProperty(obj, key, { 151 | value: value, 152 | enumerable: true, 153 | configurable: true, 154 | writable: true 155 | }); 156 | } else { 157 | obj[key] = value; 158 | } 159 | 160 | return obj; 161 | }; 162 | 163 | var get$1 = function get$1(object, property, receiver) { 164 | if (object === null) object = Function.prototype; 165 | var desc = Object.getOwnPropertyDescriptor(object, property); 166 | 167 | if (desc === undefined) { 168 | var parent = Object.getPrototypeOf(object); 169 | 170 | if (parent === null) { 171 | return undefined; 172 | } else { 173 | return get$1(parent, property, receiver); 174 | } 175 | } else if ("value" in desc) { 176 | return desc.value; 177 | } else { 178 | var getter = desc.get; 179 | 180 | if (getter === undefined) { 181 | return undefined; 182 | } 183 | 184 | return getter.call(receiver); 185 | } 186 | }; 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | var set$1 = function set$1(object, property, value, receiver) { 205 | var desc = Object.getOwnPropertyDescriptor(object, property); 206 | 207 | if (desc === undefined) { 208 | var parent = Object.getPrototypeOf(object); 209 | 210 | if (parent !== null) { 211 | set$1(parent, property, value, receiver); 212 | } 213 | } else if ("value" in desc && desc.writable) { 214 | desc.value = value; 215 | } else { 216 | var setter = desc.set; 217 | 218 | if (setter !== undefined) { 219 | setter.call(receiver, value); 220 | } 221 | } 222 | 223 | return value; 224 | }; 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | var toConsumableArray = function (arr) { 241 | if (Array.isArray(arr)) { 242 | for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; 243 | 244 | return arr2; 245 | } else { 246 | return Array.from(arr); 247 | } 248 | }; 249 | 250 | var MUTATION = 'datastore/REFRESH_DATASTORE'; 251 | var MUTATION_DELETE = 'datastore/DELETE'; 252 | var DStore = void 0; 253 | 254 | var index = function (_DStore) { 255 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 256 | _ref$namespace = _ref.namespace, 257 | namespace = _ref$namespace === undefined ? 'DS' : _ref$namespace, 258 | _ref$silent = _ref.silent, 259 | silent = _ref$silent === undefined ? true : _ref$silent; 260 | 261 | DStore = _DStore; 262 | if (!DStore) { 263 | console.warn('You must initialize vuex-jsdata-plugin with a DS store object from js-data'); 264 | return; 265 | } 266 | 267 | return function (store) { 268 | var _mutations; 269 | 270 | var ressources = Object.values(DStore.definitions); 271 | var getters = {}; 272 | var moduleState = {}; 273 | vue.set(store.state, namespace, {}); // init state 274 | getters[namespace] = function (state) { 275 | return state[namespace]; 276 | }; // set global getter 277 | 278 | ressources.forEach(function (_ref2) { 279 | var ressourceName = _ref2.class; 280 | 281 | var key = "" + namespace + ressourceName; 282 | getters[key] = function (state) { 283 | return state[ressourceName]; 284 | }; 285 | vue.set(moduleState, ressourceName, {}); 286 | }); 287 | 288 | var module = { 289 | state: moduleState, // init ressource state 290 | getters: getters, 291 | mutations: (_mutations = {}, defineProperty(_mutations, MUTATION, function (state, _ref3) { 292 | var type = _ref3.type, 293 | data = _ref3.data; 294 | var id = data.id; 295 | 296 | var namespace = state[type]; 297 | vue.set(namespace, id, Object.assign(JSON.parse(JSON.stringify(data)))); // assign to trigger reactivity 298 | }), defineProperty(_mutations, MUTATION_DELETE, function (state, _ref4) { 299 | var type = _ref4.type, 300 | data = _ref4.data; 301 | var id = data.id; 302 | 303 | var namespace = state[type]; 304 | vue.delete(namespace, id); // assign to trigger reactivity 305 | }), _mutations) 306 | }; 307 | store.registerModule('DS', module); 308 | 309 | function commitRefresh(res, data) { 310 | var commit = function commit(instance) { 311 | // set(instance, '__refresh', !instance.__refresh) 312 | store.commit(MUTATION, { 313 | type: res.class, 314 | data: instance 315 | }, { silent: silent }); 316 | }; 317 | if (Array.isArray(data)) data.forEach(commit);else commit(data); 318 | } 319 | function commitDelete(res, data) { 320 | var commit = function commit(instance) { 321 | store.commit(MUTATION_DELETE, { 322 | type: res.class, 323 | data: instance 324 | }, { silent: silent }); 325 | }; 326 | if (Array.isArray(data)) data.forEach(commit);else commit(data); 327 | } 328 | 329 | ressources.forEach(function (ressource) { 330 | ressource.on('Refresh', function (res, id) { 331 | var data = res.get(id); 332 | commitRefresh(res, data); 333 | }); 334 | // ressource.on('DS.change', (res, data) => commitRefresh(res, data)) 335 | ressource.on('DS.afterDestroy', function (res, data) { 336 | res.off('DS.change'); 337 | commitDelete(res, data); 338 | setTimeout(function () { 339 | // FIXME 340 | res.on('DS.change', function (res, data) { 341 | commitRefresh(res, data); 342 | }); 343 | }, 100); 344 | }); 345 | var refreshCb = function refreshCb(res, data) { 346 | return commitRefresh(res, data); 347 | }; 348 | ressource.on('DS.afterInject', function handler(res, data) { 349 | refreshCb(res, data); 350 | // ressource.off('DS.afterInject', handler) 351 | // ressource.on('DS.change', refreshCb) 352 | }); 353 | }); 354 | }; 355 | } 356 | 357 | function mapRessources() { 358 | var ressources = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 359 | 360 | function generateGetter(name, key) { 361 | return function getter() { 362 | var id = get(this, key); 363 | if (id === null || id === undefined || !this.$store.state.DS[name][id]) { 364 | console.warn('no ressource with id:' + id); 365 | return undefined; 366 | } // !IMPORTANT trigger reactivity 367 | return DStore.get(name, id); 368 | }; 369 | } 370 | var ressourceGetters = ressources.reduce(function (sum, ressource) { 371 | var getterName = Object.keys(ressource)[0]; 372 | ressource[getterName] = generateGetter.apply(undefined, toConsumableArray(ressource[getterName])); 373 | return Object.assign(sum, ressource); 374 | }, {}); 375 | return ressourceGetters; 376 | } 377 | 378 | exports['default'] = index; 379 | exports.mapRessources = mapRessources; 380 | 381 | Object.defineProperty(exports, '__esModule', { value: true }); 382 | 383 | }))); 384 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |{{comments}}45 | 46 | Users: 47 |
{{users}}48 | 49 | Specific User: 50 |
{{user}}51 | 52 |
Please, open your DevTool
53 |