diff --git a/History.md b/History.md index 22da11be7f..7df60f3002 100644 --- a/History.md +++ b/History.md @@ -1,14 +1,21 @@ ## vNEXT +* User documents have id's when onCreateUser and validateNewUser hooks run. + * Removed all restrictions on EJSON types in MongoDB, even user-defined ones. +* `coffeescript` package: Support literate Coffeescript files with the extension + `.litcoffee`. + * Fixed bug where an empty `fields` object was sometimes passed to a `changed` callback of `Cursor.observeChanges`. * 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. diff --git a/admin/generate-dev-bundle.sh b/admin/generate-dev-bundle.sh index 6f7697d5ed..99f9ead347 100755 --- a/admin/generate-dev-bundle.sh +++ b/admin/generate-dev-bundle.sh @@ -3,7 +3,7 @@ set -e set -u -BUNDLE_VERSION=0.2.20 +BUNDLE_VERSION=0.2.21 UNAME=$(uname) ARCH=$(uname -m) @@ -79,7 +79,7 @@ which npm cd "$DIR/lib/node_modules" npm install connect@1.9.2 # not 2.x yet. sockjs doesn't work w/ new connect npm install optimist@0.3.5 -npm install coffee-script@1.4.0 +npm install coffee-script@1.5.0 npm install less@1.3.3 npm install stylus@0.30.1 npm install nib@0.8.2 diff --git a/app/lib/files.js b/app/lib/files.js index 8514f6d1d7..250af92a17 100644 --- a/app/lib/files.js +++ b/app/lib/files.js @@ -98,7 +98,14 @@ var files = module.exports = { // given a path, returns true if it is a meteor application (has a // .meteor directory with a 'packages' file). false otherwise. is_app_dir: function (filepath) { - return fs.existsSync(path.join(filepath, '.meteor', 'packages')); + // .meteor/packages must be a *file*, not a directory; future versions of + // meteor will create a directory at $HOME/.meteor which contains a + // subdirectory called packages, but this doesn't make it an app! + try { // use try/catch to avoid the additional syscall to fs.existsSync + return fs.statSync(path.join(filepath, '.meteor', 'packages')).isFile(); + } catch (e) { + return false; + } }, // given a path, returns true if it is a meteor package (is a diff --git a/app/meteor/update.js b/app/meteor/update.js index f2dffb7e8f..7f6a17ec07 100644 --- a/app/meteor/update.js +++ b/app/meteor/update.js @@ -14,7 +14,8 @@ var _ = require('underscore'); // refuse to update if we're in a git checkout. if (files.in_checkout()) { - console.log("This is a git checkout. Update it manually with 'git pull'."); + console.log("Your Meteor installation is a git checkout. Update it " + + "manually with 'git pull'."); process.exit(1); } diff --git a/app/server/server.js b/app/server/server.js index f58d581955..115a66f8a3 100644 --- a/app/server/server.js +++ b/app/server/server.js @@ -180,8 +180,21 @@ var run = function () { var app = connect.createServer(); var static_cacheable_path = path.join(bundle_dir, 'static_cacheable'); if (fs.existsSync(static_cacheable_path)) - app.use(gzippo.staticGzip(static_cacheable_path, {clientMaxAge: 1000 * 60 * 60 * 24 * 365})); - app.use(gzippo.staticGzip(path.join(bundle_dir, 'static'), {clientMaxAge: 0})); + // cacheable files are files that should never change. Typically + // named by their hash (eg meteor bundled js and css files). + // cache them ~forever (1yr) + app.use(gzippo.staticGzip(static_cacheable_path, + {clientMaxAge: 1000 * 60 * 60 * 24 * 365})); + // cache non-cacheable file anyway. This isn't really correct, as + // users can change the files and changes won't propogate + // immediately. However, if we don't cache them, browsers will + // 'flicker' when rerendering images. Eventually we will probably want + // to rewrite URLs of static assets to include a query parameter to + // bust caches. That way we can both get good caching behavior and + // allow users to change assets without delay. + // https://github.com/meteor/meteor/issues/773 + app.use(gzippo.staticGzip(path.join(bundle_dir, 'static'), + {clientMaxAge: 1000 * 60 * 60 * 24})); // read bundle config file var info_raw = diff --git a/docs/client/api.html b/docs/client/api.html index 2066769ca8..683c8dbb11 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -548,16 +548,12 @@ your collection should be published to which users. {{#warning}} In this release, Minimongo has some limitations: -* `$elemMatch` is not supported in selectors. * `$pull` in modifiers can only accept certain kinds of selectors. -* In selectors, dot notation may not work correctly. * `$` to denote the matched array position is not supported in modifier. * `findAndModify`, upsert, aggregate functions, and map/reduce aren't supported. -* The supported types are String, Number, Boolean, Array, -and Object. All of these will be addressed in a future release. For full Minimongo release notes, see packages/minimongo/NOTES diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 67ebcecfab..188053285b 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -153,7 +153,7 @@ can be used from both client and server code. // server: populate collections with some initial documents Rooms.insert({name: "Conference Room A"}); var myRooms = Rooms.find({}).fetch(); - Messages.insert({text: "Hello world", room: myRooms[0].id}); + Messages.insert({text: "Hello world", room: myRooms[0]._id}); Parties.insert({name: "Super Bowl Party"}); Each document set is defined by a publish function on the server. The @@ -164,7 +164,7 @@ case is to publish a database query. // server: publish all room documents Meteor.publish("all-rooms", function () { return Rooms.find(); // everything - ); + }); // server: publish all messages for a given room Meteor.publish("messages", function (roomId) { @@ -377,7 +377,7 @@ queries are stopped, and they stop updating. For this reason, you never have to worry about the [zombie templates](http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/) that plague hand-written update logic. To protect your elements from cleanup, -just make sure that they on-screen before your code returns to the event loop, +just make sure that they are on-screen before your code returns to the event loop, or before any call you make to [`Meteor.flush`](#meteor_flush). Another thorny problem in hand-written applications is element @@ -574,10 +574,12 @@ Meteor environment in arbitrary ways. Some examples of packages are: it's faster to add a package. * The [underscore](#underscore) package extends both the - client and server environments. Many of the core Meteor features, - including Minimongo, the Session object, and reactive Handlebars - templates, are implemented as internal packages automatically - included with every Meteor application. + client and server environments. + +Many of the core Meteor features, +including Minimongo, the Session object, and reactive Handlebars +templates, are implemented as internal packages automatically +included with every Meteor application. You can see a list of available packages with [`meteor list`](#meteorlist), add packages to your project diff --git a/docs/client/packages/coffeescript.html b/docs/client/packages/coffeescript.html index 3f24ce8c54..ce1ee8f46e 100644 --- a/docs/client/packages/coffeescript.html +++ b/docs/client/packages/coffeescript.html @@ -8,7 +8,8 @@ code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime. CoffeeScript is supported on both the client and the server. Files -ending with `.coffee` are automatically compiled to JavaScript. +ending with `.coffee` or `.litcoffee` are automatically compiled to +JavaScript. See for more information. diff --git a/meteor b/meteor index d54264e098..d0866cc412 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.2.20 +BUNDLE_VERSION=0.2.21 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 3e621979e0..32548ddd15 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -122,9 +122,19 @@ return user; }; Accounts.insertUserDoc = function (options, user) { - // add created at timestamp (and protect passed in user object from - // modification) - user = _.extend({createdAt: +(new Date)}, user); + // - clone user document, to protect from modification + // - add createdAt timestamp + // - prepare an _id, so that you can modify other collections (eg + // create a first task for every new user) + // + // XXX If the onCreateUser or validateNewUser hooks fail, we might + // end up having modified some other collection + // inappropriately. The solution is probably to have onCreateUser + // accept two callbacks - one that gets called before inserting + // the user document (in which you can modify its contents), and + // one that gets called after (in which you should change other + // collections) + user = _.extend({createdAt: +(new Date), _id: Random.id()}, user); var result = {}; if (options.generateLoginToken) { diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index fc0759acf4..bbb4c0a98b 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -7,6 +7,16 @@ Tinytest.add('accounts - config validates keys', function (test) { }); }); +Tinytest.add('accounts - validateNewUser gets passed user with _id', function (test) { + var idInValidateNewUser; + Accounts.validateNewUser(function (user) { + idInValidateNewUser = user._id; + return true; + }); + var newUserId = Accounts.updateOrCreateUserFromExternalService('foobook', {id: Random.id()}).id; + test.equal(idInValidateNewUser, newUserId); +}); + Tinytest.add('accounts - updateOrCreateUserFromExternalService - Facebook', function (test) { var facebookId = Random.id(); diff --git a/packages/coffeescript/coffeescript_tests.js b/packages/coffeescript/coffeescript_tests.js index 79193dc27a..631675042e 100644 --- a/packages/coffeescript/coffeescript_tests.js +++ b/packages/coffeescript/coffeescript_tests.js @@ -1,4 +1,7 @@ Tinytest.add("coffeescript - presence", function(test) { test.isTrue(Meteor.__COFFEESCRIPT_PRESENT); }); +Tinytest.add("literate coffeescript - presence", function(test) { + test.isTrue(Meteor.__LITCOFFEESCRIPT_PRESENT); +}); diff --git a/packages/coffeescript/litcoffeescript_tests.litcoffee b/packages/coffeescript/litcoffeescript_tests.litcoffee new file mode 100644 index 0000000000..48b0bd9ee3 --- /dev/null +++ b/packages/coffeescript/litcoffeescript_tests.litcoffee @@ -0,0 +1,6 @@ +This file is just the same as `coffeescript_tests.coffee`, first we set a +property, which we check for in `coffeescript_tests.js`, and then a trivial +testcase. + + Meteor.__LITCOFFEESCRIPT_PRESENT = true + Tinytest.add "literate coffeescript - compile", (test) -> test.isTrue true diff --git a/packages/coffeescript/package.js b/packages/coffeescript/package.js index a1a1440420..f1c975d4ad 100644 --- a/packages/coffeescript/package.js +++ b/packages/coffeescript/package.js @@ -4,30 +4,32 @@ Package.describe({ var coffee = require('coffee-script'); var fs = require('fs'); +var path = require('path'); -Package.register_extension( - "coffee", function (bundle, source_path, serve_path, where) { - serve_path = serve_path + '.js'; +var coffeescript_handler = function(bundle, source_path, serve_path, where) { + serve_path = serve_path + '.js'; - var contents = fs.readFileSync(source_path); - var options = {bare: true, filename: source_path}; - try { - contents = coffee.compile(contents.toString('utf8'), options); - } catch (e) { - return bundle.error(e.message); - } - - contents = new Buffer(contents); - bundle.add_resource({ - type: "js", - path: serve_path, - data: contents, - where: where - }); + var contents = fs.readFileSync(source_path); + var options = {bare: true, filename: source_path, literate: path.extname(source_path) === '.litcoffee'}; + try { + contents = coffee.compile(contents.toString('utf8'), options); + } catch (e) { + return bundle.error(e.message); } -); + + contents = new Buffer(contents); + bundle.add_resource({ + type: "js", + path: serve_path, + data: contents, + where: where + }); +} + +Package.register_extension("coffee", coffeescript_handler); +Package.register_extension("litcoffee", coffeescript_handler); Package.on_test(function (api) { - api.add_files(['coffeescript_tests.coffee', 'coffeescript_tests.js'], + api.add_files(['coffeescript_tests.coffee', 'litcoffeescript_tests.litcoffee', 'coffeescript_tests.js'], ['client', 'server']); }); diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index 08b1b49f70..c07f8ee8a9 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -104,7 +104,10 @@ EJSON._isCustomType = function (obj) { var adjustTypesToJSONValue = EJSON._adjustTypesToJSONValue = function (obj) { if (obj === null) - return; + return null; + var maybeChanged = toJSONValueHelper(obj); + if (maybeChanged !== undefined) + return maybeChanged; _.each(obj, function (value, key) { if (typeof value !== 'object' && value !== undefined) return; // continue @@ -117,6 +120,7 @@ EJSON._adjustTypesToJSONValue = function (obj) { // at this level. recurse. adjustTypesToJSONValue(value); }); + return obj; }; // Either return the JSON-compatible version of the argument, or undefined (if @@ -142,11 +146,16 @@ EJSON.toJSONValue = function (item) { return item; }; -//for both arrays and objects +//for both arrays and objects. Tries its best to just +// use the object you hand it, but may return something +// different if the object you hand it itself needs changing. var adjustTypesFromJSONValue = EJSON._adjustTypesFromJSONValue = function (obj) { if (obj === null) - return; + return null; + var maybeChanged = fromJSONValueHelper(obj); + if (maybeChanged !== obj) + return maybeChanged; _.each(obj, function (value, key) { if (typeof value === 'object') { var changed = fromJSONValueHelper(value); @@ -159,6 +168,7 @@ EJSON._adjustTypesFromJSONValue = function (obj) { adjustTypesFromJSONValue(value); } }); + return obj; }; // Either return the argument changed to have the non-json @@ -212,6 +222,8 @@ EJSON.equals = function (a, b, options) { var keyOrderSensitive = !!(options && options.keyOrderSensitive); if (a === b) return true; + if (!a || !b) // if either one is falsy, they'd have to be === to be equal + return false; if (!(typeof a === 'object' && typeof b === 'object')) return false; if (a instanceof Date && b instanceof Date) diff --git a/packages/ejson/ejson_test.js b/packages/ejson/ejson_test.js index 958ddb6668..3d6859b1b5 100644 --- a/packages/ejson/ejson_test.js +++ b/packages/ejson/ejson_test.js @@ -31,3 +31,12 @@ Tinytest.add("ejson - nesting and literal", function (test) { var roundTrip = EJSON.fromJSONValue(eObj); test.equal(obj, roundTrip); }); + +Tinytest.add("ejson - equality and falsiness", function (test) { + test.isTrue(EJSON.equals(null, null)); + test.isTrue(EJSON.equals(undefined, undefined)); + test.isFalse(EJSON.equals({foo: "foo"}, null)); + test.isFalse(EJSON.equals(null, {foo: "foo"})); + test.isFalse(EJSON.equals(undefined, {foo: "foo"})); + test.isFalse(EJSON.equals({foo: "foo"}, undefined)); +}); diff --git a/packages/livedata/livedata_common.js b/packages/livedata/livedata_common.js index 2fa787a4c1..fb30e9ebc6 100644 --- a/packages/livedata/livedata_common.js +++ b/packages/livedata/livedata_common.js @@ -79,7 +79,7 @@ Meteor._parseDDP = function (stringMessage) { _.each(['fields', 'params', 'result'], function (field) { if (_.has(msg, field)) - EJSON._adjustTypesFromJSONValue(msg[field]); + msg[field] = EJSON._adjustTypesFromJSONValue(msg[field]); }); return msg; @@ -105,7 +105,7 @@ Meteor._stringifyDDP = function (msg) { // adjust types to basic _.each(['fields', 'params', 'result'], function (field) { if (_.has(copy, field)) - EJSON._adjustTypesToJSONValue(copy[field]); + copy[field] = EJSON._adjustTypesToJSONValue(copy[field]); }); if (msg.id && typeof msg.id !== 'string') { throw new Error("Message id is not a string"); diff --git a/packages/livedata/livedata_test_service.js b/packages/livedata/livedata_test_service.js index 028449e3af..8b6e33241f 100644 --- a/packages/livedata/livedata_test_service.js +++ b/packages/livedata/livedata_test_service.js @@ -5,6 +5,9 @@ Meteor.methods({ echo: function (/* arguments */) { return _.toArray(arguments); }, + echoOne: function (/*arguments*/) { + return arguments[0]; + }, exception: function (where, intended) { var shouldThrow = (Meteor.isServer && where === "server") || diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index c10d87646e..ef590e0415 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -57,12 +57,15 @@ Tinytest.add("livedata - methods with colliding names", function (test) { var echoTest = function (item) { return function (test, expect) { - if (Meteor.isServer) + if (Meteor.isServer) { test.equal(Meteor.call("echo", item), [item]); + test.equal(Meteor.call("echoOne", item), item); + } if (Meteor.isClient) test.equal(Meteor.call("echo", item), undefined); test.equal(Meteor.call("echo", item, expect(undefined, [item])), undefined); + test.equal(Meteor.call("echoOne", item, expect(undefined, item)), undefined); }; }; diff --git a/packages/minimongo/NOTES b/packages/minimongo/NOTES index 8b7d43b396..017aeda689 100644 --- a/packages/minimongo/NOTES +++ b/packages/minimongo/NOTES @@ -1,11 +1,5 @@ ## CORE FUNCTIONALITY THAT'S MISSING ## -Dot notation is not supported at all in selectors. (Specifically: it's -implemented but not tested, and ordinal indexing (into lists) isn't -implemented at all.) - -In selectors, $elemMatch is not implemented. - In update, $pull can't take a selector like {$gt: 3} (but it can take {x: 3}, or {x: {$gt: 3}} -- basically, selectors that match documents can be used, but selectors that are intended to match non-document @@ -18,11 +12,9 @@ Sort does not support subkeys. You can sort on 'a', but not 'a.b'. ## ON TYPES ## -Only the basic JSON types are implemented (string, number, boolean, -null, array, object). These additional Mongo types aren't implemented, -or aren't implemented completely: object id, binary data, date, -timestamp, symbol, javascript code (with or without scope), -minkey/maxkey, regexp (stored in the database.) +We don't implement these Mongo types completely: timestamp (but date works), +symbol, javascript code (with or without scope), minkey/maxkey, regexp (stored +in the database), fixed-precision integers. If your Javascript implementation enumerates the keys of objects in a consistent order, then we implement object equality and object @@ -37,25 +29,15 @@ integer type, and we don't support the integer type yet.) ## API ## -Our findLive() extension needs a full set of tests (it doesn't have -any yet.) - find() doesn't support retrieving a subset of fields. It always -returns the whole doc. findLive() doesn't support it either (it might -be nice, to limit what changes you'll receive notifications on.) - -There's no such thing as a cursor. find() just returns the whole -result set. +returns the whole doc. find() doesn't support the min and max parameters. findAndModify isn't supported. -The aggregate functions count(), distinct(), and group() aren't -supported. Map/reduce isn't supported. - -findLive() doesn't support any kind of pagination. You always get all -of the results. +The aggregate functions distinct(), and group() aren't supported. Map/reduce +isn't supported. update() should have a clear stance on atomicity (both in terms of multiple ops on a single document, and on multi-document update mode.) diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 65660da6b9..d6ede91ea0 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -692,7 +692,6 @@ testAsyncMulti('mongo-livedata - document with a custom type, ' + idGeneration, test.equal(cursor.count(), 1); var inColl = coll.findOne(); test.isTrue(inColl); - debugger; inColl && test.equal(inColl.d.speak(), "woof"); })); } 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) { diff --git a/packages/spiderable/spiderable.js b/packages/spiderable/spiderable.js index d9e915f243..dd298bef05 100644 --- a/packages/spiderable/spiderable.js +++ b/packages/spiderable/spiderable.js @@ -9,7 +9,7 @@ // not obey the _escaped_fragment_ protocol. The page is served // statically to any client whos user agent matches any of these // regexps. (possibly make this list configurable by user). - var AGENTS = [/^facebookexternalhit/]; + var AGENTS = [/^facebookexternalhit/i, /^linkedinbot/i]; // how long to let phantomjs run before we kill it var REQUEST_TIMEOUT = 15*1000;