diff --git a/CHANGELOG.md b/CHANGELOG.md index 17017fe..1e8ffc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +##### 2.0.0 - 02 July 2015 + +Stable Version 2.0.0 + +##### 2.0.0-rc.1 - 27 June 2015 + +Stable Version 2.0.0-rc.1 + +##### 2.0.0-beta.1 - 18 April 2015 + +###### Backwards compatible bug fixes +- #11 - Race condition, tasks need to be atomic + +###### Other +- Updated dependencies + ##### 1.1.2 - 27 May 2015 ###### Backwards compatible bug fixes diff --git a/README.md b/README.md index a3e9f8c..b279ad4 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ store.registerAdapter('firebase', adapter, { default: true }); [CHANGELOG.md](https://github.com/js-data/js-data-firebase/blob/master/CHANGELOG.md) ### Community +- [Gitter Channel](https://gitter.im/js-data/js-data) - Better than IRC! +- [Announcements](http://www.js-data.io/blog) - [Mailing List](https://groups.io/org/groupsio/jsdata) - Ask your questions! - [Issues](https://github.com/js-data/js-data-firebase/issues) - Found a bug? Feature request? Submit an issue! - [GitHub](https://github.com/js-data/js-data-firebase) - View the source code for js-data. diff --git a/bower.json b/bower.json index 7da5430..aaa797e 100644 --- a/bower.json +++ b/bower.json @@ -29,7 +29,7 @@ "karma.start.js" ], "dependencies": { - "js-data": ">=1.5.7", + "js-data": ">=2.0.0", "firebase": ">=1.1.x" } } diff --git a/dist/js-data-firebase.js b/dist/js-data-firebase.js index 360e625..c03da66 100644 --- a/dist/js-data-firebase.js +++ b/dist/js-data-firebase.js @@ -1,6 +1,6 @@ /*! * js-data-firebase - * @version 1.1.2 - Homepage + * @version 2.0.0 - Homepage * @author Jason Dobry * @copyright (c) 2014-2015 Jason Dobry * @license MIT @@ -63,37 +63,19 @@ return /******/ (function(modules) { // webpackBootstrap /* 0 */ /***/ function(module, exports, __webpack_require__) { - var _interopRequireWildcard = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }; - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }; - - Object.defineProperty(exports, '__esModule', { - value: true - }); - - var _JSData = __webpack_require__(1); - - var _JSData2 = _interopRequireWildcard(_JSData); - - var _Firebase = __webpack_require__(2); - - var _Firebase2 = _interopRequireWildcard(_Firebase); - - var _omit = __webpack_require__(3); + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - var _omit2 = _interopRequireWildcard(_omit); + var JSData = __webpack_require__(1); + var Firebase = __webpack_require__(2); + var values = __webpack_require__(3); - var _values = __webpack_require__(4); - - var _values2 = _interopRequireWildcard(_values); - - var emptyStore = new _JSData2['default'].DS(); - var DSUtils = _JSData2['default'].DSUtils; + var emptyStore = new JSData.DS(); + var DSUtils = JSData.DSUtils; + var omit = DSUtils.omit; var deepMixIn = DSUtils.deepMixIn; var removeCircular = DSUtils.removeCircular; - var P = DSUtils.Promise; var forOwn = DSUtils.forOwn; var filter = emptyStore.defaults.defaultFilter; @@ -104,6 +86,43 @@ return /******/ (function(modules) { // webpackBootstrap Defaults.prototype.basePath = ''; + var queue = []; + var taskInProcess = false; + + function enqueue(task) { + queue.push(task); + } + + function dequeue() { + if (queue.length && !taskInProcess) { + taskInProcess = true; + queue[0](); + } + } + + function queueTask(task) { + if (!queue.length) { + enqueue(task); + dequeue(); + } else { + enqueue(task); + } + } + + function createTask(fn) { + return new DSUtils.Promise(fn).then(function (result) { + taskInProcess = false; + queue.shift(); + setTimeout(dequeue, 0); + return result; + }, function (err) { + taskInProcess = false; + queue.shift(); + setTimeout(dequeue, 0); + return DSUtils.Promise.reject(err); + }); + } + var DSFirebaseAdapter = (function () { function DSFirebaseAdapter(options) { _classCallCheck(this, DSFirebaseAdapter); @@ -111,7 +130,7 @@ return /******/ (function(modules) { // webpackBootstrap options = options || {}; this.defaults = new Defaults(); deepMixIn(this.defaults, options); - this.ref = new _Firebase2['default'](options.basePath || this.defaults.basePath); + this.ref = new Firebase(options.basePath || this.defaults.basePath); } _createClass(DSFirebaseAdapter, [{ @@ -125,16 +144,18 @@ return /******/ (function(modules) { // webpackBootstrap value: function find(resourceConfig, id, options) { var _this = this; - return new P(function (resolve, reject) { - return _this.getRef(resourceConfig, options).child(id).once('value', function (dataSnapshot) { - var item = dataSnapshot.val(); - if (!item) { - reject(new Error('Not Found!')); - } else { - item[resourceConfig.idAttribute] = item[resourceConfig.idAttribute] || id; - resolve(item); - } - }, reject, _this); + return createTask(function (resolve, reject) { + queueTask(function () { + _this.getRef(resourceConfig, options).child(id).once('value', function (dataSnapshot) { + var item = dataSnapshot.val(); + if (!item) { + reject(new Error('Not Found!')); + } else { + item[resourceConfig.idAttribute] = item[resourceConfig.idAttribute] || id; + resolve(item); + } + }, reject, _this); + }); }); } }, { @@ -142,16 +163,18 @@ return /******/ (function(modules) { // webpackBootstrap value: function findAll(resourceConfig, params, options) { var _this2 = this; - return new P(function (resolve, reject) { - return _this2.getRef(resourceConfig, options).once('value', function (dataSnapshot) { - var data = dataSnapshot.val(); - forOwn(data, function (value, key) { - if (!value[resourceConfig.idAttribute]) { - value[resourceConfig.idAttribute] = '/' + key; - } - }); - resolve(filter.call(emptyStore, _values2['default'](data), resourceConfig.name, params, options)); - }, reject, _this2); + return createTask(function (resolve, reject) { + queueTask(function () { + _this2.getRef(resourceConfig, options).once('value', function (dataSnapshot) { + var data = dataSnapshot.val(); + forOwn(data, function (value, key) { + if (!value[resourceConfig.idAttribute]) { + value[resourceConfig.idAttribute] = '/' + key; + } + }); + resolve(filter.call(emptyStore, values(data), resourceConfig.name, params, options)); + }, reject, _this2); + }); }); } }, { @@ -163,27 +186,29 @@ return /******/ (function(modules) { // webpackBootstrap if (DSUtils.isString(id) || DSUtils.isNumber(id)) { return this.update(resourceConfig, id, attrs, options); } else { - return new P(function (resolve, reject) { - var resourceRef = _this3.getRef(resourceConfig, options); - var itemRef = resourceRef.push(removeCircular(_omit2['default'](attrs, resourceConfig.relationFields || [])), function (err) { - if (err) { - return reject(err); - } else { - var _id = itemRef.toString().replace(resourceRef.toString(), ''); - itemRef.child(resourceConfig.idAttribute).set(_id, function (err) { - if (err) { - reject(err); - } else { - itemRef.once('value', function (dataSnapshot) { - try { - resolve(dataSnapshot.val()); - } catch (err) { - reject(err); - } - }, reject, _this3); - } - }); - } + return createTask(function (resolve, reject) { + queueTask(function () { + var resourceRef = _this3.getRef(resourceConfig, options); + var itemRef = resourceRef.push(removeCircular(omit(attrs, resourceConfig.relationFields || [])), function (err) { + if (err) { + return reject(err); + } else { + var _id = itemRef.toString().replace(resourceRef.toString(), ''); + itemRef.child(resourceConfig.idAttribute).set(_id, function (err) { + if (err) { + reject(err); + } else { + itemRef.once('value', function (dataSnapshot) { + try { + resolve(dataSnapshot.val()); + } catch (err) { + reject(err); + } + }, reject, _this3); + } + }); + } + }); }); }); } @@ -193,46 +218,48 @@ return /******/ (function(modules) { // webpackBootstrap value: function update(resourceConfig, id, attrs, options) { var _this4 = this; - attrs = removeCircular(_omit2['default'](attrs || {}, resourceConfig.relationFields || [])); - return new P(function (resolve, reject) { - var itemRef = _this4.getRef(resourceConfig, options).child(id); - itemRef.once('value', function (dataSnapshot) { - try { - (function () { - var item = dataSnapshot.val() || {}; - var fields = undefined, - removed = undefined, - i = undefined; - if (resourceConfig.relations) { - fields = resourceConfig.relationFields; - removed = []; - for (i = 0; i < fields.length; i++) { - removed.push(attrs[fields[i]]); - delete attrs[fields[i]]; - } - } - deepMixIn(item, attrs); - if (resourceConfig.relations) { - fields = resourceConfig.relationFields; - for (i = 0; i < fields.length; i++) { - var toAddBack = removed.shift(); - if (toAddBack) { - attrs[fields[i]] = toAddBack; + return createTask(function (resolve, reject) { + queueTask(function () { + attrs = removeCircular(omit(attrs || {}, resourceConfig.relationFields || [])); + var itemRef = _this4.getRef(resourceConfig, options).child(id); + itemRef.once('value', function (dataSnapshot) { + try { + (function () { + var item = dataSnapshot.val() || {}; + var fields = undefined, + removed = undefined, + i = undefined; + if (resourceConfig.relations) { + fields = resourceConfig.relationFields; + removed = []; + for (i = 0; i < fields.length; i++) { + removed.push(attrs[fields[i]]); + delete attrs[fields[i]]; } } - } - itemRef.set(item, function (err) { - if (err) { - reject(err); - } else { - resolve(item); + deepMixIn(item, attrs); + if (resourceConfig.relations) { + fields = resourceConfig.relationFields; + for (i = 0; i < fields.length; i++) { + var toAddBack = removed.shift(); + if (toAddBack) { + attrs[fields[i]] = toAddBack; + } + } } - }); - })(); - } catch (err) { - reject(err); - } - }, reject, _this4); + itemRef.set(item, function (err) { + if (err) { + reject(err); + } else { + resolve(item); + } + }); + })(); + } catch (err) { + reject(err); + } + }, reject, _this4); + }); }); } }, { @@ -245,7 +272,7 @@ return /******/ (function(modules) { // webpackBootstrap DSUtils.forEach(items, function (item) { tasks.push(_this5.update(resourceConfig, item[resourceConfig.idAttribute], attrs, options)); }); - return P.all(tasks); + return DSUtils.Promise.all(tasks); }); } }, { @@ -253,13 +280,15 @@ return /******/ (function(modules) { // webpackBootstrap value: function destroy(resourceConfig, id, options) { var _this6 = this; - return new P(function (resolve, reject) { - _this6.getRef(resourceConfig, options).child(id).remove(function (err) { - if (err) { - reject(err); - } else { - resolve(); - } + return createTask(function (resolve, reject) { + queueTask(function () { + _this6.getRef(resourceConfig, options).child(id).remove(function (err) { + if (err) { + reject(err); + } else { + resolve(); + } + }); }); }); } @@ -273,7 +302,7 @@ return /******/ (function(modules) { // webpackBootstrap DSUtils.forEach(items, function (item) { tasks.push(_this7.destroy(resourceConfig, item[resourceConfig.idAttribute], options)); }); - return P.all(tasks); + return DSUtils.Promise.all(tasks); }); } }]); @@ -281,8 +310,7 @@ return /******/ (function(modules) { // webpackBootstrap return DSFirebaseAdapter; })(); - exports['default'] = DSFirebaseAdapter; - module.exports = exports['default']; + module.exports = DSFirebaseAdapter; /***/ }, /* 1 */ @@ -300,34 +328,7 @@ return /******/ (function(modules) { // webpackBootstrap /* 3 */ /***/ function(module, exports, __webpack_require__) { - var slice = __webpack_require__(5); - var contains = __webpack_require__(6); - - /** - * Return a copy of the object, filtered to only contain properties except the blacklisted keys. - */ - function omit(obj, var_keys){ - var keys = typeof arguments[1] !== 'string'? arguments[1] : slice(arguments, 1), - out = {}; - - for (var property in obj) { - if (obj.hasOwnProperty(property) && !contains(keys, property)) { - out[property] = obj[property]; - } - } - return out; - } - - module.exports = omit; - - - - -/***/ }, -/* 4 */ -/***/ function(module, exports, __webpack_require__) { - - var forOwn = __webpack_require__(7); + var forOwn = __webpack_require__(4); /** * Get object values @@ -346,68 +347,11 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 5 */ -/***/ function(module, exports, __webpack_require__) { - - - - /** - * Create slice of source array or array-like object - */ - function slice(arr, start, end){ - var len = arr.length; - - if (start == null) { - start = 0; - } else if (start < 0) { - start = Math.max(len + start, 0); - } else { - start = Math.min(start, len); - } - - if (end == null) { - end = len; - } else if (end < 0) { - end = Math.max(len + end, 0); - } else { - end = Math.min(end, len); - } - - var result = []; - while (start < end) { - result.push(arr[start++]); - } - - return result; - } - - module.exports = slice; - - - - -/***/ }, -/* 6 */ -/***/ function(module, exports, __webpack_require__) { - - var indexOf = __webpack_require__(10); - - /** - * If array contains values. - */ - function contains(arr, val) { - return indexOf(arr, val) !== -1; - } - module.exports = contains; - - - -/***/ }, -/* 7 */ +/* 4 */ /***/ function(module, exports, __webpack_require__) { - var hasOwn = __webpack_require__(8); - var forIn = __webpack_require__(9); + var hasOwn = __webpack_require__(5); + var forIn = __webpack_require__(6); /** * Similar to Array/forEach but works over object properties and fixes Don't @@ -428,7 +372,7 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 8 */ +/* 5 */ /***/ function(module, exports, __webpack_require__) { @@ -446,10 +390,10 @@ return /******/ (function(modules) { // webpackBootstrap /***/ }, -/* 9 */ +/* 6 */ /***/ function(module, exports, __webpack_require__) { - var hasOwn = __webpack_require__(8); + var hasOwn = __webpack_require__(5); var _hasDontEnumBug, _dontEnums; @@ -527,40 +471,6 @@ return /******/ (function(modules) { // webpackBootstrap -/***/ }, -/* 10 */ -/***/ function(module, exports, __webpack_require__) { - - - - /** - * Array.indexOf - */ - function indexOf(arr, item, fromIndex) { - fromIndex = fromIndex || 0; - if (arr == null) { - return -1; - } - - var len = arr.length, - i = fromIndex < 0 ? len + fromIndex : fromIndex; - while (i < len) { - // we iterate over sparse items since there is no way to make it - // work properly on IE 7-8. see #64 - if (arr[i] === item) { - return i; - } - - i++; - } - - return -1; - } - - module.exports = indexOf; - - - /***/ } /******/ ]) }); diff --git a/dist/js-data-firebase.min.js b/dist/js-data-firebase.min.js index e7911aa..d4144f3 100644 --- a/dist/js-data-firebase.min.js +++ b/dist/js-data-firebase.min.js @@ -1,6 +1,6 @@ /*! * js-data-firebase -* @version 1.1.2 - Homepage +* @version 2.0.0 - Homepage * @author Jason Dobry * @copyright (c) 2014-2015 Jason Dobry * @license MIT @@ -8,5 +8,5 @@ * @overview Firebase adapter for js-data. */ -!function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b(require("js-data"),require("firebase")):"function"==typeof define&&define.amd?define(["js-data","firebase"],b):"object"==typeof exports?exports.DSFirebaseAdapter=b(require("js-data"),require("firebase")):a.DSFirebaseAdapter=b(a.JSData,a.Firebase)}(this,function(a,b){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){var d=function(a){return a&&a.__esModule?a:{"default":a}},e=function(){function a(a,b){for(var c=0;cb?Math.max(d+b,0):Math.min(b,d),c=null==c?d:0>c?Math.max(d+c,0):Math.min(c,d);for(var e=[];c>b;)e.push(a[b++]);return e}a.exports=d},function(a,b,c){function d(a,b){return-1!==e(a,b)}var e=c(10);a.exports=d},function(a,b,c){function d(a,b,c){f(a,function(d,f){return e(a,f)?b.call(c,a[f],f,a):void 0})}var e=c(8),f=c(9);a.exports=d},function(a,b,c){function d(a,b){return Object.prototype.hasOwnProperty.call(a,b)}a.exports=d},function(a,b,c){function d(){h=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],g=!0;for(var a in{toString:null})g=!1}function e(a,b,c){var e,j=0;null==g&&d();for(e in a)if(f(b,a,e,c)===!1)break;if(g)for(var k=a.constructor,l=!!k&&a===k.prototype;(e=h[j++])&&("constructor"===e&&(l||!i(a,e))||a[e]===Object.prototype[e]||f(b,a,e,c)!==!1););}function f(a,b,c,d){return a.call(d,b[c],c,b)}var g,h,i=c(8);a.exports=e},function(a,b,c){function d(a,b,c){if(c=c||0,null==a)return-1;for(var d=a.length,e=0>c?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}a.exports=d}])}); +!function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b(require("js-data"),require("firebase")):"function"==typeof define&&define.amd?define(["js-data","firebase"],b):"object"==typeof exports?exports.DSFirebaseAdapter=b(require("js-data"),require("firebase")):a.DSFirebaseAdapter=b(a.JSData,a.Firebase)}(this,function(a,b){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){function d(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function e(a){u.push(a)}function f(){u.length&&!v&&(v=!0,u[0]())}function g(a){u.length?e(a):(e(a),f())}function h(a){return new n.Promise(a).then(function(a){return v=!1,u.shift(),setTimeout(f,0),a},function(a){return v=!1,u.shift(),setTimeout(f,0),n.Promise.reject(a)})}var i=function(){function a(a,b){for(var c=0;c - + - + src="https://github.com/js-data/js-data-firebase/releases/download/2.0.0-rc.1/js-data-firebase.min.js"> + @@ -64,7 +64,7 @@

Users

var basePath = 'https://js-data-firebase.firebaseio.com'; DSFirebaseAdapterProvider.defaults.basePath = basePath; }) - .run(function (DS, DSFirebaseAdapter) { + .run(function (DS, DSFirebaseAdapter, User) { // js-data-angular created a new store // automatically and registered it as DS. @@ -75,6 +75,29 @@

Users

DSFirebaseAdapter, { default: true } ); + + // Activate a mostly auto-sync with Firebase + // The only thing missing is auto-sync TO Firebase + // This will be easier with js-data 2.x, but right + // now you still have to do DS.update('user', 1, { foo: 'bar' }), etc. + angular.forEach(DS.definitions, function (Resource) { + var ref = DSFirebaseAdapter.ref.child(Resource.endpoint); + // Inject items into the store when they're added to Firebase + // Update items in the store when they're modified in Firebase + ref.on('child_changed', function (dataSnapshot) { + var data = dataSnapshot.val(); + if (data[Resource.idAttribute]) { + Resource.inject(data); + } + }); + // Eject items from the store when they're removed from Firebase + ref.on('child_removed', function (dataSnapshot) { + var data = dataSnapshot.val(); + if (data[Resource.idAttribute]) { + Resource.eject(data[Resource.idAttribute]); + } + }); + }); }) .factory('User', function (DS) { return DS.defineResource('user'); diff --git a/examples/react/example.js b/examples/react/example.js index c08ee08..5acdcaa 100644 --- a/examples/react/example.js +++ b/examples/react/example.js @@ -1,12 +1,9 @@ var store = new JSData.DS(); +var firebaseAdapter = new DSFirebaseAdapter({ + basePath: 'https://js-data-firebase.firebaseio.com' +}); -store.registerAdapter( - 'firebase', - new DSFirebaseAdapter({ - basePath: 'https://js-data-firebase.firebaseio.com' - }), - { default: true } -); +store.registerAdapter('firebase', firebaseAdapter, { default: true }); // Flux pattern var UserStore = store.defineResource({ @@ -19,6 +16,30 @@ var UserStore = store.defineResource({ } }); +// Activate a mostly auto-sync with Firebase +// The only thing missing is auto-sync TO Firebase +// This will be easier with js-data 2.x, but right +// now you still have to do store.update('user', 1, { foo: 'bar' }), etc. +for (var resourceName in store.definitions) { + var Resource = store.definitions[resourceName]; + var ref = firebaseAdapter.ref.child(Resource.endpoint); + // Inject items into the store when they're added to Firebase + // Update items in the store when they're modified in Firebase + ref.on('child_changed', function (dataSnapshot) { + var data = dataSnapshot.val(); + if (data[Resource.idAttribute]) { + Resource.inject(data); + } + }); + // Eject items from the store when they're removed from Firebase + ref.on('child_removed', function (dataSnapshot) { + var data = dataSnapshot.val(); + if (data[Resource.idAttribute]) { + Resource.eject(data[Resource.idAttribute]); + } + }); +}; + var UserItem = React.createClass({ remove: function () { UserStore.destroy(this.props.user.id); @@ -57,9 +78,12 @@ var UserApp = React.createClass({ UserStore.off('change', this.onChange); }, createUser: function (e) { + var _this = this; e.preventDefault(); UserStore.create({ - name: this.state.name + name: _this.state.name + }).then(function () { + _this.setState({ name: '' }); }); }, render: function () { diff --git a/examples/react/index.html b/examples/react/index.html index 6c99536..0d972b7 100644 --- a/examples/react/index.html +++ b/examples/react/index.html @@ -10,9 +10,8 @@ - - + + @@ -38,14 +37,11 @@

   var store = new JSData.DS();
+  var firebaseAdapter = new DSFirebaseAdapter({
+    basePath: 'https://js-data-firebase.firebaseio.com'
+  });
 
-  store.registerAdapter(
-    'firebase',
-    new DSFirebaseAdapter({
-      basePath: 'https://js-data-firebase.firebaseio.com'
-    }),
-    { default: true }
-  );
+  store.registerAdapter('firebase', firebaseAdapter, { default: true });
 
   // Flux pattern
   var UserStore = store.defineResource({
@@ -58,6 +54,30 @@ 
     }
   });
 
+  // Activate a mostly auto-sync with Firebase
+  // The only thing missing is auto-sync TO Firebase
+  // This will be easier with js-data 2.x, but right
+  // now you still have to do store.update('user', 1, { foo: 'bar' }), etc.
+  for (var resourceName in store.definitions) {
+    var Resource = store.definitions[resourceName];
+    var ref = firebaseAdapter.ref.child(Resource.endpoint);
+    // Inject items into the store when they're added to Firebase
+    // Update items in the store when they're modified in Firebase
+    ref.on('child_changed', function (dataSnapshot) {
+      var data = dataSnapshot.val();
+      if (data[Resource.idAttribute]) {
+        Resource.inject(data);
+      }
+    });
+    // Eject items from the store when they're removed from Firebase
+    ref.on('child_removed', function (dataSnapshot) {
+      var data = dataSnapshot.val();
+      if (data[Resource.idAttribute]) {
+        Resource.eject(data[Resource.idAttribute]);
+      }
+    });
+  });
+
   var UserItem = React.createClass({
     remove: function () {
       UserStore.destroy(this.props.user.id);
@@ -96,11 +116,13 @@ 
       UserStore.off('change', this.onChange);
     },
     createUser: function (e) {
+      var _this = this;
       e.preventDefault();
       UserStore.create({
-        name: this.state.name
+        name: _this.state.name
+      }).then(function () {
+        _this.setState({ name: '' });
       });
-      this.setState({ users: UserStore.getAll(), name: '' });
     },
     render: function () {
       var users = this.state.users;
diff --git a/package.json b/package.json
index c93aa84..190f536 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "js-data-firebase",
   "description": "Firebase adapter for js-data.",
-  "version": "1.1.2",
+  "version": "2.0.0",
   "homepage": "http://www.js-data.io/docs/dsfirebaseadapter",
   "repository": {
     "type": "git",
@@ -28,31 +28,30 @@
   ],
   "main": "./dist/js-data-firebase.js",
   "devDependencies": {
-    "babel-core": "4.7.16",
-    "babel-loader": "4.2.0",
+    "babel-core": "5.6.15",
+    "babel-loader": "5.2.2",
     "grunt": "0.4.5",
     "grunt-contrib-clean": "0.6.0",
-    "grunt-contrib-uglify": "0.8.0",
+    "grunt-contrib-uglify": "0.9.1",
     "grunt-contrib-watch": "0.6.1",
-    "grunt-karma": "0.10.1",
+    "grunt-karma": "0.11.2",
     "grunt-karma-coveralls": "2.5.3",
-    "grunt-webpack": "1.0.8",
+    "grunt-webpack": "1.0.11",
     "jit-grunt": "0.9.1",
-    "jshint": "2.6.3",
+    "jshint": "2.8.0",
     "jshint-loader": "0.8.3",
-    "karma": "0.12.31",
+    "karma": "0.12.37",
     "karma-chai": "0.1.0",
-    "karma-chrome-launcher": "0.1.7",
-    "karma-coverage": "0.2.7",
-    "karma-firefox-launcher": "0.1.4",
-    "karma-mocha": "0.1.10",
-    "karma-phantomjs-launcher": "0.1.4",
+    "karma-chrome-launcher": "0.2.0",
+    "karma-coverage": "0.4.2",
+    "karma-firefox-launcher": "0.1.6",
+    "karma-mocha": "0.2.0",
+    "karma-phantomjs-launcher": "0.2.0",
     "karma-script-launcher": "0.1.0",
     "karma-sinon": "1.0.4",
-    "karma-spec-reporter": "0.0.16",
-    "time-grunt": "1.1.0",
-    "webpack": "1.7.3",
-    "webpack-dev-server": "1.7.0"
+    "karma-spec-reporter": "0.0.19",
+    "time-grunt": "1.2.1",
+    "webpack-dev-server": "1.10.1"
   },
   "scripts": {
     "test": "grunt test"
@@ -61,7 +60,7 @@
     "mout": "0.11.0"
   },
   "peerDependencies": {
-    "js-data": ">= 1.5.7",
-    "firebase": ">= 1.1.x"
+    "js-data": ">=2.0.0",
+    "firebase": ">=1.1.x"
   }
 }
diff --git a/src/index.js b/src/index.js
index 121ca8c..d2663f8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,11 +1,10 @@
-import JSData from 'js-data';
-import Firebase from 'firebase';
-import omit from 'mout/object/omit';
-import values from 'mout/object/values';
+let JSData = require('js-data');
+let Firebase = require('firebase');
+let values = require('mout/object/values');
 
 let emptyStore = new JSData.DS();
 let DSUtils = JSData.DSUtils;
-let { deepMixIn, removeCircular, Promise: P, forOwn  } = DSUtils;
+let { omit, deepMixIn, removeCircular, forOwn  } = DSUtils;
 let filter = emptyStore.defaults.defaultFilter;
 
 class Defaults {
@@ -14,6 +13,43 @@ class Defaults {
 
 Defaults.prototype.basePath = '';
 
+let queue = [];
+let taskInProcess = false;
+
+function enqueue(task) {
+  queue.push(task);
+}
+
+function dequeue() {
+  if (queue.length && !taskInProcess) {
+    taskInProcess = true;
+    queue[0]();
+  }
+}
+
+function queueTask(task) {
+  if (!queue.length) {
+    enqueue(task);
+    dequeue();
+  } else {
+    enqueue(task);
+  }
+}
+
+function createTask(fn) {
+  return new DSUtils.Promise(fn).then(result => {
+    taskInProcess = false;
+    queue.shift();
+    setTimeout(dequeue, 0);
+    return result;
+  }, err => {
+    taskInProcess = false;
+    queue.shift();
+    setTimeout(dequeue, 0);
+    return DSUtils.Promise.reject(err);
+  });
+}
+
 class DSFirebaseAdapter {
   constructor(options) {
     options = options || {};
@@ -28,30 +64,34 @@ class DSFirebaseAdapter {
   }
 
   find(resourceConfig, id, options) {
-    return new P((resolve, reject) => {
-      return this.getRef(resourceConfig, options).child(id).once('value', dataSnapshot => {
-        let item = dataSnapshot.val();
-        if (!item) {
-          reject(new Error('Not Found!'));
-        } else {
-          item[resourceConfig.idAttribute] = item[resourceConfig.idAttribute] || id;
-          resolve(item);
-        }
-      }, reject, this);
+    return createTask((resolve, reject) => {
+      queueTask(() => {
+        this.getRef(resourceConfig, options).child(id).once('value', dataSnapshot => {
+          let item = dataSnapshot.val();
+          if (!item) {
+            reject(new Error('Not Found!'));
+          } else {
+            item[resourceConfig.idAttribute] = item[resourceConfig.idAttribute] || id;
+            resolve(item);
+          }
+        }, reject, this);
+      });
     });
   }
 
   findAll(resourceConfig, params, options) {
-    return new P((resolve, reject) => {
-      return this.getRef(resourceConfig, options).once('value', dataSnapshot => {
-        let data = dataSnapshot.val();
-        forOwn(data, (value, key) => {
-          if (!value[resourceConfig.idAttribute]) {
-            value[resourceConfig.idAttribute] = `/${key}`;
-          }
-        });
-        resolve(filter.call(emptyStore, values(data), resourceConfig.name, params, options));
-      }, reject, this);
+    return createTask((resolve, reject) => {
+      queueTask(() => {
+        this.getRef(resourceConfig, options).once('value', dataSnapshot => {
+          let data = dataSnapshot.val();
+          forOwn(data, (value, key) => {
+            if (!value[resourceConfig.idAttribute]) {
+              value[resourceConfig.idAttribute] = `/${key}`;
+            }
+          });
+          resolve(filter.call(emptyStore, values(data), resourceConfig.name, params, options));
+        }, reject, this);
+      });
     });
   }
 
@@ -60,69 +100,73 @@ class DSFirebaseAdapter {
     if (DSUtils.isString(id) || DSUtils.isNumber(id)) {
       return this.update(resourceConfig, id, attrs, options);
     } else {
-      return new P((resolve, reject) => {
-        let resourceRef = this.getRef(resourceConfig, options);
-        let itemRef = resourceRef.push(removeCircular(omit(attrs, resourceConfig.relationFields || [])), err => {
-          if (err) {
-            return reject(err);
-          } else {
-            let id = itemRef.toString().replace(resourceRef.toString(), '');
-            itemRef.child(resourceConfig.idAttribute).set(id, err => {
-              if (err) {
-                reject(err);
-              } else {
-                itemRef.once('value', dataSnapshot => {
-                  try {
-                    resolve(dataSnapshot.val());
-                  } catch (err) {
-                    reject(err);
-                  }
-                }, reject, this);
-              }
-            });
-          }
+      return createTask((resolve, reject) => {
+        queueTask(() => {
+          let resourceRef = this.getRef(resourceConfig, options);
+          var itemRef = resourceRef.push(removeCircular(omit(attrs, resourceConfig.relationFields || [])), err => {
+            if (err) {
+              return reject(err);
+            } else {
+              let id = itemRef.toString().replace(resourceRef.toString(), '');
+              itemRef.child(resourceConfig.idAttribute).set(id, err => {
+                if (err) {
+                  reject(err);
+                } else {
+                  itemRef.once('value', dataSnapshot => {
+                    try {
+                      resolve(dataSnapshot.val());
+                    } catch (err) {
+                      reject(err);
+                    }
+                  }, reject, this);
+                }
+              });
+            }
+          });
         });
       });
     }
   }
 
   update(resourceConfig, id, attrs, options) {
-    attrs = removeCircular(omit(attrs || {}, resourceConfig.relationFields || []));
-    return new P((resolve, reject) => {
-      let itemRef = this.getRef(resourceConfig, options).child(id);
-      itemRef.once('value', dataSnapshot => {
-        try {
-          let item = dataSnapshot.val() || {};
-          let fields, removed, i;
-          if (resourceConfig.relations) {
-            fields = resourceConfig.relationFields;
-            removed = [];
-            for (i = 0; i < fields.length; i++) {
-              removed.push(attrs[fields[i]]);
-              delete attrs[fields[i]];
+    return createTask((resolve, reject) => {
+      queueTask(() => {
+        attrs = removeCircular(omit(attrs || {}, resourceConfig.relationFields || []));
+        let itemRef = this.getRef(resourceConfig, options).child(id);
+        itemRef.once('value', dataSnapshot => {
+          try {
+            let item = dataSnapshot.val() || {};
+            let fields, removed, i;
+            if (resourceConfig.relations) {
+              fields = resourceConfig.relationFields;
+              removed = [];
+              for (i = 0; i < fields.length; i++) {
+                removed.push(attrs[fields[i]]);
+                delete attrs[fields[i]];
+              }
             }
-          }
-          deepMixIn(item, attrs);
-          if (resourceConfig.relations) {
-            fields = resourceConfig.relationFields;
-            for (i = 0; i < fields.length; i++) {
-              let toAddBack = removed.shift();
-              if (toAddBack) {
-                attrs[fields[i]] = toAddBack;
+            deepMixIn(item, attrs);
+            if (resourceConfig.relations) {
+              fields = resourceConfig.relationFields;
+              for (i = 0; i < fields.length; i++) {
+                let toAddBack = removed.shift();
+                if (toAddBack) {
+                  attrs[fields[i]] = toAddBack;
+                }
               }
             }
+            itemRef.set(item, err => {
+              if (err) {
+                reject(err);
+              } else {
+                resolve(item);
+              }
+            });
+          } catch (err) {
+            reject(err);
           }
-          itemRef.set(item, err => {
-            if (err) {
-              reject(err);
-            } else {
-              resolve(item);
-            }
-          });
-        } catch (err) {
-          reject(err);
-        }
-      }, reject, this);
+        }, reject, this);
+      });
     });
   }
 
@@ -132,18 +176,20 @@ class DSFirebaseAdapter {
       DSUtils.forEach(items, item => {
         tasks.push(this.update(resourceConfig, item[resourceConfig.idAttribute], attrs, options));
       });
-      return P.all(tasks);
+      return DSUtils.Promise.all(tasks);
     });
   }
 
   destroy(resourceConfig, id, options) {
-    return new P((resolve, reject) => {
-      this.getRef(resourceConfig, options).child(id).remove(err => {
-        if (err) {
-          reject(err);
-        } else {
-          resolve();
-        }
+    return createTask((resolve, reject) => {
+      queueTask(() => {
+        this.getRef(resourceConfig, options).child(id).remove(err => {
+          if (err) {
+            reject(err);
+          } else {
+            resolve();
+          }
+        });
       });
     });
   }
@@ -154,9 +200,9 @@ class DSFirebaseAdapter {
       DSUtils.forEach(items, item => {
         tasks.push(this.destroy(resourceConfig, item[resourceConfig.idAttribute], options));
       });
-      return P.all(tasks);
+      return DSUtils.Promise.all(tasks);
     });
   }
 }
 
-export default DSFirebaseAdapter;
+module.exports = DSFirebaseAdapter;