mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into appcache
This commit is contained in:
40
History.md
40
History.md
@@ -1,18 +1,44 @@
|
||||
|
||||
## vNEXT
|
||||
|
||||
* Publish functions may now return an array of cursors to publish. Currently,
|
||||
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
|
||||
|
||||
* Fixed bug where an empty `fields` object was sometimes passed to a `changed`
|
||||
callback of `Cursor.observeChanges`.
|
||||
* 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
|
||||
|
||||
* In an event handler, if the data context is falsey, default it to `{}`
|
||||
rather than to the global object. #777
|
||||
|
||||
* Revert caching header change from 0.5.5. This fixes image flicker on redraw.
|
||||
|
||||
* 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.
|
||||
|
||||
* Fixed `{$type: 5}` selectors for binary values on browsers that do not support
|
||||
`Uint8Array`
|
||||
|
||||
## v0.5.7
|
||||
|
||||
|
||||
127
LICENSE.txt
127
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 <christkv@gmail.com>
|
||||
|
||||
|
||||
----------
|
||||
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 <i@izs.me> (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 <domenic@domenicdenicola.com>
|
||||
|
||||
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
|
||||
----------
|
||||
|
||||
@@ -45,7 +45,15 @@ that Meteor will call each time a client subscribes to the name.
|
||||
|
||||
Publish functions can return a
|
||||
[`Collection.Cursor`](#meteor_collection_cursor), in which case Meteor
|
||||
will publish that cursor's documents.
|
||||
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 () {
|
||||
@@ -59,6 +67,14 @@ will publish that cursor's documents.
|
||||
return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}});
|
||||
});
|
||||
|
||||
// publish dependent documents and simulate joins
|
||||
Meteor.publish("roomAndMessages", 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
|
||||
@@ -136,7 +152,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 +916,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:
|
||||
|
||||
@@ -952,12 +968,15 @@ 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}}
|
||||
|
||||
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.
|
||||
|
||||
@@ -1002,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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -821,8 +821,9 @@ _.extend(Meteor._LivedataSubscription.prototype, {
|
||||
|
||||
// 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
|
||||
// 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,8 +835,40 @@ _.extend(Meteor._LivedataSubscription.prototype, {
|
||||
// reactiveThingy.publishMe();
|
||||
// });
|
||||
// };
|
||||
if (res && res._publishCursor)
|
||||
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;
|
||||
}
|
||||
collectionNames[collectionName] = true;
|
||||
};
|
||||
|
||||
_.each(res, function (cur) {
|
||||
cur._publishCursor(self);
|
||||
});
|
||||
self.ready();
|
||||
}
|
||||
},
|
||||
|
||||
// This calls all stop callbacks and prevents the handler from updating any
|
||||
|
||||
@@ -277,4 +277,47 @@ 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) {
|
||||
// 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) {
|
||||
// Suppress the log of the expected internal error.
|
||||
Meteor._suppress_log(1);
|
||||
return [
|
||||
One.find(),
|
||||
"not a cursor",
|
||||
Two.find()
|
||||
];
|
||||
} else
|
||||
throw "unexpected options";
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -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, "Internal server error"))
|
||||
});
|
||||
},
|
||||
function (test, expect) {
|
||||
Meteor.subscribe("multiPublish", {notCursor: 1}, {
|
||||
onReady: failure(),
|
||||
onError: expect(failure(test, 500, "Internal server error"))
|
||||
});
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// XXX some things to test in greater detail:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -398,14 +398,21 @@ Cursor.prototype._publishCursor = function (sub) {
|
||||
}
|
||||
});
|
||||
|
||||
// observeChanges only returns after the initial added callbacks have run.
|
||||
// mark subscription as ready.
|
||||
sub.ready();
|
||||
// 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();});
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
Cursor.prototype.observe = function (callbacks) {
|
||||
var self = this;
|
||||
return LocalCollection._observeFromObserveChanges(self, callbacks);
|
||||
@@ -815,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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,6 +19,8 @@ Meteor.startup(function () {
|
||||
Meteor.onTestsComplete && Meteor.onTestsComplete();
|
||||
_resultsChanged();
|
||||
Meteor.flush();
|
||||
|
||||
Meteor.default_connection._unsubscribeAll();
|
||||
}, Session.get("groupPath"));
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user