├── .babelrc
├── .gitignore
├── .npmignore
├── README.md
├── lib
└── umd
│ ├── meteor-immutable-observer.js
│ └── meteor-immutable-observer.min.js
├── meteor
├── .meteor
│ ├── .finished-upgraders
│ ├── .gitignore
│ ├── .id
│ ├── packages
│ ├── platforms
│ ├── release
│ └── versions
├── meteor.html
├── packages
│ └── immutable-observer
│ │ ├── README.md
│ │ ├── global-meteor-immutable-observer.js
│ │ ├── package.js
│ │ └── tests
│ │ ├── Players.js
│ │ └── client
│ │ └── ImmutableObserverSpec.js
└── test
├── package.json
├── scripts
└── build.sh
├── src
├── ImmutableListObserver.js
├── ImmutableMapObserver.js
├── index.js
├── setGlobalImmutableObserver.js
└── updateDeep.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/*
2 | !lib/umd
3 | /node_modules
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | scripts
3 | __tests__
4 | examples
5 | .babelrc
6 | .eslintrc
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # meteor-immutable-observer
2 |
3 | **(work in progress!)**
4 |
5 | This uses [`Mongo.Cursor.observe`](http://docs.meteor.com/#/full/observe) and `Mongo.Cursor.observeChanges`
6 | to provide [Immutable.js](http://facebook.github.io/immutable-js/) views of the collection and its documents.
7 | This is especially handy to pass to React pure render components; when documents are changed, a custom
8 | `updateDeep` method is used so that objects/arrays inside them that didn't change will still be `===` their
9 | previous values.
10 |
11 | ## Installation
12 |
13 | ### Node/Webpack/Browserify/jspm/HTML9 Responsive Boilerstrap JS
14 | ```
15 | npm install meteor-immutable-observer
16 | ```
17 | Then `var ImmutableObserver = require('meteor-immutable-observer')`
18 |
19 | There are Webpack UMDs in the `lib/umd` folder (they rely on `immutable` being in a commons chunk)
20 |
21 | ### Meteor Package *(not deployed here yet)*
22 | ```
23 | meteor add mindfront:immutable-observer
24 | ```
25 | This will put `ImmutableObserver` in the package scope.
26 |
27 | ## API
28 |
29 | **Note**: this doesn't seem to work properly when you `Meteor.subscribe()` to your collection within the same reactive computation. I haven't yet investigated exactly why. Right now I just `subscribe()` outside of any reactive computation.
30 |
31 | ### `ImmutableObserver.Map(cursor: Mongo.Cursor)`
32 |
33 | Begins a live query via `cursor.observeChanges`, and tracks changes in an `Immutable.Map` of documents indexed by `_id`.
34 |
35 | Theoretically this should perform better than `ImmutableObserver.List`, since it doesn't keep track of document order.
36 |
37 | **This should not be called within a reactive computation.** Since its `observeChanges` can trigger dependency
38 | changes, it could cause an infinite autorun loop.
39 |
40 | *Make sure you `stop()` the observer when done with it.*
41 |
42 | ###### Example:
43 |
44 | ```javascript
45 | var Players = new Meteor.Collection('players');
46 | var observer = ImmutableObserver.Map(Players.find({}, {limit: 10}));
47 | ...
48 | observer.stop();
49 | ```
50 |
51 | #### Methods
52 |
53 | ##### `documents(): Immutable.Map`
54 |
55 | Returns an `Immutable.Map` of the currently available documents, indexed by `_id`.
56 |
57 | Also registers a dependency on the underlying live query.
58 |
59 | ##### `stop()`
60 |
61 | Stops the live query (calls `stop()` on what `observeChanges` returned)
62 |
63 | ### `ImmutableObserver.List(cursor: Mongo.Cursor)`
64 |
65 | Begins a live query via `cursor.observe`, and tracks changes in an `Immutable.List` of documents in order.
66 |
67 | **This should not be called within a reactive computation.** Since its `observe` can trigger dependency
68 | changes, it could cause an infinite autorun loop.
69 |
70 | *Make sure you `stop()` the observer when done with it.*
71 |
72 | ###### Example:
73 |
74 | ```javascript
75 | var Players = new Meteor.Collection('players');
76 | var observer = ImmutableObserver.List(Players.find({}, {sort: {score: 1}, limit: 10}));
77 | ...
78 | observer.stop();
79 | ```
80 |
81 | #### Methods
82 |
83 | ##### `documents(): Immutable.List`
84 |
85 | Returns an `Immutable.List` of the currently available documents.
86 |
87 | Also registers a dependency on the underlying live query.
88 |
89 | ##### `stop()`
90 |
91 | Stops the live query (calls `stop()` on what `observe` returned)
92 |
93 | ## Example (not tested)
94 |
95 | ```jsx
96 | import React from 'react';
97 | import classNames from 'classnames';
98 | import ImmutableObserver from 'meteor-immutable-observer';
99 | import shouldPureComponentUpdate from 'react-pure-render/function';
100 |
101 | class Post extends React.Component {
102 | shouldComponentUpdate = shouldPureComponentUpdate
103 | render() {
104 | var {post} = this.props;
105 | return
106 |
107 |
{post.get('title')}
108 |
109 |
110 |
111 | {post.get('content')}
112 |
113 |
;
114 | }
115 | }
116 |
117 | class PostList extends React.Component {
118 | shouldComponentUpdate = shouldPureComponentUpdate
119 | render() {
120 | var {posts} = this.props;
121 | var postComponents = [];
122 | posts.forEach(post => postComponents.push());
123 | return
124 | {postComponents}
125 |
;
126 | }
127 | }
128 |
129 | export default React.createClass({
130 | mixins: [ReactMeteorData],
131 | componentWillMount() {
132 | this.subscription = Meteor.subscribe('posts');
133 | this.postsObserver = ImmutableObserver.List(Posts.find({}, {sort: {createdDate: 1}}));
134 | }
135 | componentWillUnmount() {
136 | this.subscription.stop();
137 | this.postsObserver.stop();
138 | }
139 | getMeteorData() {
140 | return {
141 | posts: postsObserver.documents(),
142 | };
143 | },
144 | render() {
145 | return ;
146 | }
147 | });
148 | ```
149 |
--------------------------------------------------------------------------------
/lib/umd/meteor-immutable-observer.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory(require("immutable"));
4 | else if(typeof define === 'function' && define.amd)
5 | define(["immutable"], factory);
6 | else if(typeof exports === 'object')
7 | exports["meteor-immutable-observer"] = factory(require("immutable"));
8 | else
9 | root["meteor-immutable-observer"] = factory(root["Immutable"]);
10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_2__) {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 |
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 |
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId])
20 | /******/ return installedModules[moduleId].exports;
21 |
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ exports: {},
25 | /******/ id: moduleId,
26 | /******/ loaded: false
27 | /******/ };
28 |
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 |
32 | /******/ // Flag the module as loaded
33 | /******/ module.loaded = true;
34 |
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 |
39 |
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 |
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 |
46 | /******/ // __webpack_public_path__
47 | /******/ __webpack_require__.p = "";
48 |
49 | /******/ // Load entry module and return exports
50 | /******/ return __webpack_require__(0);
51 | /******/ })
52 | /************************************************************************/
53 | /******/ ([
54 | /* 0 */
55 | /***/ function(module, exports, __webpack_require__) {
56 |
57 | 'use strict';
58 |
59 | exports.__esModule = true;
60 |
61 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
62 |
63 | var _ImmutableMapObserver = __webpack_require__(1);
64 |
65 | var _ImmutableMapObserver2 = _interopRequireDefault(_ImmutableMapObserver);
66 |
67 | var _ImmutableListObserver = __webpack_require__(4);
68 |
69 | var _ImmutableListObserver2 = _interopRequireDefault(_ImmutableListObserver);
70 |
71 | exports['default'] = {
72 | Map: _ImmutableMapObserver2['default'],
73 | List: _ImmutableListObserver2['default']
74 | };
75 | module.exports = exports['default'];
76 |
77 | /***/ },
78 | /* 1 */
79 | /***/ function(module, exports, __webpack_require__) {
80 |
81 | 'use strict';
82 |
83 | exports.__esModule = true;
84 | exports['default'] = ImmutableMapObserver;
85 |
86 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
87 |
88 | var _immutable = __webpack_require__(2);
89 |
90 | var _immutable2 = _interopRequireDefault(_immutable);
91 |
92 | var _updateDeep = __webpack_require__(3);
93 |
94 | var _updateDeep2 = _interopRequireDefault(_updateDeep);
95 |
96 | function mergeChanges(document, fields) {
97 | return document.withMutations(function (document) {
98 | for (var key in fields) {
99 | if (fields.hasOwnProperty(key)) {
100 | var newValue = fields[key];
101 | if (newValue === undefined) {
102 | document['delete'](key);
103 | } else {
104 | document.update(key, function (oldValue) {
105 | return _updateDeep2['default'](oldValue, _immutable2['default'].fromJS(newValue));
106 | });
107 | }
108 | }
109 | }
110 | });
111 | }
112 |
113 | function ImmutableMapObserver(cursor) {
114 | var _documents = undefined;
115 | var dep = new Tracker.Dependency();
116 |
117 | function update(newDocuments) {
118 | _documents = newDocuments;
119 | dep.changed();
120 | }
121 |
122 | var initialDocuments = {};
123 | var handle = cursor.observeChanges({
124 | added: function added(id, fields) {
125 | fields._id = id;
126 | if (initialDocuments) {
127 | initialDocuments[id] = _immutable2['default'].fromJS(fields);
128 | } else {
129 | update(_documents.set(id, _immutable2['default'].fromJS(fields)));
130 | }
131 | },
132 | changed: function changed(id, fields) {
133 | update(_documents.update(id, function (document) {
134 | return mergeChanges(document, fields);
135 | }));
136 | },
137 | removed: function removed(id) {
138 | update(_documents['delete'](id));
139 | }
140 | });
141 | _documents = _immutable2['default'].Map(initialDocuments);
142 | initialDocuments = undefined;
143 |
144 | if (Tracker.active) {
145 | Tracker.onInvalidate(function () {
146 | handle.stop();
147 | });
148 | }
149 |
150 | return {
151 | documents: function documents() {
152 | dep.depend();
153 | return _documents;
154 | },
155 | stop: function stop() {
156 | handle.stop();
157 | }
158 | };
159 | }
160 |
161 | module.exports = exports['default'];
162 |
163 | /***/ },
164 | /* 2 */
165 | /***/ function(module, exports) {
166 |
167 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__;
168 |
169 | /***/ },
170 | /* 3 */
171 | /***/ function(module, exports, __webpack_require__) {
172 |
173 | 'use strict';
174 |
175 | exports.__esModule = true;
176 | exports['default'] = updateDeep;
177 |
178 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
179 |
180 | var _immutable = __webpack_require__(2);
181 |
182 | var _immutable2 = _interopRequireDefault(_immutable);
183 |
184 | function updateDeep(a, b) {
185 | if (!(a instanceof _immutable2['default'].Collection) || !(b instanceof _immutable2['default'].Collection) || Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
186 | return a === b ? a : b;
187 | }
188 | return a.withMutations(function (result) {
189 | a.forEach(function (oldValue, key) {
190 | if (!b.has(key)) {
191 | result['delete'](key);
192 | }
193 | });
194 | b.forEach(function (newValue, key) {
195 | if (!a.has(key)) {
196 | result.set(key, newValue);
197 | } else {
198 | result.set(key, updateDeep(a.get(key), newValue));
199 | }
200 | });
201 | });
202 | }
203 |
204 | module.exports = exports['default'];
205 |
206 | /***/ },
207 | /* 4 */
208 | /***/ function(module, exports, __webpack_require__) {
209 |
210 | 'use strict';
211 |
212 | exports.__esModule = true;
213 | exports['default'] = ImmutableListObserver;
214 |
215 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
216 |
217 | var _immutable = __webpack_require__(2);
218 |
219 | var _immutable2 = _interopRequireDefault(_immutable);
220 |
221 | var _updateDeep = __webpack_require__(3);
222 |
223 | var _updateDeep2 = _interopRequireDefault(_updateDeep);
224 |
225 | function ImmutableListObserver(cursor) {
226 | var _documents = undefined;
227 | var dep = new Tracker.Dependency();
228 |
229 | function update(newDocuments) {
230 | _documents = newDocuments;
231 | dep.changed();
232 | }
233 |
234 | var initialDocuments = [];
235 | var handle = cursor.observe({
236 | addedAt: function addedAt(document, atIndex, before) {
237 | if (initialDocuments) {
238 | initialDocuments.splice(atIndex, 0, _immutable2['default'].fromJS(document));
239 | } else {
240 | update(_documents.splice(atIndex, 0, _immutable2['default'].fromJS(document)));
241 | }
242 | },
243 | changedAt: function changedAt(newDocument, oldDocument, atIndex) {
244 | update(_documents.update(id, function (document) {
245 | return _updateDeep2['default'](document, _immutable2['default'].fromJS(newDocument));
246 | }));
247 | },
248 | removedAt: function removedAt(oldDocument, atIndex) {
249 | update(_documents.splice(atIndex, 1));
250 | },
251 | movedTo: function movedTo(document, fromIndex, toIndex, before) {
252 | var movedDocument = _documents.get(fromIndex);
253 | update(_documents.splice(fromIndex, 1).splice(toIndex, 0, movedDocument));
254 | }
255 | });
256 | _documents = _immutable2['default'].List(initialDocuments);
257 | initialDocuments = undefined;
258 |
259 | if (Tracker.active) {
260 | Tracker.onInvalidate(function () {
261 | handle.stop();
262 | });
263 | }
264 |
265 | return {
266 | documents: function documents() {
267 | dep.depend();
268 | return _documents;
269 | },
270 | stop: function stop() {
271 | handle.stop();
272 | }
273 | };
274 | }
275 |
276 | module.exports = exports['default'];
277 |
278 | /***/ }
279 | /******/ ])
280 | });
281 | ;
--------------------------------------------------------------------------------
/lib/umd/meteor-immutable-observer.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("immutable")):"function"==typeof define&&define.amd?define(["immutable"],t):"object"==typeof exports?exports["meteor-immutable-observer"]=t(require("immutable")):e["meteor-immutable-observer"]=t(e.Immutable)}(this,function(e){return function(e){function t(o){if(n[o])return n[o].exports;var u=n[o]={exports:{},id:o,loaded:!1};return e[o].call(u.exports,u,u.exports,t),u.loaded=!0,u.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}t.__esModule=!0;var u=n(4),r=o(u),f=n(3),i=o(f);t["default"]={Map:r["default"],List:i["default"]},e.exports=t["default"]},function(t,n){t.exports=e},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function u(e,t){return e instanceof f["default"].Collection&&t instanceof f["default"].Collection&&Object.getPrototypeOf(e)===Object.getPrototypeOf(t)?e.withMutations(function(n){e.forEach(function(e,o){t.has(o)||n["delete"](o)}),t.forEach(function(t,o){e.has(o)?n.set(o,u(e.get(o),t)):n.set(o,t)})}):e===t?e:t}t.__esModule=!0,t["default"]=u;var r=n(1),f=o(r);e.exports=t["default"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function u(e){function t(e){n=e,o.changed()}var n=void 0,o=new Tracker.Dependency,u=[],r=e.observe({addedAt:function(e,o,r){u?u.splice(o,0,f["default"].fromJS(e)):t(n.splice(o,0,f["default"].fromJS(e)))},changedAt:function(e,o,u){t(n.update(id,function(t){return d["default"](t,f["default"].fromJS(e))}))},removedAt:function(e,o){t(n.splice(o,1))},movedTo:function(e,o,u,r){var f=n.get(o);t(n.splice(o,1).splice(u,0,f))}});return n=f["default"].List(u),u=void 0,Tracker.active&&Tracker.onInvalidate(function(){r.stop()}),{documents:function(){return o.depend(),n},stop:function(){r.stop()}}}t.__esModule=!0,t["default"]=u;var r=n(1),f=o(r),i=n(2),d=o(i);e.exports=t["default"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function u(e,t){return e.withMutations(function(e){for(var n in t)if(t.hasOwnProperty(n)){var o=t[n];void 0===o?e["delete"](n):e.update(n,function(e){return a["default"](e,i["default"].fromJS(o))})}})}function r(e){function t(e){n=e,o.changed()}var n=void 0,o=new Tracker.Dependency,r={},f=e.observeChanges({added:function(e,o){o._id=e,r?r[e]=i["default"].fromJS(o):t(n.set(e,i["default"].fromJS(o)))},changed:function(e,o){t(n.update(e,function(e){return u(e,o)}))},removed:function(e){t(n["delete"](e))}});return n=i["default"].Map(r),r=void 0,Tracker.active&&Tracker.onInvalidate(function(){f.stop()}),{documents:function(){return o.depend(),n},stop:function(){f.stop()}}}t.__esModule=!0,t["default"]=r;var f=n(1),i=o(f),d=n(2),a=o(d);e.exports=t["default"]}])});
--------------------------------------------------------------------------------
/meteor/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 |
--------------------------------------------------------------------------------
/meteor/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/meteor/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 196ubmjboa7qx2vr5ju
8 |
--------------------------------------------------------------------------------
/meteor/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | meteor-platform
8 | insecure
9 | mindfront:immutable-observer
10 | velocity:html-reporter
11 |
--------------------------------------------------------------------------------
/meteor/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/meteor/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.1.0.3
2 |
--------------------------------------------------------------------------------
/meteor/.meteor/versions:
--------------------------------------------------------------------------------
1 | amplify@1.0.0
2 | autoupdate@1.2.1
3 | base64@1.0.3
4 | binary-heap@1.0.3
5 | blaze@2.1.2
6 | blaze-tools@1.0.3
7 | boilerplate-generator@1.0.3
8 | callback-hook@1.0.3
9 | check@1.0.5
10 | coffeescript@1.0.6
11 | dataflows:immutable@3.6.2
12 | ddp@1.1.0
13 | deps@1.0.7
14 | ejson@1.0.6
15 | fastclick@1.0.3
16 | geojson-utils@1.0.3
17 | grigio:babel@0.1.7
18 | html-tools@1.0.4
19 | htmljs@1.0.4
20 | http@1.1.0
21 | id-map@1.0.3
22 | insecure@1.0.3
23 | jedwards1211:immutable-observer@0.0.1
24 | jquery@1.11.3_2
25 | json@1.0.3
26 | launch-screen@1.0.2
27 | less@1.0.14
28 | livedata@1.0.13
29 | logging@1.0.7
30 | meteor@1.1.6
31 | meteor-platform@1.2.2
32 | minifiers@1.1.5
33 | minimongo@1.0.8
34 | mobile-status-bar@1.0.3
35 | mongo@1.1.0
36 | observe-sequence@1.0.6
37 | ordered-dict@1.0.3
38 | package-version-parser@3.0.3
39 | practicalmeteor:chai@2.1.0_1
40 | practicalmeteor:loglevel@1.2.0_2
41 | random@1.0.3
42 | reactive-dict@1.1.0
43 | reactive-var@1.0.5
44 | reload@1.1.3
45 | retry@1.0.3
46 | routepolicy@1.0.5
47 | sanjo:jasmine@0.18.0
48 | sanjo:karma@1.7.0
49 | sanjo:long-running-child-process@1.1.3
50 | sanjo:meteor-files-helpers@1.1.0_7
51 | sanjo:meteor-version@1.0.0
52 | session@1.1.0
53 | spacebars@1.0.6
54 | spacebars-compiler@1.0.6
55 | templating@1.1.1
56 | tracker@1.0.7
57 | ui@1.0.6
58 | underscore@1.0.3
59 | url@1.0.4
60 | velocity:chokidar@1.0.3_1
61 | velocity:core@0.9.3
62 | velocity:html-reporter@0.8.2
63 | velocity:meteor-internals@1.1.0_7
64 | velocity:meteor-stubs@1.1.0
65 | velocity:shim@0.1.0
66 | velocity:source-map-support@0.3.2_1
67 | webapp@1.2.0
68 | webapp-hashing@1.0.3
69 |
--------------------------------------------------------------------------------
/meteor/meteor.html:
--------------------------------------------------------------------------------
1 |
2 | meteor
3 |
4 |
5 |
6 | Welcome to Meteor!
7 |
8 | {{> hello}}
9 |
--------------------------------------------------------------------------------
/meteor/packages/immutable-observer/README.md:
--------------------------------------------------------------------------------
1 | ../../../README.md
--------------------------------------------------------------------------------
/meteor/packages/immutable-observer/global-meteor-immutable-observer.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory(require("immutable"));
4 | else if(typeof define === 'function' && define.amd)
5 | define(["immutable"], factory);
6 | else if(typeof exports === 'object')
7 | exports["meteor-immutable-observer"] = factory(require("immutable"));
8 | else
9 | root["meteor-immutable-observer"] = factory(root["Immutable"]);
10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_3__) {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 |
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 |
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId])
20 | /******/ return installedModules[moduleId].exports;
21 |
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ exports: {},
25 | /******/ id: moduleId,
26 | /******/ loaded: false
27 | /******/ };
28 |
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 |
32 | /******/ // Flag the module as loaded
33 | /******/ module.loaded = true;
34 |
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 |
39 |
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 |
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 |
46 | /******/ // __webpack_public_path__
47 | /******/ __webpack_require__.p = "";
48 |
49 | /******/ // Load entry module and return exports
50 | /******/ return __webpack_require__(0);
51 | /******/ })
52 | /************************************************************************/
53 | /******/ ([
54 | /* 0 */
55 | /***/ function(module, exports, __webpack_require__) {
56 |
57 | var global = (function() { return this; })();
58 | global.ImmutableObserver = __webpack_require__(1);
59 |
60 |
61 | /***/ },
62 | /* 1 */
63 | /***/ function(module, exports, __webpack_require__) {
64 |
65 | 'use strict';
66 |
67 | exports.__esModule = true;
68 |
69 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
70 |
71 | var _ImmutableMapObserver = __webpack_require__(2);
72 |
73 | var _ImmutableMapObserver2 = _interopRequireDefault(_ImmutableMapObserver);
74 |
75 | var _ImmutableListObserver = __webpack_require__(5);
76 |
77 | var _ImmutableListObserver2 = _interopRequireDefault(_ImmutableListObserver);
78 |
79 | exports['default'] = {
80 | Map: _ImmutableMapObserver2['default'],
81 | List: _ImmutableListObserver2['default']
82 | };
83 | module.exports = exports['default'];
84 |
85 | /***/ },
86 | /* 2 */
87 | /***/ function(module, exports, __webpack_require__) {
88 |
89 | 'use strict';
90 |
91 | exports.__esModule = true;
92 | exports['default'] = ImmutableMapObserver;
93 |
94 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
95 |
96 | var _immutable = __webpack_require__(3);
97 |
98 | var _immutable2 = _interopRequireDefault(_immutable);
99 |
100 | var _updateDeep = __webpack_require__(4);
101 |
102 | var _updateDeep2 = _interopRequireDefault(_updateDeep);
103 |
104 | function mergeChanges(document, fields) {
105 | return document.withMutations(function (document) {
106 | for (var key in fields) {
107 | if (fields.hasOwnProperty(key)) {
108 | var newValue = fields[key];
109 | if (newValue === undefined) {
110 | document['delete'](key);
111 | } else {
112 | document.update(key, function (oldValue) {
113 | return _updateDeep2['default'](oldValue, _immutable2['default'].fromJS(newValue));
114 | });
115 | }
116 | }
117 | }
118 | });
119 | }
120 |
121 | function ImmutableMapObserver(cursor) {
122 | var _documents = undefined;
123 | var dep = new Tracker.Dependency();
124 |
125 | function update(newDocuments) {
126 | _documents = newDocuments;
127 | dep.changed();
128 | }
129 |
130 | var initialDocuments = {};
131 | var handle = cursor.observeChanges({
132 | added: function added(id, fields) {
133 | fields._id = id;
134 | if (initialDocuments) {
135 | initialDocuments[id] = _immutable2['default'].fromJS(fields);
136 | } else {
137 | update(_documents.set(id, _immutable2['default'].fromJS(fields)));
138 | }
139 | },
140 | changed: function changed(id, fields) {
141 | update(_documents.update(id, function (document) {
142 | return mergeChanges(document, fields);
143 | }));
144 | },
145 | removed: function removed(id) {
146 | update(_documents['delete'](id));
147 | }
148 | });
149 | _documents = _immutable2['default'].Map(initialDocuments);
150 | initialDocuments = undefined;
151 |
152 | if (Tracker.active) {
153 | Tracker.onInvalidate(function () {
154 | handle.stop();
155 | });
156 | }
157 |
158 | return {
159 | documents: function documents() {
160 | dep.depend();
161 | return _documents;
162 | },
163 | stop: function stop() {
164 | handle.stop();
165 | }
166 | };
167 | }
168 |
169 | module.exports = exports['default'];
170 |
171 | /***/ },
172 | /* 3 */
173 | /***/ function(module, exports) {
174 |
175 | module.exports = __WEBPACK_EXTERNAL_MODULE_3__;
176 |
177 | /***/ },
178 | /* 4 */
179 | /***/ function(module, exports, __webpack_require__) {
180 |
181 | 'use strict';
182 |
183 | exports.__esModule = true;
184 | exports['default'] = updateDeep;
185 |
186 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
187 |
188 | var _immutable = __webpack_require__(3);
189 |
190 | var _immutable2 = _interopRequireDefault(_immutable);
191 |
192 | function updateDeep(a, b) {
193 | if (!(a instanceof _immutable2['default'].Collection) || !(b instanceof _immutable2['default'].Collection) || Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
194 | return a === b ? a : b;
195 | }
196 | return a.withMutations(function (result) {
197 | a.forEach(function (oldValue, key) {
198 | if (!b.has(key)) {
199 | result['delete'](key);
200 | }
201 | });
202 | b.forEach(function (newValue, key) {
203 | if (!a.has(key)) {
204 | result.set(key, newValue);
205 | } else {
206 | result.set(key, updateDeep(a.get(key), newValue));
207 | }
208 | });
209 | });
210 | }
211 |
212 | module.exports = exports['default'];
213 |
214 | /***/ },
215 | /* 5 */
216 | /***/ function(module, exports, __webpack_require__) {
217 |
218 | 'use strict';
219 |
220 | exports.__esModule = true;
221 | exports['default'] = ImmutableListObserver;
222 |
223 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
224 |
225 | var _immutable = __webpack_require__(3);
226 |
227 | var _immutable2 = _interopRequireDefault(_immutable);
228 |
229 | var _updateDeep = __webpack_require__(4);
230 |
231 | var _updateDeep2 = _interopRequireDefault(_updateDeep);
232 |
233 | function ImmutableListObserver(cursor) {
234 | var _documents = undefined;
235 | var dep = new Tracker.Dependency();
236 |
237 | function update(newDocuments) {
238 | _documents = newDocuments;
239 | dep.changed();
240 | }
241 |
242 | var initialDocuments = [];
243 | var handle = cursor.observe({
244 | addedAt: function addedAt(document, atIndex, before) {
245 | if (initialDocuments) {
246 | initialDocuments.splice(atIndex, 0, _immutable2['default'].fromJS(document));
247 | } else {
248 | update(_documents.splice(atIndex, 0, _immutable2['default'].fromJS(document)));
249 | }
250 | },
251 | changedAt: function changedAt(newDocument, oldDocument, atIndex) {
252 | update(_documents.update(id, function (document) {
253 | return _updateDeep2['default'](document, _immutable2['default'].fromJS(newDocument));
254 | }));
255 | },
256 | removedAt: function removedAt(oldDocument, atIndex) {
257 | update(_documents.splice(atIndex, 1));
258 | },
259 | movedTo: function movedTo(document, fromIndex, toIndex, before) {
260 | var movedDocument = _documents.get(fromIndex);
261 | update(_documents.splice(fromIndex, 1).splice(toIndex, 0, movedDocument));
262 | }
263 | });
264 | _documents = _immutable2['default'].List(initialDocuments);
265 | initialDocuments = undefined;
266 |
267 | if (Tracker.active) {
268 | Tracker.onInvalidate(function () {
269 | handle.stop();
270 | });
271 | }
272 |
273 | return {
274 | documents: function documents() {
275 | dep.depend();
276 | return _documents;
277 | },
278 | stop: function stop() {
279 | handle.stop();
280 | }
281 | };
282 | }
283 |
284 | module.exports = exports['default'];
285 |
286 | /***/ }
287 | /******/ ])
288 | });
289 | ;
--------------------------------------------------------------------------------
/meteor/packages/immutable-observer/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: 'mindfront:immutable-observer',
3 | version: '0.0.1',
4 | // Brief, one-line summary of the package.
5 | summary: 'Creates and auto-updates Immutable.js collections from Meteor Collection cursors',
6 | // URL to the Git repository containing the source code for this package.
7 | git: 'https://github.com/mindfront/meteor-immutable-observer',
8 | // By default, Meteor will default to using README.md for documentation.
9 | // To avoid submitting documentation, set this field to null.
10 | documentation: 'README.md'
11 | });
12 |
13 | Package.onUse(function(api) {
14 | api.versionsFrom('1.1.0.3');
15 | api.use('dataflows:immutable@3.6.2')
16 | api.addFiles('global-meteor-immutable-observer.js');
17 | });
18 |
19 | Package.onTest(function(api) {
20 | api.use('sanjo:jasmine@0.18.0');
21 | api.use('dataflows:immutable@3.6.2')
22 | api.use('insecure');
23 | api.addFiles('global-meteor-immutable-observer.js');
24 | api.addFiles('tests/Players.js');
25 | api.addFiles('tests/client/ImmutableObserverSpec.js', 'client');
26 | });
--------------------------------------------------------------------------------
/meteor/packages/immutable-observer/tests/Players.js:
--------------------------------------------------------------------------------
1 | // Set up a collection to contain player information. On the server,
2 | // it is backed by a MongoDB collection named "players".
3 |
4 | Players = new Meteor.Collection("players");
5 |
6 | // On server startup, create some players if the database is empty.
7 | if (Meteor.isServer) {
8 | Meteor.methods({
9 | clearPlayers: function() {
10 | Players.remove({});
11 | }
12 | });
13 |
14 | Meteor.startup(function () {
15 | Meteor.publish('players', function(){
16 | return Players.find();
17 | });
18 | });
19 | }
--------------------------------------------------------------------------------
/meteor/packages/immutable-observer/tests/client/ImmutableObserverSpec.js:
--------------------------------------------------------------------------------
1 | describe('ImmutableObserver', function() {
2 | beforeEach(function(done) {
3 | cleanupContext = {
4 | stoppables: [],
5 | };
6 | Meteor.call('clearPlayers', function() {
7 | stopAfter(Meteor.subscribe('players', {onReady: function() {
8 | done();
9 | }}));
10 | });
11 | });
12 |
13 | afterEach(function(done) {
14 | cleanupContext.stoppables.forEach(function(stoppable) {
15 | stoppable.stop();
16 | });
17 | done();
18 | });
19 |
20 | function stopAfter(stoppable) {
21 | cleanupContext.stoppables.push(stoppable);
22 | }
23 |
24 | describe('ImmutableObserver.List', function() {
25 | it('gets multiple items in correct sorted order', function(done) {
26 | var observer = ImmutableObserver.List(Players.find({}, {sort: {score: 1}}));
27 | stopAfter(observer);
28 |
29 | stopAfter(Tracker.autorun(function() {
30 | var list = observer.documents();
31 | console.log(JSON.stringify(list.toJS(), null, " "));
32 | var prev;
33 | list.forEach(function(next) {
34 | if (prev) {
35 | expect(next.get('score')).not.toBeLessThan(prev.get('score'));
36 | }
37 | prev = next;
38 | });
39 | if (list.size === 5) {
40 | done();
41 | }
42 | }));
43 |
44 | Players.insert({name: 'Andy', score: 1000000});
45 | Players.insert({name: 'Jim', score: 30516});
46 | Players.insert({name: 'John', score: 178982});
47 | Players.insert({name: 'Kate', score: 999999});
48 | Players.insert({name: 'Carla', score: 97817});
49 | });
50 | });
51 |
52 | describe('ImmutableObserver.Map', function() {
53 | it('basic test', function(done) {
54 | var observer = ImmutableObserver.Map(Players.find({name: 'Andy', score: {$gt: 1000000}}));
55 | stopAfter(observer);
56 |
57 | stopAfter(Tracker.autorun(function() {
58 | var documents = observer.documents();
59 | console.log(JSON.stringify(documents.toJS(), null, " "));
60 | if (documents.size) {
61 | done();
62 | }
63 | }));
64 | Players.insert({name: 'Andy', score: 1000000}, function(error, _id) {
65 | if (error) {
66 | done.fail(error);
67 | }
68 | Players.update({_id: _id}, {$inc: {score: 5}}, function(error) {
69 | if (error) {
70 | done.fail(error);
71 | }
72 | });
73 | });
74 | });
75 |
76 | it('updates fields correctly', function(done) {
77 | var observer = ImmutableObserver.Map(Players.find());
78 | stopAfter(observer);
79 |
80 | var _id;
81 |
82 | Players.insert({name: 'Andy', test: {a: [{b: [{c: 1}, {d: 2}]}], e: [{f: 1}]}}, function(error, id) {
83 | if (error) done.fail(error);
84 | _id = id;
85 | phase1();
86 | });
87 |
88 | var initPlayer;
89 |
90 | function phase1() {
91 | var comp = Tracker.autorun(function(comp) {
92 | var documents = observer.documents();
93 | console.log(JSON.stringify(documents.toJS(), null, " "));
94 | if (documents.size === 1 && documents.has(_id)) {
95 | initPlayer = documents.get(_id);
96 | comp.stop();
97 | phase2();
98 | }
99 | });
100 | stopAfter(comp);
101 | }
102 |
103 | function phase2() {
104 | var updated = false;
105 | var newTest = {test: {a: [{b: [{c: 1}, {d: 2, g: 3, h: 4}, {i: 5}]}], e: [{f: 1}]}};
106 | Players.update({_id: _id}, {$set: newTest, $unset: {name: ""}});
107 |
108 | var comp = Tracker.autorun(function() {
109 | Meteor.subscribe('players');
110 | var documents = observer.documents();
111 | console.log(JSON.stringify(documents.toJS(), null, " "));
112 | if (documents.size === 1 && documents.get(_id) !== initPlayer) {
113 | var newPlayer = documents.get(_id);
114 | var unchangedPaths = [['test', 'a', 0, 'b', 0], ['test', 'e', 0]];
115 | var changedPaths = [['name'], ['test', 'a', 0, 'b', 1], ['test', 'a', 0, 'b', 2]];
116 | unchangedPaths.forEach(function(path) {
117 | expect(newPlayer.getIn(path)).toBe(initPlayer.getIn(path));
118 | });
119 | changedPaths.forEach(function(path) {
120 | expect(newPlayer.getIn(path)).not.toBe(initPlayer.getIn(path));
121 | });
122 | expect(newPlayer.mergeDeep(newTest)).toBe(newPlayer);
123 | expect(newPlayer.get('name')).toBeUndefined();
124 | done();
125 | }
126 | });
127 | stopAfter(comp);
128 | }
129 | });
130 | });
131 |
132 | });
--------------------------------------------------------------------------------
/meteor/test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | VELOCITY_TEST_PACKAGES=1 meteor test-packages --driver-package velocity:html-reporter mindfront:immutable-observer
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "meteor-immutable-observer",
3 | "version": "0.0.1",
4 | "description": "Creates and auto-updates Immutable.js collections from Meteor Collection cursors",
5 | "main": "lib/index",
6 | "scripts": {
7 | "build": "./scripts/build.sh",
8 | "prepublish": "npm run build"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/mindfront/meteor-immutable-observer"
13 | },
14 | "keywords": [
15 | "meteor",
16 | "immutable",
17 | "immutablejs",
18 | "observer",
19 | "collection",
20 | "mongo"
21 | ],
22 | "author": "Andy Edwards",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/mindfront/meteor-immutable-observer/issues"
26 | },
27 | "homepage": "https://github.com/mindfront/meteor-immutable-observer",
28 | "peerDependencies": {
29 | "immutable": "^3.7.5"
30 | },
31 | "devDependencies": {
32 | "babel": "^5.8.23",
33 | "babel-core": "^5.8.23",
34 | "babel-loader": "^5.3.2",
35 | "immutable": "^3.7.5",
36 | "webpack": "^1.12.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | babel=node_modules/.bin/babel
3 | webpack=node_modules/.bin/webpack
4 | build_dir=lib
5 | meteor_package_dir=meteor/packages/immutable-observer
6 |
7 | rm -rf $build_dir
8 |
9 | $babel ./src -d $build_dir --ignore "__tests__" --loose all
10 |
11 | NODE_ENV=production $webpack src/index.js $build_dir/umd/meteor-immutable-observer.js
12 | NODE_ENV=production $webpack -p src/index.js $build_dir/umd/meteor-immutable-observer.min.js
13 | NODE_ENV=production $webpack src/setGlobalImmutableObserver.js $meteor_package_dir/global-meteor-immutable-observer.js
14 |
15 | echo "gzipped, the global build is `gzip -c $build_dir/umd/meteor-immutable-observer.min.js | wc -c | sed -e 's/^[[:space:]]*//'` bytes"
16 |
--------------------------------------------------------------------------------
/src/ImmutableListObserver.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 |
3 | import updateDeep from './updateDeep';
4 |
5 | export default function ImmutableListObserver(cursor) {
6 | let documents;
7 | let dep = new Tracker.Dependency();
8 |
9 | function update(newDocuments) {
10 | documents = newDocuments;
11 | dep.changed();
12 | }
13 |
14 | let initialDocuments = [];
15 | let handle = cursor.observe({
16 | addedAt: (document, atIndex, before) => {
17 | if (initialDocuments) {
18 | initialDocuments.splice(atIndex, 0, Immutable.fromJS(document));
19 | }
20 | else {
21 | update(documents.splice(atIndex, 0, Immutable.fromJS(document)));
22 | }
23 | },
24 | changedAt: (newDocument, oldDocument, atIndex) => {
25 | update(documents.update(id, document => updateDeep(document, Immutable.fromJS(newDocument))));
26 | },
27 | removedAt: (oldDocument, atIndex) => {
28 | update(documents.splice(atIndex, 1));
29 | },
30 | movedTo: (document, fromIndex, toIndex, before) => {
31 | var movedDocument = documents.get(fromIndex);
32 | update(documents.splice(fromIndex, 1).splice(toIndex, 0, movedDocument));
33 | },
34 | });
35 | documents = Immutable.List(initialDocuments);
36 | initialDocuments = undefined;
37 |
38 | if (Tracker.active) {
39 | Tracker.onInvalidate(() => {
40 | handle.stop();
41 | });
42 | }
43 |
44 | return {
45 | documents() {
46 | dep.depend();
47 | return documents;
48 | },
49 | stop() {
50 | handle.stop();
51 | }
52 | };
53 | }
--------------------------------------------------------------------------------
/src/ImmutableMapObserver.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 |
3 | import updateDeep from './updateDeep';
4 |
5 | function mergeChanges(document, fields) {
6 | return document.withMutations(document => {
7 | for (var key in fields) {
8 | if (fields.hasOwnProperty(key)) {
9 | var newValue = fields[key];
10 | if (newValue === undefined) {
11 | document.delete(key);
12 | }
13 | else {
14 | document.update(key, oldValue => updateDeep(oldValue, Immutable.fromJS(newValue)));
15 | }
16 | }
17 | }
18 | });
19 | }
20 |
21 | export default function ImmutableMapObserver(cursor) {
22 | let documents;
23 | let dep = new Tracker.Dependency();
24 |
25 | function update(newDocuments) {
26 | documents = newDocuments;
27 | dep.changed();
28 | }
29 |
30 | let initialDocuments = {};
31 | let handle = cursor.observeChanges({
32 | added: (id, fields) => {
33 | fields._id = id;
34 | if (initialDocuments) {
35 | initialDocuments[id] = Immutable.fromJS(fields);
36 | }
37 | else {
38 | update(documents.set(id, Immutable.fromJS(fields)));
39 | }
40 | },
41 | changed: (id, fields) => {
42 | update(documents.update(id, document => mergeChanges(document, fields)));
43 | },
44 | removed: (id) => {
45 | update(documents.delete(id));
46 | },
47 | });
48 | documents = Immutable.Map(initialDocuments);
49 | initialDocuments = undefined;
50 |
51 | if (Tracker.active) {
52 | Tracker.onInvalidate(() => {
53 | handle.stop();
54 | });
55 | }
56 |
57 | return {
58 | documents() {
59 | dep.depend();
60 | return documents;
61 | },
62 | stop() {
63 | handle.stop();
64 | }
65 | };
66 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Map from './ImmutableMapObserver';
2 | import List from './ImmutableListObserver';
3 |
4 | export default {
5 | Map,
6 | List,
7 | };
8 |
--------------------------------------------------------------------------------
/src/setGlobalImmutableObserver.js:
--------------------------------------------------------------------------------
1 | var global = (function() { return this; })();
2 | global.ImmutableObserver = require('./index');
3 |
--------------------------------------------------------------------------------
/src/updateDeep.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 |
3 | export default function updateDeep(a, b) {
4 | if (!(a instanceof Immutable.Collection) ||
5 | !(b instanceof Immutable.Collection) ||
6 | Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
7 | return a === b ? a : b;
8 | }
9 | return a.withMutations(result => {
10 | a.forEach((oldValue, key) => {
11 | if (!b.has(key)) {
12 | result.delete(key);
13 | }
14 | });
15 | b.forEach((newValue, key) => {
16 | if (!a.has(key)) {
17 | result.set(key, newValue);
18 | }
19 | else {
20 | result.set(key, updateDeep(a.get(key), newValue));
21 | }
22 | });
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 |
3 | module.exports = {
4 |
5 | output: {
6 | library: 'meteor-immutable-observer',
7 | libraryTarget: 'umd'
8 | },
9 |
10 | externals: [
11 | {
12 | "immutable": {
13 | root: "Immutable",
14 | commonjs2: "immutable",
15 | commonjs: "immutable",
16 | amd: "immutable"
17 | }
18 | }
19 | ],
20 |
21 | module: {
22 | loaders: [
23 | { test: /\.js$/, exclude: /node_modules|setGlobalImmutableObserver\.js/, loader: 'babel?loose=all' }
24 | ]
25 | },
26 |
27 | plugins: [
28 | new webpack.DefinePlugin({
29 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
30 | })
31 | ]
32 |
33 | };
34 |
--------------------------------------------------------------------------------