From 03025db527b6167b4e8e3179919efa221f7a6f19 Mon Sep 17 00:00:00 2001 From: Andrew Wilcox Date: Tue, 26 Feb 2013 14:55:04 -0800 Subject: [PATCH 01/13] Only load Session on the client. Fixes 751. --- packages/session/package.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/session/package.js b/packages/session/package.js index e6dd2d0895..2b2ca13bd9 100644 --- a/packages/session/package.js +++ b/packages/session/package.js @@ -8,19 +8,14 @@ Package.describe({ // XXX hack -- need a way to use a package at bundle time var _ = require(path.join('..', '..', 'packages', 'underscore', 'underscore.js')); -Package.on_use(function (api, where) { - where = where || ['client', 'server']; - - api.use(['underscore', 'deps'], where); +Package.on_use(function (api) { + api.use(['underscore', 'deps'], 'client'); // XXX what I really want to do is ensure that if 'reload' is going to // be loaded, it should be loaded before 'session'. Session can work // with or without reload. - if (where === "client" || - (where instanceof Array && _.indexOf(where, "client") !== -1)) { - api.use("reload", "client"); - } + api.use('reload', 'client'); - api.add_files('session.js', where); + api.add_files('session.js', 'client'); }); Package.on_test(function (api) { From 93af9a2a038c313b2ad00df2b203137a34239d19 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 4 Mar 2013 12:46:33 -0800 Subject: [PATCH 02/13] Add History.md note about Session. --- History.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/History.md b/History.md index 832904c823..7df60f3002 100644 --- a/History.md +++ b/History.md @@ -14,6 +14,8 @@ * Fixed `{$type: 5}` selectors for binary values on browsers that do not support `Uint8Array` +* Stop making `Session` available on the server; it's not very useful there. + ## v0.5.7 * The DDP wire protocol has been redesigned. From 12a123c4662d0ef1df8f4dbc379f801c4542f535 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 4 Mar 2013 14:08:44 -0800 Subject: [PATCH 03/13] docs link fixes from check_links() --- docs/client/api.html | 6 +++--- docs/client/docs.js | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 683c8dbb11..e93b23844c 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -136,7 +136,7 @@ function is rerun with the new value. {{> api_box subscription_onStop}} -If you call [`observe`](#observe) or [`observeChanges`](#observeChanges) in your +If you call [`observe`](#observe) or [`observeChanges`](#observe_changes) in your publish handler, this is the place to stop the observes. {{> api_box subscription_error}} @@ -900,7 +900,7 @@ Establishes a *live query* that invokes callbacks when the result of the query changes. The callbacks receive the entire contents of the document that was affected, as well as its old contents, if applicable. If you only need to receive the fields that changed, see -[`observeChanges`](#cursor_observe_changes). +[`observeChanges`](#observe_changes). `callbacks` may have the following functions as properties: @@ -957,7 +957,7 @@ down the query. **The query will run forever until you call this.** {{> api_box cursor_observe_changes}} Establishes a *live query* that invokes callbacks when the result of -the query changes. In contrast to [`observe`](#cursor_observe), +the query changes. In contrast to [`observe`](#observe), `observeChanges` provides only the difference between the old and new result set, not the entire contents of the document that changed. diff --git a/docs/client/docs.js b/docs/client/docs.js index 749ac59b82..b7dfd4c54c 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -53,6 +53,9 @@ Meteor.startup(function () { }; var scrollToSection = function (section) { + if (! $(section).length) + return; + ignore_waypoints = true; Session.set("section", section.substr(1)); scroller().animate({ From 3fb2434d7155df8938106f656c17db8cb85dea87 Mon Sep 17 00:00:00 2001 From: "k.kurilov" Date: Thu, 21 Feb 2013 00:12:21 +0400 Subject: [PATCH 04/13] Return multiple cursors from publish function. --- docs/client/api.html | 13 ++++++-- packages/livedata/livedata_server.js | 34 ++++++++++++++++--- packages/livedata/livedata_test_service.js | 39 ++++++++++++++++++++++ packages/livedata/livedata_tests.js | 24 +++++++++++++ packages/mongo-livedata/mongo_driver.js | 12 ++++--- 5 files changed, 112 insertions(+), 10 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index e93b23844c..585c988a99 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -43,9 +43,10 @@ To publish records to clients, call `Meteor.publish` on the server with two parameters: the name of the record set, and a *publish function* that Meteor will call each time a client subscribes to the name. -Publish functions can return a +Publish functions can return an instance or array of [`Collection.Cursor`](#meteor_collection_cursor), in which case Meteor -will publish that cursor's documents. +will publish that cursor's documents. You can return only one cursor +for each collection when returning an array of cursors. // server: publish the rooms collection, minus secret info. Meteor.publish("rooms", function () { @@ -59,6 +60,14 @@ will publish that cursor's documents. return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}}); }); + // publish dependent documents and simulate joins + Meteor.publish("room", function (roomId) { + return [ + Rooms.find({_id: roomId}, {fields: {secretInfo: 0}}), + Messages.find({roomId: roomId}) + ]; + }); + Otherwise, the publish function should call the functions [`added`](#publish_added) (when a new document is added to the published record set), [`changed`](#publish_changed) (when some fields on a document in the diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 7fd44c950f..dec23874eb 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -819,9 +819,9 @@ _.extend(Meteor._LivedataSubscription.prototype, { return; } - // SPECIAL CASE: Instead of writing their own callbacks that invoke + // SPECIAL CASE: Instead of writing their own callbacks that invoke // this.added/changed/ready/etc, the user can just return a collection - // cursor from the publish function; we call its _publishCursor method which + // cursor or array of cursors from the publish function; we call its _publishCursor method which // starts observing the cursor and publishes the results. // // XXX This uses an undocumented interface which only the Mongo cursor @@ -834,8 +834,34 @@ _.extend(Meteor._LivedataSubscription.prototype, { // reactiveThingy.publishMe(); // }); // }; - if (res && res._publishCursor) - res._publishCursor(self); + if (res) { + if (res._publishCursor) { + res._publishCursor(self); + // _publishCursor only returns after the initial added callbacks have run. + // mark subscription as ready. + self.ready(); + } else if (_.isArray(res)) { + // check all the elements are cursors + if (! _.every(res, function (cur) { return cur && cur._publishCursor; })) { + self.error(new Meteor.Error(500, "Pulish function returned an array of non-Cursors")); + return; + } + // find duplicate collection names + var dups = []; + _.each(_.countBy(res, function (cur) { return cur._getCollectionName(); }), + function (count, col) { + count > 1 && dups.push(col); + }); + + if (dups.length === 0) { // no duplicates + _.each(res, function (cur) { + cur._publishCursor(self); + }); + self.ready(); + } else + self.error(new Meteor.Error(500, "Publish function returned multiple cursors for one collection", dups)); + } + } }, // This calls all stop callbacks and prevents the handler from updating any diff --git a/packages/livedata/livedata_test_service.js b/packages/livedata/livedata_test_service.js index 8b6e33241f..edb1133a49 100644 --- a/packages/livedata/livedata_test_service.js +++ b/packages/livedata/livedata_test_service.js @@ -277,4 +277,43 @@ if (Meteor.isServer) { } +/*****/ + +/// Helpers for "livedata - publish multiple cursors" +One = new Meteor.Collection("collectionOne"); +Two = new Meteor.Collection("collectionTwo"); + +if (Meteor.isServer) { + One.remove({}); + One.insert({name: "value1"}); + One.insert({name: "value2"}); + + Two.remove({}); + Two.insert({name: "value3"}); + Two.insert({name: "value4"}); + Two.insert({name: "value5"}); + + Meteor.publish("multiPublish", function (options) { + if (options.normal) + return [ + One.find(), + Two.find() + ]; + else if (options.dup) + return [ + One.find(), + One.find({name: "value2"}), // multiple cursors for one collection - error + Two.find() + ]; + else if (options.notCursor) + return [ + One.find(), + "not a cursor", + Two.find() + ]; + else + throw "unexpected options"; + }); +} + })(); diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index ef590e0415..38ee3e9f61 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -507,6 +507,30 @@ if (Meteor.isClient) { conn._stream.forceDisconnect(); } ];})()); + + testAsyncMulti("livedata - publish multiple cursors", [ + function (test, expect) { + Meteor.subscribe("multiPublish", {normal: 1}, { + onReady: expect(function () { + test.equal(One.find().count(), 2); + test.equal(Two.find().count(), 3); + }), + onError: failure() + }); + }, + function (test, expect) { + Meteor.subscribe("multiPublish", {dup: 1}, { + onReady: failure(), + onError: expect(failure(test, 500, "Publish function returned multiple cursors for one collection")) + }); + }, + function (test, expect) { + Meteor.subscribe("multiPublish", {notCursor: 1}, { + onReady: failure(), + onError: expect(failure(test, 500, "Pulish function returned an array of non-Cursors")) + }); + }, + ]); } // XXX some things to test in greater detail: diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index d317737f54..3aa4f4dd08 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -398,14 +398,18 @@ Cursor.prototype._publishCursor = function (sub) { } }); - // observeChanges only returns after the initial added callbacks have run. - // mark subscription as ready. - sub.ready(); - // register stop callback (expects lambda w/ no args). sub.onStop(function () {observeHandle.stop();}); }; +// When you call Meteor.publish() with a function that returns an array of Cursor, we need +// to check uniqueness of Cursor for each collection in that array. We can accomplish it through +// Cursor collection name comparison. +Cursor.prototype._getCollectionName = function () { + var self = this; + return self._cursorDescription.collectionName; +} + Cursor.prototype.observe = function (callbacks) { var self = this; return LocalCollection._observeFromObserveChanges(self, callbacks); From 26430d117bd05a29aa5848d37950d655596172d5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 4 Mar 2013 18:30:38 -0800 Subject: [PATCH 05/13] Review of PR #716 (multi-cursor publish). - History.md update - tweak docs - refactor the "one per collection" check - make errors into internal errors. Programming errors like returning the wrong type from a function on the server don't need to be reported to the client. --- History.md | 3 ++ docs/client/api.html | 15 ++++-- packages/livedata/livedata_server.js | 61 ++++++++++++---------- packages/livedata/livedata_test_service.js | 12 +++-- packages/livedata/livedata_tests.js | 6 +-- packages/mongo-livedata/mongo_driver.js | 9 ++-- 6 files changed, 65 insertions(+), 41 deletions(-) diff --git a/History.md b/History.md index 7df60f3002..36ee74e686 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,9 @@ ## vNEXT +* Publish functions may now return an array of cursors to publish. Currently, + the cursors must all be from different collections. + * User documents have id's when onCreateUser and validateNewUser hooks run. * Removed all restrictions on EJSON types in MongoDB, even user-defined ones. diff --git a/docs/client/api.html b/docs/client/api.html index 585c988a99..6a261a76a9 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -43,10 +43,17 @@ To publish records to clients, call `Meteor.publish` on the server with two parameters: the name of the record set, and a *publish function* that Meteor will call each time a client subscribes to the name. -Publish functions can return an instance or array of +Publish functions can return a [`Collection.Cursor`](#meteor_collection_cursor), in which case Meteor -will publish that cursor's documents. You can return only one cursor -for each collection when returning an array of cursors. +will publish that cursor's documents. You can also return an array of +`Collection.Cursor`s, in which case Meteor will publish all of the +cursors. + +{{#warning}} +If you return multiple cursors in an array, they currently must all be from +different collections. We hope to lift this restriction in a future release. +cursors. +{{/warning}} // server: publish the rooms collection, minus secret info. Meteor.publish("rooms", function () { @@ -61,7 +68,7 @@ for each collection when returning an array of cursors. }); // publish dependent documents and simulate joins - Meteor.publish("room", function (roomId) { + Meteor.publish("roomAndMessages", function (roomId) { return [ Rooms.find({_id: roomId}, {fields: {secretInfo: 0}}), Messages.find({roomId: roomId}) diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index dec23874eb..901dac065a 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -819,10 +819,11 @@ _.extend(Meteor._LivedataSubscription.prototype, { return; } - // SPECIAL CASE: Instead of writing their own callbacks that invoke + // SPECIAL CASE: Instead of writing their own callbacks that invoke // this.added/changed/ready/etc, the user can just return a collection - // cursor or array of cursors from the publish function; we call its _publishCursor method which - // starts observing the cursor and publishes the results. + // cursor or array of cursors from the publish function; we call their + // _publishCursor method which starts observing the cursor and publishes the + // results. Note that _publishCursor does NOT call ready(). // // XXX This uses an undocumented interface which only the Mongo cursor // interface publishes. Should we make this interface public and encourage @@ -834,33 +835,39 @@ _.extend(Meteor._LivedataSubscription.prototype, { // reactiveThingy.publishMe(); // }); // }; - if (res) { - if (res._publishCursor) { - res._publishCursor(self); - // _publishCursor only returns after the initial added callbacks have run. - // mark subscription as ready. - self.ready(); - } else if (_.isArray(res)) { - // check all the elements are cursors - if (! _.every(res, function (cur) { return cur && cur._publishCursor; })) { - self.error(new Meteor.Error(500, "Pulish function returned an array of non-Cursors")); + var isCursor = function (c) { + return c && c._publishCursor; + }; + if (isCursor(res)) { + res._publishCursor(self); + // _publishCursor only returns after the initial added callbacks have run. + // mark subscription as ready. + self.ready(); + } else if (_.isArray(res)) { + // check all the elements are cursors + if (! _.all(res, isCursor)) { + self.error(new Error("Publish function returned an array of non-Cursors")); + return; + } + // find duplicate collection names + // XXX we should support overlapping cursors, but that would require the + // merge box to allow overlap within a subscription + var collectionNames = {}; + for (var i = 0; i < res.length; ++i) { + var collectionName = res[i]._getCollectionName(); + if (_.has(collectionNames, collectionName)) { + self.error(new Error( + "Publish function returned multiple cursors for collection " + + collectionName)); return; } - // find duplicate collection names - var dups = []; - _.each(_.countBy(res, function (cur) { return cur._getCollectionName(); }), - function (count, col) { - count > 1 && dups.push(col); - }); + collectionNames[collectionName] = true; + }; - if (dups.length === 0) { // no duplicates - _.each(res, function (cur) { - cur._publishCursor(self); - }); - self.ready(); - } else - self.error(new Meteor.Error(500, "Publish function returned multiple cursors for one collection", dups)); - } + _.each(res, function (cur) { + cur._publishCursor(self); + }); + self.ready(); } }, diff --git a/packages/livedata/livedata_test_service.js b/packages/livedata/livedata_test_service.js index edb1133a49..213282a92e 100644 --- a/packages/livedata/livedata_test_service.js +++ b/packages/livedata/livedata_test_service.js @@ -294,24 +294,28 @@ if (Meteor.isServer) { Two.insert({name: "value5"}); Meteor.publish("multiPublish", function (options) { - if (options.normal) + if (options.normal) { return [ One.find(), Two.find() ]; - else if (options.dup) + } else if (options.dup) { + // Suppress the log of the expected internal error. + Meteor._suppress_log(1); return [ One.find(), One.find({name: "value2"}), // multiple cursors for one collection - error Two.find() ]; - else if (options.notCursor) + } else if (options.notCursor) { + // Suppress the log of the expected internal error. + Meteor._suppress_log(1); return [ One.find(), "not a cursor", Two.find() ]; - else + } else throw "unexpected options"; }); } diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index 38ee3e9f61..02a8f9a6a4 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -521,15 +521,15 @@ if (Meteor.isClient) { function (test, expect) { Meteor.subscribe("multiPublish", {dup: 1}, { onReady: failure(), - onError: expect(failure(test, 500, "Publish function returned multiple cursors for one collection")) + onError: expect(failure(test, 500, "Internal server error")) }); }, function (test, expect) { Meteor.subscribe("multiPublish", {notCursor: 1}, { onReady: failure(), - onError: expect(failure(test, 500, "Pulish function returned an array of non-Cursors")) + onError: expect(failure(test, 500, "Internal server error")) }); - }, + } ]); } diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index 3aa4f4dd08..dd8c1403a2 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -398,13 +398,16 @@ Cursor.prototype._publishCursor = function (sub) { } }); + // We don't call sub.ready() here: it gets called in livedata_server, after + // possibly calling _publishCursor on multiple returned cursors. + // register stop callback (expects lambda w/ no args). sub.onStop(function () {observeHandle.stop();}); }; -// When you call Meteor.publish() with a function that returns an array of Cursor, we need -// to check uniqueness of Cursor for each collection in that array. We can accomplish it through -// Cursor collection name comparison. +// Used to guarantee that publish functions return at most one cursor per +// collection. Private, because we might later have cursors that include +// documents from multiple collections somehow. Cursor.prototype._getCollectionName = function () { var self = this; return self._cursorDescription.collectionName; From aa16a06d679a080b65174b63bbe4206e5ef0da30 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Mon, 4 Mar 2013 21:40:41 -0800 Subject: [PATCH 06/13] Update license file for new npm modules. --- LICENSE.txt | 127 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 93 insertions(+), 34 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index bd83550eb8..2a6fbc3e05 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -286,30 +286,13 @@ All rights reserved. ---------- optimist: https://github.com/substack/node-optimist ----------- - -Copyright 2010 James Halliday (mail@substack.net) - - ----------- node-mkdirp: https://github.com/substack/node-mkdirp ----------- - -Copyright 2010 James Halliday (mail@substack.net) - - ----------- wordwrap: https://github.com/substack/node-wordwrap ----------- - -Copyright 2011 James Halliday (mail@substack.net) - - ----------- node-archy: https://github.com/substack/node-archy +node-shell-quote: https://github.com/substack/node-shell-quote ---------- -Copyright 2012 James Halliday (mail@substack.net) +Copyright 2010, 2011, 2012 James Halliday (mail@substack.net) ---------- @@ -356,6 +339,14 @@ node-form-data: https://github.com/felixge/node-form-data Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors +---------- +node-kexec: https://github.com/jprichardson/node-kexec +---------- + +Copyright (c) 2011-2012 JP Richardson + + + ============== Apache License ============== @@ -422,6 +413,20 @@ bson: https://github.com/mongodb/js-bson Copyright (c) 2012 Christian Amor Kvalheim +---------- +websocket: https://github.com/Worlize/WebSocket-Node/blob/master/LICENSE +---------- + +Brian McKelvey + + +---------- +amdefine: https://github.com/jrburke/amdefine +---------- + +Copyright (c) 2011, The Dojo Foundation + + ============ BSD Licenses @@ -579,11 +584,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND node-tar: https://github.com/isaacs/node-tar fstream: https://github.com/isaacs/fstream fstream-npm: https://github.com/isaacs/fstream-npm +fstream-ignore: https://github.com/isaacs/fstream-ignore read: https://github.com/isaacs/read block-stream: https://github.com/isaacs/block-stream node-glob: https://github.com/isaacs/node-glob chownr: https://github.com/isaacs/chownr uid-number: https://github.com/isaacs/uid-number +sigmund: https://github.com/isaacs/sigmund ---------- Copyright (c) Isaac Z. Schlueter @@ -657,7 +664,7 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------- -d3: https://github.com/mbostock/d3 +D3: https://github.com/mbostock/d3 ---------- Copyright (c) 2012, Michael Bostock @@ -756,10 +763,40 @@ OF THE POSSIBILITY OF SUCH DAMAGE. ---------- -D3: http://d3js.org/ +ycssmin: https://github.com/yui/ycssmin ---------- -Copyright (c) 2012, Michael Bostock +Copyright 2012 Yahoo! Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Yahoo! Inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +---------- +source-map: https://github.com/mozilla/source-map +---------- + +Copyright (c) 2009-2011, Mozilla Foundation and contributors All rights reserved. Redistribution and use in source and binary forms, with or without @@ -772,19 +809,20 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* The name Michael Bostock may not be used to endorse or promote products - derived from this software without specific prior written permission. +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -834,6 +872,7 @@ mimelib: https://github.com/andris9/mimelib mailcomposer: https://github.com/andris9/mailcomposer simplesmtp: https://github.com/andris9/simplesmtp rai: https://github.com/andris9/rai +xoauth2: https://github.com/andris9/xoauth2 ---------- Copyright (c) 2012 Andris Reinman @@ -876,6 +915,26 @@ By Isaac Z. Schlueter (http://blog.izs.me/) 0. You just DO WHAT THE FUCK YOU WANT TO. +---------- +opener: https://github.com/domenic/opener +---------- + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2012 Domenic Denicola + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + + + ---------- node-stream-buffer: https://github.com/samcday/node-stream-buffer ---------- From 9154b5d3c40241f3f909c548bb80d3e07860e7f1 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 4 Mar 2013 22:18:04 -0800 Subject: [PATCH 07/13] C.find(q, {reactive: false}).observe() should only produce initial adds. Fixes #771. --- History.md | 4 ++++ docs/client/api.html | 6 ++++++ packages/minimongo/minimongo.js | 15 +++++++++++---- packages/minimongo/minimongo_tests.js | 8 ++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/History.md b/History.md index 36ee74e686..fe7e409a2f 100644 --- a/History.md +++ b/History.md @@ -11,6 +11,10 @@ * `coffeescript` package: Support literate Coffeescript files with the extension `.litcoffee`. +* If you call `observe` or `observeChanges` on a cursor created with + `Collection.find(query, {reactive: false})`, it now only calls initial add + callbacks and does not continue watching the query. + * Fixed bug where an empty `fields` object was sometimes passed to a `changed` callback of `Cursor.observeChanges`. diff --git a/docs/client/api.html b/docs/client/api.html index 6a261a76a9..72111483e5 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -968,6 +968,9 @@ or more times to deliver the initial results of the query. `observe` returns a live query handle, which is an object with a `stop` method. Call `stop` with no arguments to stop calling the callback functions and tear down the query. **The query will run forever until you call this.** +(If the cursor was created with the option `reactive` set to false, it will +only deliver the initial results and will not call any further callbacks; +it is not necessary to call `stop` on the handle.) {{> api_box cursor_observe_changes}} @@ -1018,6 +1021,9 @@ zero or more times to deliver the initial results of the query. `observeChanges` returns a live query handle, which is an object with a `stop` method. Call `stop` with no arguments to stop calling the callback functions and tear down the query. **The query will run forever until you call this.** +(If the cursor was created with the option `reactive` set to false, it will +only deliver the initial results and will not call any further callbacks; +it is not necessary to call `stop` on the handle.) {{#note}} Unlike `observe`, `observeChanges` does not provide absolute position diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 30bd53b08c..c75646ede8 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -224,10 +224,8 @@ _.extend(LocalCollection.Cursor.prototype, { if (!ordered && (self.skip || self.limit)) throw new Error("must use ordered observe with skip or limit"); - var qid = self.collection.next_qid++; - // XXX merge this object w/ "this" Cursor. they're the same. - var query = self.collection.queries[qid] = { + var query = { selector_f: self.selector_f, // not fast pathed sort_f: ordered && self.sort_f, results_snapshot: null, @@ -235,6 +233,14 @@ _.extend(LocalCollection.Cursor.prototype, { cursor: this, observeChanges: options.observeChanges }; + var qid; + + // Non-reactive queries call added[Before] and then never call anything + // else. + if (self.reactive) { + qid = self.collection.next_qid++; + self.collection.queries[qid] = query; + } query.results = self._getRawObjects(ordered); if (self.collection.paused) query.results_snapshot = (ordered ? [] : {}); @@ -273,7 +279,8 @@ _.extend(LocalCollection.Cursor.prototype, { _.extend(handle, { collection: self.collection, stop: function () { - delete self.collection.queries[qid]; + if (self.reactive) + delete self.collection.queries[qid]; } }); return handle; diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 272836676f..3131382bb2 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -1411,6 +1411,14 @@ _.each([true, false], function (ordered) { c.update(4, {$set: {eek: 5}}); expect('cb4_'); handle.stop(); + + // Test observe with reactive: false. + handle = c.find({tags: "flower"}, {reactive: false}).observe(makecb('c')); + expect('ac4_ac5_'); + // This insert shouldn't trigger a callback because it's not reactive. + c.insert({_id: 6, name: "river", tags: ["flower"]}); + expect(''); + handle.stop(); }); }); From 57b067b5bdbdbc5fe72e04aa80b340b0f080b8b5 Mon Sep 17 00:00:00 2001 From: Chris Mather Date: Mon, 4 Mar 2013 11:54:53 -0800 Subject: [PATCH 08/13] Make eventData an empty object if no data context is found. This prevents the thisArg from getting set to `window` if eventData is undefined. See: http://stackoverflow.com/questions/15137206/the-context-of-this-in-meteor-template-event-handlers-using-handlebars-for-te/15149650#15149650 --- packages/spark/spark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spark/spark.js b/packages/spark/spark.js index 48533e30fc..9736565100 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -801,7 +801,7 @@ Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) { } // Found a matching handler. Call it. - var eventData = Spark.getDataContext(event.currentTarget); + var eventData = Spark.getDataContext(event.currentTarget) || {}; var landmarkRange = findParentOfType(Spark._ANNOTATION_LANDMARK, range); var landmark = (landmarkRange && landmarkRange.landmark); From 3dff27d7edfbecbc6c1c96e1c65449625b9c894a Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 5 Mar 2013 11:26:02 -0800 Subject: [PATCH 09/13] History.md update --- History.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/History.md b/History.md index fe7e409a2f..82e812c750 100644 --- a/History.md +++ b/History.md @@ -21,6 +21,9 @@ * Fixed `{$type: 5}` selectors for binary values on browsers that do not support `Uint8Array` +* In an event handler, if the data context is falsey, default it to `{}` rather + than to the global object. + * Stop making `Session` available on the server; it's not very useful there. ## v0.5.7 From 01bbc58b11b50bbcc60b4389c72cb5119d85bd7d Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Tue, 5 Mar 2013 23:59:19 -0800 Subject: [PATCH 10/13] Editing pass on History.md --- History.md | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/History.md b/History.md index 82e812c750..a2e7327082 100644 --- a/History.md +++ b/History.md @@ -2,29 +2,43 @@ ## vNEXT * Publish functions may now return an array of cursors to publish. Currently, - the cursors must all be from different collections. + the cursors must all be from different collections. #716 * User documents have id's when onCreateUser and validateNewUser hooks run. -* Removed all restrictions on EJSON types in MongoDB, even user-defined ones. +* Encode and store custom EJSON types in MongoDB. -* `coffeescript` package: Support literate Coffeescript files with the extension - `.litcoffee`. +* Support literate CoffeeScript files with the extension `.litcoffee`. #766 -* If you call `observe` or `observeChanges` on a cursor created with - `Collection.find(query, {reactive: false})`, it now only calls initial add - callbacks and does not continue watching the query. +* If you call `observe` or `observeChanges` on a cursor created with the + `reactive: false` option, it now only calls initial add callbacks and + does not continue watching the query. #771 -* Fixed bug where an empty `fields` object was sometimes passed to a `changed` - callback of `Cursor.observeChanges`. +* In an event handler, if the data context is falsey, default it to `{}` + rather than to the global object. #777 -* Fixed `{$type: 5}` selectors for binary values on browsers that do not support - `Uint8Array` +* Revert caching header change from 0.5.5. This fixes image flicker on redraw. -* In an event handler, if the data context is falsey, default it to `{}` rather - than to the global object. +* Stop making `Session` available on the server; it's not useful there. #751 + +* Force URLs in stack traces in browser consoles to be hyperlinks. #725 + +* Suppress spurious `changed` callbacks with empty `fields` from + `Cursor.observeChanges`. + +* Fix logic bug in template branch matching. #724 + +* Make `spiderable` user-agent test case insensitive. #721 + +* Fix several bugs in EJSON type support: + * Fix `{$type: 5}` selectors for binary values on browsers that do + not support `Uint8Array`. + * Fix EJSON equality on falsey values. + * Fix for returning a scalar EJSON type from a method. #731 + +Patches contributed by GitHub users awwx, cmather, graemian, jmhredsox, +kevinxucs, krizka, mitar, raix, and rasmuserik. -* Stop making `Session` available on the server; it's not very useful there. ## v0.5.7 From ece35ebb46b5212b6606456ce7ea037271711102 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 6 Mar 2013 15:08:24 -0800 Subject: [PATCH 11/13] observe changes tests no longer leak observe handles --- .../mongo-livedata/observe_changes_tests.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/mongo-livedata/observe_changes_tests.js b/packages/mongo-livedata/observe_changes_tests.js index 366cf63554..61fb4daf92 100644 --- a/packages/mongo-livedata/observe_changes_tests.js +++ b/packages/mongo-livedata/observe_changes_tests.js @@ -27,7 +27,7 @@ _.each ([{added:'added', forceOrdered: true}, function (logger) { var barid = c.insert({thing: "stuff"}); var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); - c.find(fooid).observeChanges(logger); + var handle = c.find(fooid).observeChanges(logger); if (added === 'added') logger.expectResult(added, [fooid, {noodles: "good", bacon: "bad",apples: "ok"}]); else @@ -44,6 +44,7 @@ _.each ([{added:'added', forceOrdered: true}, c.insert({noodles: "good", bacon: "bad", apples: "ok"}); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -54,9 +55,10 @@ Tinytest.addAsync("observeChanges - single id - initial adds", function (test, o var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); - c.find(fooid).observeChanges(logger); + var handle = c.find(fooid).observeChanges(logger); logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -68,7 +70,7 @@ Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, o withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); var barid = c.insert({noodles: "good", bacon: "weird", apples: "ok"}); - c.find().observeChanges(logger); + var handle = c.find().observeChanges(logger); logger.expectResultUnordered([ {callback: "added", args: [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]}, @@ -76,6 +78,7 @@ Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, o args: [barid, {noodles: "good", bacon: "weird", apples: "ok"}]} ]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -83,7 +86,7 @@ Tinytest.addAsync("observeChanges - unordered - initial adds", function (test, o Tinytest.addAsync("observeChanges - unordered - basics", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - c.find().observeChanges(logger); + var handle = c.find().observeChanges(logger); var barid = c.insert({thing: "stuff"}); logger.expectResultOnly("added", [barid, {thing: "stuff"}]); @@ -104,6 +107,7 @@ Tinytest.addAsync("observeChanges - unordered - basics", function (test, onCompl logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad", apples: "ok"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -112,7 +116,7 @@ if (Meteor.isServer) { Tinytest.addAsync("observeChanges - unordered - specific fields", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - c.find({}, {fields:{noodles: 1, bacon: 1}}).observeChanges(logger); + var handle = c.find({}, {fields:{noodles: 1, bacon: 1}}).observeChanges(logger); var barid = c.insert({thing: "stuff"}); logger.expectResultOnly("added", [barid, {}]); @@ -133,6 +137,7 @@ if (Meteor.isServer) { logger.expectResult("added", [fooid, {noodles: "good", bacon: "bad"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); @@ -142,7 +147,7 @@ if (Meteor.isServer) { Tinytest.addAsync("observeChanges - unordered - enters and exits result set through change", function (test, onComplete) { var c = makeCollection(); withCallbackLogger(test, ["added", "changed", "removed"], Meteor.isServer, function (logger) { - c.find({noodles: "good"}).observeChanges(logger); + var handle = c.find({noodles: "good"}).observeChanges(logger); var barid = c.insert({thing: "stuff"}); var fooid = c.insert({noodles: "good", bacon: "bad", apples: "ok"}); @@ -158,6 +163,7 @@ Tinytest.addAsync("observeChanges - unordered - enters and exits result set thro c.update(fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}); logger.expectResult("added", [fooid, {noodles: "good", potatoes: "tasty", apples: "ok"}]); logger.expectNoResult(); + handle.stop(); onComplete(); }); }); From ca7c56c530cb04122f1a1155f31dd369e86e76a6 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 6 Mar 2013 15:54:23 -0800 Subject: [PATCH 12/13] Don't leak a LiveResultsSet on erroneous recursive observe. --- packages/meteor/fiber_helpers.js | 16 ++++++++++++---- packages/mongo-livedata/mongo_driver.js | 8 ++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index 07d4d40cdf..cee9407694 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -60,10 +60,12 @@ _.extend(Meteor._SynchronousQueue.prototype, { runTask: function (task) { var self = this; - if (!Fiber.current) - throw new Error("Can only call runTask in a Fiber"); - if (self._currentTaskFiber === Fiber.current) - throw new Error("Can't runTask from another task in the same fiber"); + if (!self.safeToRunTask()) { + if (Fiber.current) + throw new Error("Can't runTask from another task in the same fiber"); + else + throw new Error("Can only call runTask in a Fiber"); + } var fut = new Future; self._taskHandles.push({task: task, future: fut}); @@ -82,6 +84,12 @@ _.extend(Meteor._SynchronousQueue.prototype, { var self = this; return self._taskRunning; }, + + safeToRunTask: function () { + var self = this; + return Fiber.current && self._currentTaskFiber !== Fiber.current; + }, + _scheduleRun: function () { var self = this; diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index dd8c1403a2..0c0f6782c7 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -822,6 +822,14 @@ _.extend(LiveResultsSet.prototype, { // with a call to _pollMongo or another call to this function. _addObserveHandleAndSendInitialAdds: function (handle) { var self = this; + + // Check this before calling runTask (even though runTask does the same + // check) so that we don't leak a LiveResultsSet by incrementing + // _addHandleTasksScheduledButNotPerformed and never decrementing it. + if (!self._taskQueue.safeToRunTask()) + throw new Error( + "Can't call observe() from an observe callback on the same query"); + // Keep track of how many of these tasks are on the queue, so that // _removeObserveHandle knows if it's safe to GC. ++self._addHandleTasksScheduledButNotPerformed; From 63869d033023081fcd34ec7366062b9099de4e8e Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 6 Mar 2013 16:49:02 -0800 Subject: [PATCH 13/13] Stop keeping around so many subscriptions when we are done testing --- packages/livedata/livedata_connection.js | 10 ++++++++++ packages/test-in-browser/driver.js | 2 ++ 2 files changed, 12 insertions(+) diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 7bf2cfebe1..61971de604 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -764,6 +764,16 @@ _.extend(Meteor._LivedataConnection.prototype, { } }, + // This is very much a private function we use to make the tests + // take up fewer server resources after they complete. + _unsubscribeAll: function () { + var self = this; + _.each(_.clone(self._subscriptions), function (sub, id) { + self._send({msg: 'unsub', id: id}); + delete self._subscriptions[id]; + }); + }, + // Sends the DDP stringification of the given message object _send: function (obj) { var self = this; diff --git a/packages/test-in-browser/driver.js b/packages/test-in-browser/driver.js index 2a1e7f43c1..9a55127c9a 100644 --- a/packages/test-in-browser/driver.js +++ b/packages/test-in-browser/driver.js @@ -19,6 +19,8 @@ Meteor.startup(function () { Meteor.onTestsComplete && Meteor.onTestsComplete(); _resultsChanged(); Meteor.flush(); + + Meteor.default_connection._unsubscribeAll(); }, Session.get("groupPath")); });