diff --git a/packages/minimongo/README.md b/packages/minimongo/README.md index b3589e5a9d..d91dc2f6b8 100644 --- a/packages/minimongo/README.md +++ b/packages/minimongo/README.md @@ -1,4 +1,4 @@ -# minimongo +# Minimongo `minimongo` is reimplementation of (almost) the entire MongoDB API, against an in-memory JavaScript database. It is like a MongoDB emulator that runs inside @@ -11,4 +11,72 @@ Minimongo is used as a temporary data cache in the standard Meteor stack, to learn more about mini-databases and what they can do, see [the project page on www.meteor.com](https://www.meteor.com/mini-databases) +## Internals + +Minimongo implements the following features, mirroring the MongoDB features: + +- Selectors +- Modifiers +- Fields projections +- Querying with `sort` and `limit` +- ObjectID generation +- Geo-positional operator `$near` with GeoJSON parsing + +Internally, all documents are mapped in a single JS object from `_id` to the +document. Besides this mapping, Minimongo doesn't implement any types of +secondary indexes. + +Also, currently Minimongo doesn't implement any aggregation features. The full +list of incompatible features can be found in the NOTES file. + +Besides the MongoDB features, Minimongo implements the following for a better +integration with the Meteor stack: + +- `observe` and `observeChanges` APIs, notification callbacks when the result of +a query changes +- integration with Meteor's [Tracker](https://www.meteor.com/tracker) +- Meteor's Publish a cursor interface `_publishCursor`, that results into an + `observe` call +- Meteor's interface of `pauseObservers` and `resumeObservers`, for client-side + caches (allow latency compensation to change multiple objects at once w/o a + flicker) +- In addition to the previous point, `saveOriginals` and `retrieveOriginals` are + used to revert the local changes +- additional analysis functions for Meteor's server-side Oplog Observe Driver +(only loaded for the server-side): + * can this modifier change the result of this selector if the document didn't + match before + * can the sort order change after applying this modifier (for a given + selector) + * what is a combined projection for these selector, sorter and projection + + +### saveOriginals/retrieveOriginals & pauseObserver/resumeObservers + +This part of the implementation is very important for a smooth Optimistic UI +experience (also called "Latency Compensation") avoiding an extra flicker. + +Let's review the usual Optimistic UI flow: + +- User triggers an interaction on the client +- The simulation applies some mutations to the state locally +- The RPC is fired to be executed on the server +- After some time, the RPC returns with the result +- After some more time, RPC returns an "updated" message (on DDP level), meaning +that all the changes from RPC have persisted +- At this point we know, that all the actual changes from the server are synced, + we can throw away the simulated mutations (preserving the real changes from + the server) + +To implement the last step, Minimongo implements the following: + +- When a simulation starts, `saveOriginals` is called, to take a snapshot of the +state before the update. Internally, it is implemented in a more efficient +copy-on-write style. +- After the simulation, wait, to get all the real changes from the server. +- When the server is done with giving us new changes, we apply all the new +changes and throw away all the simulated changes (with `retrieveOriginals`). +- All massive replacements happen in between `pauseObservers` and + `resumeObservers` calls, so any user code (such as Blaze) sees the whole + change in one tick (helps to avoid the flicker). diff --git a/tools/tests/parse-stack-test.js b/tools/tests/parse-stack-test.js index b72700eabf..920eca8339 100644 --- a/tools/tests/parse-stack-test.js +++ b/tools/tests/parse-stack-test.js @@ -15,12 +15,19 @@ selftest.define("parse-stack - parse stack traces without fibers", () => { markBottom(() => { const markedErr = new Error(); - const parsedStack = parse(markedErr).outsideFiber; + + const { + outsideFiber, + insideFiber + } = parse(markedErr); + + // Don't return an empty array + selftest.expectEqual(insideFiber, undefined); // The stack trace should only contain this one function since we marked the // bottom - selftest.expectEqual(parsedStack.length, 1); - selftest.expectEqual(_.last(parsedStack[0].file.split("/")), + selftest.expectEqual(outsideFiber.length, 1); + selftest.expectEqual(_.last(outsideFiber[0].file.split("/")), "parse-stack-test.js"); })(); }); diff --git a/tools/utils/buildmessage.js b/tools/utils/buildmessage.js index cfb1b1c0f3..cbfda41c61 100644 --- a/tools/utils/buildmessage.js +++ b/tools/utils/buildmessage.js @@ -92,7 +92,7 @@ _.extend(Job.prototype, { // If a nontrivial stack trace (more than just the file and line // we already complained about), print it. var where = ""; - if (frame && frame.file) { + if (frame.file) { where += frame.file; if (frame.line) { where += ":" + frame.line; @@ -102,11 +102,11 @@ _.extend(Job.prototype, { } } - if (frame && ! frame.func && ! where) + if (! frame.func && ! where) return; // that's a pretty lame stack frame line += " at "; - if (frame && frame.func) + if (frame.func) line += frame.func + " (" + where + ")\n"; else line += where + "\n"; @@ -439,7 +439,7 @@ var error = function (message, options) { } = parseStack.parse(new Error()); // Concatenate and get rid of lines about Future and buildmessage - info.stack = outsideFiber.concat(insideFiber).slice(2); + info.stack = outsideFiber.concat(insideFiber || []).slice(2); if (typeof info.useMyCaller === 'number') { info.stack = info.stack.slice(info.useMyCaller); }